Criando um jogo com o engine do quake3

Bem vindo ao mundo dos mods para o quake. O Quake3 parece destinado a ser a melhor plataforma para criação de mods – temos aqui a vantagem de programar em C em um IDE padrão da indústria e os recursos de segurança dos qvm. Temos também o melhor engine para gráficos do momento. Sem mencionar o suporte para ctf e bots. Isso nos dá uma plataforma sólida para criação de modificações.

O primeiro capítulo desse tutorial não é um tutorial de fato – apenas descreve os passos básicos de criação de mods (o que é, como fazê-los funcionar, etc). Se você for um veterano na criação de mods (ou apenas queira sujar um pouco as mãos), pule direto para o capítulo 2.

Ainda aqui? Ok, a melhor forma de aprender a codificar um mod é praticando. Dessa forma, tente ir codificando cada capítulo desse artigo. Sim, está correto – não apenas leia, siga em frente e compile cada um. Você aprenderá dicas preciosas apenas olhando o código e até digitando-o. Mas, para começar… alguns tópicos introdutórios.

1. O que é um mod?

Uma modificação do quake3 ou apenas ‘mod’ é essencialmente um novo jogo que você pode jogar no quake3, usando regras, armas, níveis e personagens modificados. Desde o início do quake1, a id Software tem generosamente permitido que a comunidade crie os seus prṕrios mods (Em minha opinião, são esses mods que tem permitido ao quake sua longevidade e fomentado a grande comunidade que tem hoje). Quem pode esquecer do “Captura da bandeira” (ctf) do Zoid e o “Team Fortress” (tfs) para o quake 1 ? Se você não jogou um mod anida, vá e jogue um cts.

(3 horas depois)

…de volta já? Ok. É um grande sentimento não é? Quem poderia pensar que um simples jogo “corra em volta e atire em todo mundo” como o quake poderia ter sido modificado em um verdadeiro clássico? Existem centenas de modificações e muitos clássicos.  Dê uma olhada na seção de mods do planetquake.

2. Quem cria os mods?

Bem, potencialmente, você (desde que já tenha lido esse artigo). Se estiver interessado em criar mods porque ama o sentimento da captura a bandeira com seus amigos e você considera que pode contribuir com algo para a comunidade quake, ótimo. Se quiser entrar na indústria de jogos, os mods são uma excelente forma de mostrar suas habilidades. Quem sabe, crie um mod matador e você pode ser o novo Zoid (ele trabalha hoje como contratado da id).

Se você quiser criar um mod e vendê-lo, você está fazendo isso errado – os mods podem apenas ser distribuídos gratuitamente (leia o acordo de licenciamento). Você pode distribuir os seus mods em cd ou eles podem ser distribuídos eletronicamente.

3. Como jogar com um mod?

Você encontrará um mod de seu interesse em uma página web em algum lugar, ou possivelmente em seu servidor (in-game ou externo, como o GameSpy). Se for um “mod apenas para servidor” então tudo o que você precisa fazer é se conectar ao servidor (você não precisa baixar nada). Pronto, você está jogando! Muitos mods, porém, requerem o download de um cliente… você precisará baixar alguns arquivos e extrai-los em seu diretório quake3/mod_name (Como em tudo que você baixa, tenha cuidado).

Nós devemos ver suporte para auto-download de qvm no futuro, mas até lá estamos presos a um download manual. Uma vez que o mod esteja instalado, encontre um servidor que rode o mod e vá jogar. Para rodar o seu próprio mod, execute C:\quake3\quake3.exe +set fs_game mod_name.

4. O que é preciso para criar um mod?

  • 1. Uma boa ideia
  • 2. Habilidade de programação em C
  • 3. Conhecimento da base de código q3a
  • 4. Membros de equipe que complementem seu trabalho (mappers, modellers, etc)
  • 5. Partir pra ação !

Isso é basicamente tudo. Pegue uma ideia, alguma experiência com código e vá fazer loucuras com o seu compilado. Quando tiver criado uma versão jogável de seu mod, chame os seus amigos para testá-lo. Se você chegar no ponto deles estarem gritando um com o outro, então você fez tudo que devia ter feito bem. Uma vez que todos os bugs forem corrigidos, libere seu mod na rede, de forma que outras pessoas possam jogá-lo. Disponibilize seu próprio servidor se você puder, ou peça a um administrador para rodar seu mod por um tempo.

5. Seu próprio mod!

Então, você criou um mod, liberou ele na rede, ele alcançou fama e você se tornou famoso na comunidade quake. Isso acontece com muitas pessoas depois de criar bons mods.

Mas o caminho pode ser difícil as vezes – esteja preparado para receber pilas de críticas construtivas (especialmente se você liberou um mod sem realmente testá-lo 100%). Esteja preparado para ficar preso por vários dias tentando descobrir porque seu jogo está travando. Esteja preparado para ter membros da equipe abandonando o barco. Esteja preparado para fazer escolhas entre o quake e outras pessoas da sua vida (sim, é um grande compromisso). Mas acima de tudo, esteja preparado para ter um monte de diversão.

…Ainda aqui? Ótimo. Agora vamos partir para codificação e “manter tudo isso real”.

2 – Pronto pra começar

(Ou, COMO BOTAR ESSE MALDITO COMPILADOR PARA TRABALHAR)

Ok, hora de sujar as mãos. Visite fileplanet.com se ainda não tiver os seguintes itens:

  • quake3 (cd)
  • Q3PointRelease_Jan1300.exe
  • Q3AGameSource.exe

Você precisará de um compilador. Para esse artigo, foi usado o MSVC6, que é uma IDE padrão da indústria e os códigos fonte do jogo vem com projetos para ele, assim você pode começar dessa forma. Se for um estudante, pode conseguir uma versão barata dele. Ou obter um do trabalho.

1. Configurando o MSVC

O seu sistema precisa ser configurado para rodar o MSVC. Abra o seu autoexec.bat. Adicione a seguinte linha no final:

C:\MICROSOFT VISUAL STUDIO\VC98\BIN\VCVARS32.BAT

Se seu VCVAR32.BAT estiver localizado em outro local então modifique a linha de acordo. Se não possuir um VCVARS32.BAT então re-instale o MSVC. Em seguida, precisamos adicionar algo ao nosso path – que é onde as ferramentas para criar as qvm estão localizadas. Adicione a seguinte linha em seu autoexec.bar (no final do arquivo, ou logo abaixo das outras linhas que definem seu path):

SET PATH= C:\QUAKE3\BIN_NT;%PATH%

Reinicie seu computador.

2. Instalando

Certifique-se de que tanto o quake3 quanto o point release estejam instalados e funcionando (sim, você PRECISA ter o point release instalado… Nem a versão 1.11 nem um quake3.exe crackeado irão funcionar). Instale tudo no diretório c:\quake3 se puder (o Q3AGameSource roda somante nesse diretório… de outra forma você irá precisar de algumas alterações de diretório).

Em seguida instale o código fonte do q3a (rodando o arquivo Q3AGameSource.exe). Você deve poder ver agora um diretório quake3\source.

3. Abra o projeto

Se possuir o MSVC instalado então podemos começar a desenvolver nosso mod – dê um clique duplo em quake3\source\q3agame.dsw. Você deve ver o projeto ser aberto em MSVC. Se você não estiver usando o MSVC então precisará criar seu projeto usando os arquivos de código fonte do diretório quake3\source.

Ok, se você for novo no MSVC, segue agora alguns pontos sobre ele. Clique na aba “FileView” no painel a esquerda – você verá uma arvore nessa seção. Observe que existem três projetos aqui. ‘game’ é o projeto mais interessante – ele define como o servidor roda o jogo. ‘cgame’ contém o código do cliente, e ‘ui’ contém coisas relativas a interface com o usuário in-game (exemplo: menus).

Iremos fazer muitas das modificações nos arquivos de ‘Source files’ e ‘Header files’ no propjeto ‘game’. Dê um clique duplo na arvore para abri-la na janela do editor. Abra algum dos arquivos de código fonte para visualiza-los (não se preocupe se eles parecerem densos a primeira vista). Abaixo segue um pedaço do arquivo mais interessante, g_weapon.c:

/*
======================================================================

ROCKET

======================================================================
*/

void Weapon_RocketLauncher_Fire (gentity_t *ent) {
        gentity_t       *m;

        m = fire_rocket (ent, muzzle, forward);
        m->damage *= s_quadFactor;
        m->splashDamage *= s_quadFactor;

//      VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );
        // "real" physics
}

4. Configurando o diretório onde as dll estão

Queremos que as dll apareçam em nosso diretório quake3\source. Por padrão gamex86.dll é compilador no diretório quake3\source\Debug (que precisamos alterar).

Ok, ainda no MSVC, vá para o menu “Project” e escolha “Settings (ALT-F7)”. Dê um clique em ‘game’ na lista de projetos no lado esquerdo. Iremos alterar o local onde o arquivo qagamex86.dll é compilador. Clique na aba “Link” e dê uma olhada em “Output file name” – por padrão estará configurado para “..\Debug/qagamex86.dll”. Mude para “..\qagamex86.dll”.

5. Compilando o projeto

Ok. No MSVC, vá para p menu “Project” e escolha “Set Active Project -> 2 game”.

Para compilar o projeto ‘game’, vamos escolher “Build qagamex86.dll (F7)” no menu “Build”. O MSVC informará a você o progresso da operação na painel na parte de baixo. Todo o processo deve finalizar com a mensagem “0 error(s), 0 warning(s)” (desde que você não tenha modificado nada ainda). O arquivo qagamex86.dll deve aparecer no diretório quake3\source.

6. Testando a .dll

Ok, vamos agora executar o comando quake3\quake3.exe +set fs_game source +map q3dm1. Presisone a tecla ‘tilde’ (no canto superior esquerdo do seu teclado) e use as teclas pageup e pagedown para mover-se através do log… Você deve ver uma mensagem em algum lugar que o arquivo dll foi encontrado e carregado com sucesso. Se ao invés disso você visualizar uma mensagem dizendo  que nenhuma dll foi encontrada, então ela deve estar no lugar errado… Se o quake não pode encontrar sua dll então obviamente seu mod não pode ser executado.

7. Criar uma QVM

Uma qvm, ou ‘quake virtual machine’, é bem fácil de ser criada (assumindo que seu ambiente esteja configurado corretamente). O que é uma qvm? Eu gosto de pensar nela como um substituto seguro para a dll. É mais seguro que uma dll pois não é capaz de fazer coisas ruins que uma dll pode (apagar seu disco rígido, por exemplo). Não se pode dizer que seja 100% segura (lembre quando foi quebrada a segurança dos applets Java pela sobrecarga ao pilha da máquina virtual?) mas o qvm é certamente mais seguro que a dll.

Vamos criar uma qvm agora. Abra o terminal e entre no diretório quake3\source\game. Em seguida rode script game.bat. Voilá – você verá um arquivo *.qvm aparecer no diretório quake3\source\vm. Podemos executar cgame.bat e ui.bat (em seus respectivos diretórios) para criar mais duas qvm. Você pode distribuir esses arquivos qvm para outras pessoas para que elas possam rodar seu mod.

Ok, agora estamos prontos para começar a desenvolver nosso primeiro mod. Vamos dar uma olhada em como fazer isso.

3 – Hello, Qworld!

A partir de agora, é um pré-requisito que você saiba compilar o qagamex86.dll – se não conseguiu isso ainda, terá que dar uma olhada nos capítulos anteriores.

Nesse capítulo, não iremos imprimir “Hello, qworld!” no terminal, mas sim modificar a velocidade dos foguetes (como sugerido no arquivo Leia-me). Iremos também aprender algumas coisas sobre os foguetes no decorrer dessa jornada.

1. Crie uma cópia da base de código (codebase)

É bom sempre manter um cópia limpa da base de código original. Imagine se você estragar um dos arquivos fontes originais (deletar linhas crucias ou o que seja), você precisará de um backup, certo? Ao invés de re-instalar o Q3aGameSource.exe a cada vez, vamos manter o diretório quake3\source com nossa base de código original. Nunca crie mods nesse diretório! Criaremos nossos mods em diretórios individuais… vamos começar criando o diretório quake3\mymod.

Ok, então simplesmente copiamos o diretório quake3\source para quake3\mymod. Dê um clique duplo no arquivo quake3\mymod\q3agame.dsw – a partir desse momento, siga todos os passos desse artigo usando essa base de código. Se você modificou o “Output file name” corretamente no primeiro capítulo, a dll de mymod deve ser compilada no diretório quake3\mymod – compile o projeto ‘game’ para checar isso.

A qualquer momento que terminarmos de mexer com o código em um dos capítulos a seguir, e quisermos atualizar a base de código de volta ao original, simplesmente copie quake3\source para quake3\mymod. Você pode ter quantos diretórios de mods quiser.

2. Encontre algo interessante

Vamos carregar o projeto quake3\mymod\q3agame.dsw no MSVC. Maximize o MSVC – você precisará de todo os espaço que seu desktop pode lhe dar. Certifique-se de que o projeto ‘game’ seja o projeto ativo. Selecione “File View” no painel da esquerda, e encontre o arquivo ‘g_missile.c’. Dê um clique duplo nesse arquivo – o arquivo deve ser aberto na janela do editor.

Vá para a linha 362 (aperte Ctrl-G para um atalho) e encontre a função fire_rocket:

/*
=================
fire_rocket blah
=================
*/
gentity_t *fire_rocket (gentity_t *self, vec3_t start, vec3_t dir)
 {
    gentity_t       *bolt;

    VectorNormalize (dir);

    bolt = G_Spawn();
    bolt->classname = "rocket";
    bolt->nextthink = level.time + 10000;
    bolt->think = G_ExplodeMissile;
    bolt->s.eType = ET_MISSILE;
    bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    bolt->s.weapon = WP_ROCKET_LAUNCHER;
    bolt->r.ownerNum = self->s.number;
    bolt->parent = self;
    bolt->damage = 100;
    bolt->splashDamage = 100;
    bolt->splashRadius = 120;
    bolt->methodOfDeath = MOD_ROCKET;
    bolt->splashMethodOfDeath = MOD_ROCKET_SPLASH;
    bolt->clipmask = MASK_SHOT;

    bolt->s.pos.trType = TR_LINEAR;
    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;   // move a bit
on the very first frame
    VectorCopy( start, bolt->s.pos.trBase );
    VectorScale( dir, 900, bolt->s.pos.trDelta );
    SnapVector( bolt->s.pos.trDelta );                       // save net bandwidth
    VectorCopy (start, bolt->r.currentOrigin);

    return bolt;
}

A linha 372 cria uma nova entidade (chamada bolt). A função G_Spawn() cria um entidade em branco… o próximo trabalho é botar pra fora todos os detalhes (que entidade é essa? quais as características? etc). As próximos 20 e tantas linhas de código definem tudo isso.

As linhas 373-375 definem o bolt como sendo um “rocket”, e ‘pensará’ novamente em 10 segundos (o tempo é medido em milissegundos no mundo quake, assim 1000 equivale a 1 segundo). Quando o foguete (“rocket”) ‘pensa’, ele chama a função G_ExplodeMissile. Em outras palavras, se ele não tocou em nada, nosso foguete irá explodir em 10 segundos. Isso previne que nossos foguetes fiquem vagando pelo espaço para sempre.

Além disso, descobrimos alguma coisa sobre o jogo da nossa olhada pelo código! Vamos ver o que ainda podemos ver sobre os foguetes.

Podemos ver que o foguete lida dá 100 de danos (diretamente ou respigando), e o raio do respingo é de 120 unidades. A linha 391 nos diz que o foguete se move a velocidade de 900 unidades/segundo. Existe ainda uma carga inteira de constantes usadas nessa função (constantes são escritas geralmente em maiúsculas)… podemos adivinhar o que a maioria significa. ‘ET_MISSILE’ por exemplo, significa ‘Entity Tyoe: Missile’ (em oposição a um item ou um jogador).

3. Nossa primeira alteração no código

Vamos modificar a linha 391 para ficar da seguinte forma:

VectorScale( dir, 300, bolt->s.pos.trDelta );

Pressione F7 para compilar nossa dll. Inicie o quake (Execute “quake3\quake3.exe +set fs_game mymod +map q3dm1”) e veja se funciona – seus foguetes devem estar bem mais lentos.

Pronto! Esse foi nosso primeiro capitulo. Vamos mantendo a chama acesa…

4 – Aceleração permanente

Ok, de agora em diante iremos aprender mais e mais sobre o mundo quake. Porém, você precisará de algum conhecimento básico de C. Se você for um programador novato de C, talvez deva dar uma olhada em algum livro sobre programação em C. Ou até melhor, navegar em alguns sites – existem grandes fontes de recursos on-line disponíveis.

Como estamos com sorte, existem alguns tutoriais sobre programação em C no site do quake2. Existem vários tutoriais sobre programação para quake2 também.  Embora você não possa seguir esses tutoriais para criar mods para o quake3, pode pegar algumas idéias navegando por eles.

Time to start coding again, “Innit”.

Como eles fazem isso?

(Como sempre, assumiremos que você esteja dominando o MSVC e esteja trabalhando no diretório quake3\mymod).

Agora, vamos começar nosso trabalho de Sherlock Holmes… vamos descobrir como um comportamento em particular é implementado na base de código. Que tal falarmos sobre o ‘Haste’? Vamos dar um “Find in Files” por “Haste” (na barra de ferramentas existe um pequeno ícone com uma pasta amarela com binóculos – use esse íucone se quiser). O painel da parte de baixo da tela irá pesquisar por todo o código fonte e arquivos de cabeçalhos por linhas contendo a palavra ‘haste’ (você pode aumentar esse painel arrastando a bordar para cima).

Searching for 'Haste'...
D:\q3\source\game\ai_chat.c(349):               bs->inventory[INVENTORY_HASTE] ||
D:\q3\source\game\ai_dmq3.c(564):       bs->inventory[INVENTORY_HASTE] =
bs->cur_ps.powerups[PW_HASTE] != 0;
D:\q3\source\game\bg_misc.c(536):/*QUAKED item_haste (.3 .3 1) (-16 -16 -16) (16 16 16)
suspended
D:\q3\source\game\bg_misc.c(539):               "item_haste",
D:\q3\source\game\bg_misc.c(540):               "sound/items/haste.wav",
D:\q3\source\game\bg_misc.c(541):        { "models/powerups/instant/haste.md3",
D:\q3\source\game\bg_misc.c(542):               "models/powerups/instant/haste_ring.md3",
D:\q3\source\game\bg_misc.c(544):/* icon */             "icons/haste",
D:\q3\source\game\bg_misc.c(548):               PW_HASTE,
D:\q3\source\game\bg_pmove.c(1568):     if ( pm->ps->powerups[PW_HASTE] ) {
D:\q3\source\game\bg_public.h(214):     PW_HASTE,
D:\q3\source\game\g_active.c(604):      if ( client->ps.powerups[PW_HASTE] ) {
D:\q3\source\game\inv.h(34):#define INVENTORY_HASTE                             29
D:\q3\source\game\inv.h(79):#define MODELINDEX_HASTE                    30
14 occurrence(s) have been found.

Ok, existem algumas coisas interessantes aqui. O pedaço que diz “item_haste” provavelmente define os atributos para o ícone Haste. De fato, “models/powerups/instant/haste.md3” define que modelo representa o ícone Haste.Vamos continuar olhando abaixo… existem duas linhas que dizem if ( blah blah [PW_HASTE]). Isso é interessante, porque a sentença if está provavelmente dizendo “se o jogador possuir a Haste então faça isso, senão faça isso, ou aquilo”. Dê um clique duplo nessa linha:

D:\q3\source\game\g_active.c(604):     if ( client->ps.powerups[PW_HASTE] ) {

Bang, g_active.c deve ser aberto na janela do editor e a linha 604 terá um pequeno marcado próximo a ela. Você deve ver isso:

       if ( client->ps.powerups[PW_HASTE] ) {
               client->ps.speed *= 1.3;
       }

Algum conhecimento sobre C é necessário aqui: Uma sentença if é sempre da forma “if (isso_é_verdadeiro) { faça_isso}”. O perador “*=” significa que o que estiver a esquerda deve ser multiplicado pelo que estiver a direita. Em outras palavras “multiplique client->ps.speed por 1.3”.

Se dermos uma olhada acima e abaixo, podemos ver que esse segmento de código está em algum lugar no meio da função ClientThink_real. Acabamos de descobrir a parte do código que faz com que você se mova mais rápido quando tem um “Haste rune”. Adicionalmente, podemos ver que você pode ser mover exatamente 30% mais rápido que o normal.

Vamos fazer uma alteração

Ok, vamos nos dar aceleração permanente. Modifique o código na linha 604 para ficar dessa forma:

       if ( qtrue ) {
               client->ps.speed *= 1.3;
       }

A parte que acelera o objeto (client->ps.speed *= 1.3) não é executado apenas quando o jogador possuir um mecanismo de aceleração. Por ajustarmos a condição de if para ser true (verdadeiro), a aceleração é sempre executada, independentemente dos mecanismos de aceleração. Em outras palavras: todos os jogadores tem aceleração permanente. Compile e tome uma surra dos bots!

Hint: don’t play this mod for too long… you might get a little dizzy.

5 – Trilha perfurante da blindagem (Armor Piercing Rails)

Você fica frustado quando o inimigo que você mirou na cabeça de repente se esconde atrás de uma pilastra? Vamos ensinar o frangote uma lição. Se quiser, dê uma lida sobre Vetores. (Goste ou não, um bom entendimento da matemática de vetores é essencial para a programação para o quake).

Como as trilhas funcionam?

Vamos encontrar a função weapon_railgun_fire na lina 334 de g_weapon.c:

/*
=================
weapon_railgun_fire
=================
*/
#define MAX_RAIL_HITS   4
void weapon_railgun_fire (gentity_t *ent) {
        vec3_t          end;
        trace_t         trace;
        gentity_t       *tent;
        gentity_t       *traceEnt;
        int                     damage;
        int                     radiusDamage;
        int                     i;
        int                     hits;
        int                     unlinked;
        gentity_t       *unlinkedEntities[MAX_RAIL_HITS];

        damage = 100 * s_quadFactor;
        radiusDamage = 30 * s_quadFactor;

        VectorMA (muzzle, 8192, forward, end);

        // trace only against the solids, so the railgun will go through people
        unlinked = 0;
        hits = 0;
        do {
                trap_Trace (&trace, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
                if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) {
                        break;
                }
                traceEnt = &g_entities[ trace.entityNum ];
                if ( traceEnt->takedamage ) {
                        if( LogAccuracyHit( traceEnt, ent ) ) {
                                hits++;
                        }
                        G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0,
                        MOD_RAILGUN);
                }
                if ( trace.contents & CONTENTS_SOLID ) {
                        break;          // we hit something solid enough to stop the beam
                }
                // unlink this entity, so the next trace will go past it
                trap_UnlinkEntity( traceEnt );
                unlinkedEntities[unlinked] = traceEnt;
                unlinked++;
        } while ( unlinked < MAX_RAIL_HITS );

        // link back in any entities we unlinked
        for ( i = 0 ; i < unlinked ; i++ ) {
                trap_LinkEntity( unlinkedEntities[i] );
        }

        // the final trace endpos will be the terminal point of the rail trail

        // snap the endpos to integers to save net bandwidth, but nudged towards the line
        SnapVectorTowards( trace.endpos, muzzle );

        // send railgun beam effect
        tent = G_TempEntity( trace.endpos, EV_RAILTRAIL );

        // set player number for custom colors on the railtrail
        tent->s.clientNum = ent->s.clientNum;

        VectorCopy( muzzle, tent->s.origin2 );
        // move origin a bit to come closer to the drawn gun muzzle
        VectorMA( tent->s.origin2, 4, right, tent->s.origin2 );
        VectorMA( tent->s.origin2, -1, up, tent->s.origin2 );

        // no explosion at end if SURF_NOIMPACT, but still make the trail
        if ( trace.surfaceFlags & SURF_NOIMPACT ) {
                tent->s.eventParm = 255;        // don't make the explosion at the end
        } else {
                tent->s.eventParm = DirToByte( trace.plane.normal );
        }
        tent->s.clientNum = ent->s.clientNum;

        // give the shooter a reward sound if they have made two railgun hits in a row
        if ( hits == 0 ) {
                // complete miss
                ent->client->accurateCount = 0;
        } else {
                // check for "impressive" reward sound
                ent->client->accurateCount += hits;
                if ( ent->client->accurateCount >= 2 ) {
                        ent->client->accurateCount -= 2;
                        ent->client->ps.persistant[PERS_REWARD_COUNT]++;
                        ent->client->ps.persistant[PERS_REWARD] = REWARD_IMPRESSIVE;
                        ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++;
                        // add the sprite over the player's head
                        ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE |
                        EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET );
                        ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE;
                        ent->client->rewardTime = level.time + REWARD_SPRITE_TIME;
                }
                ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
        }

}

Em primeiro lugar, assume que ‘muzzle’ é um vetor localização em frente ao atirador, e ‘forward’ um vetor direção apontando na direção que o cliente está encarando.

OK, existe um loop aqui que basicamente faz {rastreie o inimigo até que você dispare algo; se ele encontra uma parede, saia do loop; senão repita }. O do {} simplesmente repete tudo estiver entre as chaves até que a condição de saída seja alcançada. Dentro do loop, a primeira linha chama a função trap_Trace(…) – isso rastreia uma linha através do espaço desde a origem (muzzle) na direção no trilho (forward) por uma distância máxima de 8192 unidades. Se nós dispararmos algo, o rastreamento retorna informações sobre o que atingimos.

Se não atingirmos nada, o loop simplesmente acaba. Se atingirmos alguma coisa que sofra danos (if (traceEnt->takedamage), por exemplo, um jogador ou um botão) então o alvo recebe um dano (com G_Damage). Se atingirmos uma parede ou um canto ( if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) ) então o loop é encerrado; Nós queremos mudar isso.

Fogo através das paredes

Em primeiro lugar, precisamos de uma variável para um vetor (logo abaixo das outras variáveis, próxima ao início da função). Depois da linha 344, adicione isso:

void weapon_railgun_fire (gentity_t *ent) {
       vec3_t          end;
       trace_t         trace;
       gentity_t       *tent;
       gentity_t       *traceEnt;
       int                     damage;
       int                     radiusDamage;
       int                     i;
       int                     hits;
       int                     unlinked;
       gentity_t       *unlinkedEntities[MAX_RAIL_HITS];
 vec3_t tracefrom; // SUM

 

Algumas linhas a seguir, podemos ver a chamada a função ‘VectorMA’. Isso cria um vetor ‘end’  que tem 8192 unidades na direção ‘forward’ de ‘muzzle’. Precisamos fazer uma cópia de ‘muzzle’ em ‘traceform’, assim adicionamos uma linha para que nosso código se parece com isso:

       damage = 100 * s_quadFactor;
       radiusDamage = 30 * s_quadFactor;
       VectorMA (muzzle, 8192, forward, end);
 VectorCopy (muzzle, tracefrom);

Em seguida, vamos alterar a chamada da função trap_Trace de forma que  a trajetória do disparo seja traçada de ‘traceform’ (ao invés de ‘muzzle’)… existe uma boa razão para isso, leia a seguir:

       // rastreia somente contra sólidos, de forma que a trajetória da arma passe pelas pessoas
       unlinked = 0;
       hits = 0;
       do {
 trap_Trace (&trace, tracefrom, NULL, NULL, end, ent->s.number, MASK_SHOT );

 

Em seguida, queremos alterar o comportamento do bloco if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) { … }. Essa sentença if detecta se nosso disparo corre na direçã ode um ‘sólido’ (uma parede ou o céu). O que queremos mudar? Bem, ao invés de simplesmente sair do loop (fazendo com que seja o fim para nosso disparo), queremos que o disparo continue através das pareces. Naturalmente, ainda queremos que o disparo pare quando atinge o céu. O código para fazer isso é:

               if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) {
 // SUM break if we hit the sky if (trace.surfaceFlags & SURF_SKY) break; // Hypo: break if we traversed length of vector tracefrom if (trace.fraction == 1.0) break; // otherwise continue tracing thru walls VectorMA (trace.endpos,1,forward,tracefrom); continue;
               }
               traceEnt = &g_entities[ trace.entityNum ];

 

Em outras palavras, se atingirmos o céu (cheque o surfaceFlags), pare. Se percorremos o tamanho total do vetor, também precisamos parar (não há nenhuma garantia que atingiremos o céu). De outra forma, atingiremos uma parede sólida. Ajustamos a nova posição de ‘traceform’ para 1 unidade a frente do ponto de impacto (o que efetivamente atravessará a parede). O loop então irá se repetir; nos levando de volta ao início do loop.

Todos podem enxergar isso?

Essa seção é uma idéia do [WarZone]: do jeito que está, a trilha da arma possivelmente não seja vista por pessoas distantes do nível. A pequena adição abaixo certifica que a trilha seja ‘transmitida’ para todos (não apenas para aqueles nas proximidades do atirador). Isso faz sentido, seria estúpido ter um disparo de uma arma que atravesse a parede e atingisse algo se a vitima não pudesse ver a trilha do disparo.

       // no explosion at end if SURF_NOIMPACT, but still make the trail
       if ( trace.surfaceFlags & SURF_NOIMPACT ) {
               tent->s.eventParm = 255;        // don't make the explosion at the end
       } else {
               tent->s.eventParm = DirToByte( trace.plane.normal );
       }
       tent->s.clientNum = ent->s.clientNum;
       //send the effect to everyone since it tunnels through walls
 tent->r.svFlags |= SVF_BROADCAST;

O resto da função continua o mesmo, e nossa arma agora deve atravessar paredes.

6 – Disparando foguetes

Esse capítulo mostrará como fazer com que os foguetes saltarem das paredes usando um comando do terminal que ativa/desativa o comportamento de lançamento de foguete.

Adicione uma nova Flag de Entidade

Abra o arquivo g_local.h e adicione a linha em vermelho após a linha 32:

// gentity->flags
#define FL_GODMODE              0x00000010
#define FL_NOTARGET             0x00000020
#define FL_TEAMSLAVE            0x00000400      // not the first on the team
#define FL_NO_KNOCKBACK         0x00000800
#define FL_DROPPED_ITEM         0x00001000
#define FL_NO_BOTS              0x00002000      // spawn point not for bot use
#define FL_NO_HUMANS            0x00004000      // spawn point just for bots
#define FL_ROCKETBOUNCE 0x00008000

Se você estiver acostumado a trabalhar com ‘flags’ ou ‘bits’ então essas coisas devem serem familiares para você. Se parecer como grego, alguma explicação sobre ‘números hexadecimais’ e ‘operadores de bits’ podem ser úteis.

Implemente um comando ‘Rbounce’

Agoea abra o arquivo g_cmds.c e adicione as linhas em vermelho depois da linha 1022:

 

/*
=================
Cmd_RBounce_f
=================
*/
void Cmd_RBounce_f( gentity_t *ent ) {

       char *msg; // message to player

       ent->flags ^= FL_ROCKETBOUNCE;

       if (!(ent->flags & FL_ROCKETBOUNCE))
               msg = "Rocket Bounce OFF\n";
       else
               msg = "Rocket Bounce ON\n";
       trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg));
}

 

Isso permite que um jogador possa alternar o lançamento de foguetes entre ligado e desligado. Agora vá para a linha 1155 e adicione a linha em vermelho:

       else if (Q_stricmp (cmd, "setviewpos") == 0)
       Cmd_SetViewpos_f( ent ); else if (Q_stricmp (cmd, "rbounce") == 0) Cmd_RBounce_f( ent );

Essa parte simplesmente captura um comando que o jogador dispara pelo terminal (digitando “\rbounce” por exemplo) e chame a função Cmd_RBounce_f(). Gostamos de pensar nessa parte do código como nosso ‘gancho’ para o mundo exterior.

Modifique a física do foguete

       bolt = G_Spawn();
       bolt->classname = "rocket"; if (self->flags & FL_ROCKETBOUNCE) bolt->nextthink = level.time + 2500; else bolt->nextthink = level.time + 10000;
       bolt->think = G_ExplodeMissile;
       bolt->s.eType = ET_MISSILE;
       bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
       bolt->s.weapon = WP_ROCKET_LAUNCHER; if (self->flags & FL_ROCKETBOUNCE) bolt->s.eFlags = EF_BOUNCE;
       bolt->r.ownerNum = self->s.number;

A sentença if (self->flags & FL_ROCKETBOUNCE) testa se um cliente em particular possui a propriedade FL_ROCKETBOUNCE ligada. Se tiver, o foguete irá explodir depois de 2.5 segundos (ao invés de 10). Essa parte mágica é a adição da propriedade EF_BOUNCE aos eFlags do Foguete (que se supõe estar na linha 192 do arquivo bg_public.h, que contém uma lista de todas as flags EF_).

Re-compile o arquivo qvm de seu jogo e aprecie o resultado.

7 – Disfarces

Ensine uma lição a esses bots, ao estilo predador! Vamos adicionar um comportamento legal de disfarce que também diminui a saúde dos jogadores enquanto estiver ativado.

Adicione uma nova flag de entidade

Em primeiro lugar iremos adicionar uma nova flag de entidade chamada FL_CLOAK que usaremos para ativar e desativar nosso disfarce. Abra o arquivo g_local.h e adicione o código a seguir logo após a linha 32:

#define FL_NO_HUMANS       0x00004000     // spawn point just for bots
#define FL_CLOAK 0x00010000 // health cloaking

Crie um novo comando de console

Abra o arquivo g_cmds.c e adicione o código abaixo logo após a linha 1022:

 

/* ================= Cmd_Cloak_f ================= */ void Cmd_Cloak_f( gentity_t *ent ) { char *msg; // message to player ent->flags ^= FL_CLOAK; if (!(ent->flags & FL_CLOAK)) { msg = "Cloaking OFF\n"; ent->client->ps.powerups[PW_INVIS] = level.time; // Removes the invisible powerup from the player } else { msg = "Cloaking ON\n"; ent->client->ps.powerups[PW_INVIS] = level.time + 1000000000; // Gives the invisible powerup to the player } trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); }

Tudo que fizemos aqui foi criar uma nova função que será chamada quando você usar o comando /cloak que estaremos adicionando a seguir. Essa função mudará o estado atual de FL_CLOAK, ativando ou desativando o escudo de invisibilidade e imprimindo uma mensagem para o jogador.

Mas o que significa esse “level.time + 1000000000”? O que estamos fazendo aqui é tornando o escudo de invisibilidade ativo por 1666 minutos. Não precisamos que dure mais que isso porque estaremos diminuindo a saúde do jogador até que atinja o nível 10 e depois auto-desativamos o disfarce.

Agora precisamos adicionar o comando /cloak e fazê-lo chamar a função que criamos. Vá para a linha 1095 e adicione o código a seguir:

else if (Q_stricmp (cmd, "setviewpos") == 0)
       Cmd_SetViewpos_f( ent ); else if (Q_stricmp (cmd, "cloak") == 0) Cmd_Cloak_f( ent );

Faça a saúde do cliente ser diminuída com o tempo

Iremos agora fazer com que a saúde do jogador seja diminuída a cada segundo que o disfarce está ativo. Abra g_active.c e vá para linha 397 em ClientTimeActions. Essas funções serão chamadas a cada segundo, sendo perfeito para nosso comportamento de disfarce. Modifique o código existente:

// count down health when over max
if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) {
       ent->health--;

para ficar de acordo com o seguinte:

if (!(ent->flags & FL_CLOAK)) { // count down health when over max and not cloaked. if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { ent->health--; } } else { // count down health when cloaked. ent->health--; if ( ent->health < 11) { ent->flags ^= FL_CLOAK; ent->client->ps.powerups[PW_INVIS] = level.time; } }

Vamos dar uma olhada no que fizemos. Em primeiro lugar, checamos se FL_CLOAK está ativo, se não estiver então checamos que a saúde do jogador está num nível maior do que 100 (STAT_MAX_HEALTH), Se estiver nesse nível, diminuiremos a saúde do jogador. Se o jogador estiver disfarçado, a seção “else” será executada, o que significa que a saúde dos jogadores será diminuída. A razão para que façamos dessa maneira é porque não queremos que a saúde do jogador seja diminuída em 2 unidades se estiver disfarçado e sua saúde esteja acima de STAT_MAX_HEALTH. Temos também que forçar a desativação do disfarce quando a saúde do jogador atingir o nível 10.

4. Não deixe a energia do jogador cair quando estiver disfarçado

O próximo passo é remover a energia para o disfarce logo antes da energia normal ser descartada quando o jogador morre. Precisamos fazer isso porque se um jogador morre enquanto estiver disfarçado, a energia da invisibilidade irá  surgir e o próximo jogador terá invisibilidade ilimitada, sem que sua saúde seja diminuída (o que seria bem injusto se você pensar nisso).

Abra o arquivo g_combat.c e vá para a linha 67, e adicione o código a seguir:

 

if (self->flags & FL_CLOAK) { // remove the invisible powerup if the player is cloaked. self->client->ps.powerups[PW_INVIS] = level.time; } 

E é isso! Crie o seu arquivo qvm e veja o resultado.

 

  • Fábio Silva Lopes

    nco sei se vc ainda mexe com esse blog, mais responde ai se sim

  • Fábio Silva Lopes

    e o designe , animacao e tal???