VGA – Tutorial 7 – Denthor

                       --==[ PARTE 7 ]==--

Para obter os programas mencionados neste tutorial, por favor baixe o zip com a versão antiga do tutorial, neste link.

[Nota: essas coisas entre colchetes foram adicionadas pelo Snownan. O texto original ficou quase todo inalterado, exceto pela inclusão do material em C++]

Introdução

Alô! Por solicitação popular, essa parte é toda sobre animação. Vou falar sobre três métodos de fazer animação no PC, e me concentrarei especificamente em uma, que será demonstrada no código de amostra, em anexo.

Embora não seja muito usada em programação de demos, a animação é geralmente usada em programação de jogos, o que pode ser quase recompensante 😉

Nesta parte também serei muito mais mesquinho com código assembly 🙂 Teremos incluída uma putpixel em assembly puro muito rápida, um comando de flip de tela em asm, uma de flip-parcial, e uma ou duas outras. Vou explicar como elas funcionam em detalhe, então isso pode ser usado como um pouco de treinamento em assembly também.

A propósito, perdoem-me por essa parte ter demorado tanto a sair, mas eu só terminei minhas provas há poucos dias atrás, e é claro que elas tinham preferência ;-). Eu tenho também notado que a Mailbox BBS não funciona mais, então o treinamento será colocado regularmente para as listas de BBS mostradas no fim desse tutorial.

Se você gostaria de me contactar, ou ao time, há muitos modos que você pode fazê-lo:
1) Escrever uma mensagem para Grant Smith/Denthor/Asphyxia em email privado na ASPHYXIA BBS.
2) Escrever uma mensagem aqui na conferência de Programação, na
For Your Eyes Only BBS (do qual eu sou Moderador)
De preferência use isso se você tem uma pergunta geral
de programação ou problemas de que outros se beneficiariam.
4) Escrever para Denthor, Eze ou Livewire na Connectix.
5) Escrever para:
Grant Smith
P.O.Box 270 Kloof
3640
Natal
6) Ligar para mim (Grant Smith) no número (031) 73 2129
(deixe uma mensagem se você ligar quando eu estiver na faculdade)
7) Escreva para mcphail@beastie.cs.und.ac.za na InterNet, e
mencione a palavra Denthor perto do topo da carta.

(by Renato: tem algo de errado na numeração do Denthor… HAHAHAH Olha lá em cima de novo e você vai ver que ele pulou do 2 para o 4… Viva o Copy/Paste :-)))) )

OBS1 : Se você é um representante de uma companhia ou BBS e quer que a ASPHYXIA faça um demo para você, mande um email pra mim; podemos discutir.
OBS2 : Se você fez/tentou fazer um demo, MANDE PARA MIM! Estamos nos sentindo muito solitários e queremos encontrar/ajudar/trocar código com outros grupos de demos. O que você tem a perder? Mande uma mensagem aqui e podemos ver como transferir. Nós realmente queremos ouvir de você.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Os princípios da Animação

Tenho certeza de que todos vocês já viram um jogo de computador com animação uma vez ou outra. Há umas poucas coisas que uma sequência de animação deve fazer de modo a dar uma impressão de realismo. Primeiramente, ela deve se mover, de preferência usando frames diferentes para aumentar o realismo (por exemplo, com um homem andando você deveria ter frames diferentes com os braços e pernas em posições diferentes). Em segundo lugar, ela não deve destruir o fundo, mas restaurá-lo depois que passar por ele.

Isso parece óbvio o suficiente, mas pode ser muito difícil de codificar quando você não tem ideia de como fazer pra alcançar aquilo.

Nesse treinamento vamos discutir vários métodos para conseguir esses dois objetivos.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Frames e Controle de Objeto

É muito óbvio que para a maioria das animações terem sucesso, você precisa ter muitos frames do objeto em várias poses (tal como um homem com vários frames dele andando). Quando mostrados um após o outro, estes dão a impressão de movimento natural.

Então, como armazenamos esses frames? Ouço vocês gritando. Bem, o método óbvio é armazenar em arrays. Após desenhar um frame em Autodesk Animator e salvar como um arquivo .CEL, geralmente usamos o seguinte código para carregá-lo:

TYPE icon = Array [1..50,1..50] of byte;

VAR tree : icon; 

Procedure LoadCEL (FileName :  string; ScrPtr : pointer);
 var
   Fil : file;
   Buf : array [1..1024] of byte;
   BlocksRead, Count : word;
 begin
   assign (Fil, FileName);
   reset (Fil, 1);
   BlockRead (Fil, Buf, 800);    { Lê e ignora o cabeçalho de 800 bytes }
   Count := 0; BlocksRead := $FFFF;
   while (not eof (Fil)) and (BlocksRead <> 0) do begin
     BlockRead (Fil, mem [seg (ScrPtr^): ofs (ScrPtr^) + Count], 1024, BlocksRead);
     Count := Count + 1024;
   end;
   close (Fil);
 end;
 BEGIN
   Loadcel ('Tree.CEL',addr (tree));
 END.

[Nota: Isso poderia ter sido facilmente convertido para C++, mas eu acabei de converter aquele programa gigantesco, e eu não estou com vontade fazer isso. 🙂 Se você não sabe Pascal, vai ter que brigar com esse.]

Agora temos a figura de 50×50 de TREE.CEL em nosso array tree. podemos acessar esse array da maneira normal (ex.: col:=tree [25,30]). Se o frame é grande, ou se você tem muitos frames, tente usar ponteiros (veja partes anteriores)

Agora que temos a figura, como controlamos o objeto? E se quisermos manipular várias árvores passeando pela tela cada uma fazendo seu próprio trajeto? A solução é ter um registro de informação para cada árvore. Uma estrutura de dados típica poderia parecer com o seguinte:

TYPE Treeinfo = Record
                   x,y:word;  { Onde a árvore está }
                   speed:byte; { O quão rápido ela está se movendo }
                   Direction:byte; { Para onde a árvore está indo }
                   frame:byte      { Em que frame de animação a árvore está nesse momento }
                   active:boolean; { É para a árvore ser mostrada nesse momento? }
                 END;
 VAR Forest : Array [1..20] of Treeinfo;

Você tem agora 20 árvores, cada uma com sua própria informação, posição, etc. Essas são acessadas desse modo:

Forest [15].x:=100;

Isso setaria a coordenada x da 15a árvore para 100.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Restaurando o Background que foi sobreescrito

Vou discutir três métodos de fazer isso. Esses NÃO SÃO NECESSARIAMENTE O ÚNICO OU MELHOR MODO DE FAZER ISSO! Você deve experimentar e decidir o que é o melhor para seu tipo de programa em particular.

MÉTODO 1 :

Passo 1 : Criar duas páginas virtuais, Vaddr e Vaddr2.
[Nota: a versão em C++ usa Vaddr1 e Vaddr2]
Passo 2 : Desenhe o fundo em Vaddr2.
Passo 3 : “Flip” Vaddr2 para Vaddr.
Passo 4 : Desenhe todos os objetos do primeiro plano em Vaddr.
Passo 5 : “Flip” Vaddr para VGA.
Passo 6 : Repetir a partir do 3 continuamente.

Em ASCII, se parece com isso:

+---------+           +---------+           +---------+
|         |           |         |           |         |
|  VGA    | <=======  |  VADDR  |  <======  |  VADDR2 |
|         |           | (bckgnd)|           | (bckgnd)|
|         |           |+(icons) |           |         |
+---------+           +---------+           +---------+

As vantagens desse abordagem são que ela é clara, a leitura contínua do fundo não é necessária, não há flicker e é simples de implementar. As desvantagens são que duas telas virtuais de 64000 bytes são necessárias, e o procedimento não é muito rápido por causa da velocidade do “flip”.

MÉTODO 2 :

Passo 1 : Desenhar o background na VGA.
Passo 2 : Capture a porção do fundo onde o ícone será colocado.
Passo 3 : Coloque o ícone.
Passo 4 : Substitua a porção do background do passo 2 sobre o ícone.
Passo 5 : Repetir a partir do passo 2 continuamente.

Em termos de ASCII…

  +---------+
  |      +--|------- + Background restaurado (3)
  |      * -|------> * Background gravado na memória (1)
  |      ^  |
  |      +--|------- # Ícone colocado (2)
  +---------+

A vantagem desse método é que muito pouca memória é necessária. As desvantagens são que escrever na VGA é mais lento que escrever na memória, e há muito flicker.

MÉTODO 3 :

Passo 1 : Sete uma tela virtual, VADDR.
Passo 2 : Desenhe o fundo em VADDR.
Passo 3 : “Flip” VADDR para VGA.
Passo 4 : Desenhe o ícone na VGA.
Passo 5 : Transfira a porção do background de VADDR para a VGA.
Passo 6 : Repetir a partir do passo 4 continuamente.

Em ASCII…

 +---------+           +---------+
 |         |           |         |
 |   VGA   |           |  VADDR  |
 |         |           | (bckgnd)|
 | Icon>* <|-----------|--+      |
 +---------+           +---------+

As vantagens são que escrever para a tela virtual é mais rápido que na VGA, e há menos flicker que no método 2. As desvantagens são que você usa uma tela virtual de 64000 bytes, e você tem flicker se usar um grande número de objetos.

No programa de amostra em anexo, eu uso uma mistura dos Métodos 3 e 1. É mais rápido que usar só o método 1, e não tem flicker, diferente do método 3. O que eu faço é usar VADDR2 para o fundo, antes de jogar para a VGA.

No programa de amostra, você verá que eu restauro o fundo inteiro para cada ícone, e então coloco os ícones de cada objeto individualmente, se dois objetos ficam um por cima do outro, um deles será parcialmente sobrescrito.

As seções seguintes são explicações de como as várias rotinas de assembly funcionam. Isso será provavelmente bastante chato para você se você já sabe assembly, mas deveria ajudar principiantes e amadores.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

A Putpixel em ASM

Para começar, vou explicar um pouco das variáveis de funções em ASM:

<< NOTE QUE ESSA É UMA VISÃO EXTREMAMENTE SIMPLISTA DA LINGUAGEM ASSEMBLY!
Há vários livros para aumentar seu conhecimento, e o guia de assembly do Norton podem ser de valor incalculável para as pessoas começando a codificar em assembly. Eu não te dei as figuras lindas que você deveria ter para te ajudar a entender melhor, eu meramente a descrevi como uma linguagem de programação com suas procedures especiais. >>

Existem 4 variáveis de registro: AX, BX, CX, DX. Elas são words (dois bytes) com uma escala de 0 a 65535. Você pode acessar os bytes altos e baixos, apenas trocando o X com um “H” para alto e “L” para baixo. Por exemplo, o AL vai de 0 a 255.

Você pode também ter dois ponteiros: ES:DI e DS:SI. A parte da esquerda é o segmento para que você está apontando (ex.: $a000), e o lado direito é o offset 9deslocamento), que é o quão longe para dentro do segmento você está apontando. O Turbo Pascal coloca uma variável acima de 16k na “base” do segmento, ou seja, DI ou SI será zero no início da variável.

Se você quiser apontar para o pixel 3000 na VGA (veja partes anteriores para ver o layout da tela VGA), ES deveria ser igual a $a000 e DI seria igual a 3000. Você pode facilmente fazer ES ou DS ser igual ao offset de uma tela virtual (by Renato: acho que ele queria dizer endereço da tela… segmento, ao invés de offset…)

Aqui estão algumas poucas funções que você vai precisar saber:

  mov   destino,fonte            Isso move o valor de fonte para 
                                 o destino. ex.:  mov ax,50
  add   destino,fonte            Isso soma fonte com destino,
                                 o resultado é guardado no destino
  mul   fonte             Isso multiplica AX por fonte. Se
                                 o fonte é um byte, a fonte é
                                 multiplicada por AL, o resultado 
                                 é guardado em in AX. Se fonte 
                                 é uma word, a fonte é 
                                 multiplicada por
                                 AX, e o resultado fica em DX:AX
  movsb                          Isso move o byte que DS:SI está
                                 apontando para ES:DI, e
                                 incrementa SI e DI.
  movsw                          O mesmo que movsb exceto que 
                                 ele move uma word ao invés 
                                 de um byte.
  stosw                          Isso move AX para ES:DI. stosb
                                 move AL para ES:DI. DI é então 
                                 incrementado.
  push registro                  Isso salva o valor do registro 
                                 colocando- na pilha. O
                                 registro pode ser então alterado,
                                 mas será restaurado ao seu valor
                                 original quando for "POPado"
  pop  registro                  Isso restaura o valor de um
                                 registrador "PUSHado". 
                                 NOTA: valores Pushados
                                 devem ser popados na MESMA 
                                 ORDEM, mas AO CONTRÁRIO.
  rep  comando                   Isso repete o comando quantas
                                 vezes for o valor em CX

SHL Destino, contagem;
e
SHR Destino, contagem;
precisam de um pouco mais de explicação. Como você sabe, computadores pensam em um’s e zero’s. Cada número pode ser representado nessa operação de base 2. Um byte consiste de 8 um’s e zero’s (bits), e têm valores de 0 a 255. Uma word consiste de 16 um’s e zero’s (bits), e têm valores de 0 a 65535. Uma double word consiste de 32 bits.

O número 53 pode ser representado assim: 00110101. Pergunte a alguém que parece esperto para explicar a você como converter de binário para decimal e vice-versa.

O que acontece se você deslocar tudo para a esquerda? Tirar o número mais à esquerda e colocar um zero à direita? Isso é o que acontece:

            00110101     =  53
             <-----
            01101010     =  106

Como você pode ver, o número dobrou! Da mesma forma, deslocando o uma vez para a direita, você divide o valor ao meio! Esse é um modo MUITO rápido de multiplicar ou dividir por 2. (note que para dividir deslocando temos o valor truncado… ou seja 15 shr 1 = 7)

Em assembly o formato é “SHL destino,contagem” Isso desloca (shift) o destino tantos bits quanto for o valor de contagem (1=2, 2=4, 3=8, 4=16 etc) Note que um shift leva apenas 2 clocks, enquanto um MUL pode demorar até 133 clocks. Uma diferença grande, né? Só os 286 ou superiores podem ter “contagem” maior que um.

É por isso que fazer o seguinte para calcular as coordenadas, para uma putpixel, é muito lento:

       mov    ax,[Y]
       mov    bx,320
       mul    bx
       add    ax,[X]
       mov    di,ax

Mas, que droga! Ouço vocês chorando. 320 não é um número que você pode shiftar, já que você só pode shiftar por 2, 4, 8, 16, 32, 64, 128, 256, 512 etc etc… A solução é bem esperta. Observe.

mov     bx, [X]      // seta BX para X
mov     dx, [Y]      // seta DX para Y
push    bx           // salva BX (nosso valor X)
mov     bx, dx       // agora BX e DX são iguais a Y
mov     dh, dl       // copia DL para DH (multiplica Y por 256)
xor     dl, dl       // zera DL
shl     bx, 6        // shifta BX à esquerda 6 casas (multiplica Y por 64).
add     dx, bx       // soma BX a DX (Y*64 + Y*256 = Y*320)
pop     bx           // restaura BX (coordenada X)
add     bx, dx       // soma BX a DX (Y*320 + X).  isso te dá 
                     //   o offset na memória que você quer
mov     di, bx       // mover para DI

Vamos dar uma olhada nisso mais de perto?
bx=dx=y dx=dx256 ; bx=bx64 ( Note, 256+64 = 320 )

dx+bx=O valor y correto, apenas some X!

Como você pode ver, em assembly o código mais curto frequentemente não é o mais rápido.

A procedure putpixel completa é a seguinte:

[Pascal]

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
   { This puts a pixel on the screen by writing directly to memory. }
 BEGIN
   Asm
     push    ds                      {; Make sure these two go out the }
     push    es                      {; same they went in }
     mov     ax,[where]
     mov     es,ax                   {; Point to segment of screen }
     mov     bx,[X]
     mov     dx,[Y]
     push    bx                      {; and this again for later}
     mov     bx, dx                  {; bx = dx}
     mov     dh, dl                  {; dx = dx * 256}
     xor     dl, dl
     shl     bx, 1
     shl     bx, 1
     shl     bx, 1
     shl     bx, 1
     shl     bx, 1
     shl     bx, 1                   {; bx = bx * 64}
     add     dx, bx                  {; dx = dx + bx (ie y*320)}
     pop     bx                      {; get back our x}
     add     bx, dx                  {; finalise location}
     mov     di, bx                  {; di = offset }
     {; es:di = where to go}
     xor     al,al
     mov     ah, [Col]
     mov     es:[di],ah              {; move the value in ah to screen
                                        point es:[di] }
     pop     es
     pop     ds
   End;
 END;



 [C++]
/////////////////////////////////////////////////
// Putpixel() - This puts a pixel on the 
//screen by writing directly to     
//              memory.     
/////////////////////////////////////////////////                                             

 void Putpixel (word X, word Y, byte Col, word Where) {
   asm {
     push    ds           // save DS
     push    es           // save ES
     mov     ax, [Where]  // move segment of Where to AX
     mov     es, ax       // set ES to segment of Where
     mov     bx, [X]      // set BX to X
     mov     dx, [Y]      // set DX to Y
     push    bx           // save BX (our X value)
     mov     bx, dx       // now BX and DX are equal to Y
     mov     dh, dl       // copy DL to DH (multiply Y by 256)
     xor     dl, dl       // zero out DL
     shl     bx, 6        // shift BX left 6 places (multiply Y by 64).
     add     dx, bx       // add BX to DX (Y64 + Y256 = Y320)     pop     bx           // restore BX (X coordinate)     add     bx, dx       // add BX to DX (Y320 + X).  this gives you
                          //   the offset in memory you want
     mov     di, bx       // move the offset to DI
     xor     al, al       // zero out AL
     mov     ah, [Col]    // move value of Col into AH
     mov     es:[di], ah  // move Col to the offset in memory (DI)
     pop     es           // restore ES
     pop     ds           // restore DS
   }
 }

Note isso com DI e SI, quando você os usa:
mov di,50 Move di para a posição 50
mov [di],50 Move 50 para a posição que di está apontando

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

A procedure de Flip

Isso é bastante direto. Fazemos ES:DI apontar para o início da tela de destino, e DS:DI para o início da tela fonte, então fazemos 32000 movsw (64000 bytes).

[Pascal]
 procedure flip(source,dest:Word);
   { Isso copia a tela inteira no "fonte" para o destino }
 begin
   asm
     push    ds
     mov     ax, [Dest]
     mov     es, ax                  { ES = Segmento do destino }
     mov     ax, [Source]
     mov     ds, ax                  { DS = Segmento da fonte }
     xor     si, si                  { SI = 0   Mais rápido que mov si,0 }
     xor     di, di                  { DI = 0 }
     mov     cx, 32000
     rep     movsw                   { Repete movsw 32000 vezes }
     pop     ds
   end;
 end; 


[C++] 
 /////////////////////////////////////////////////
// Flip() - Isso copia a tela inteira 
// no "fonte" para o destino.
///////////////////////////////////////////////// 
void Flip(word source, word dest) {
   asm {
     push    ds           // salva DS
     mov     ax, [dest]   // copia o segmento do destino para AX
     mov     es, ax       // seta ES para apontar para o destino
     mov     ax, [source] // copia o segmento da fonte para AX
     mov     ds, ax       // seta DS para apontar par a fonte
     xor     si, si       // zera SI
     xor     di, di       // zera DI
     mov     cx, 32000    // seta nosso contador para 32000
     rep     movsw        // move fonta para o destino em words.  decrementa
                          //   CX de 1 a cada vez até CX = 0
     pop     ds           // restaura DS
   }
 } 

A procedure CLS funciona do mesmo modo, apenas ele move a cor para AX e depois usa um “rep stosw” (veja o programa para maiores detalhes).

O comando PAL é quase o mesmo que seu equivalente em Pascal (veja os tutoriais anteriores). Olhe o código do exemplo para ver como ele usa os comandos IN e OUT.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Fechando

Os procedimentos em Assembly apresentados a você aqui não estão no seu melhor. A Maioria delas ASPHYXIA abandonou por outras melhores depois de meses de uso.Mas, como você verá em breve, são MUITO mais rápidas que as equivalentes em Pascal que lhe dei no início. No futuro, espero lhe dar mais e mais procedimentos em assembly para suas coleções cada vez maiores. Mas, como você sabe, não sou muito rápido com essa série (eu nem mesmo sei se alguma dessas partes foi lançada em semanas consecutivas), então, se você quiser alguma coisa feita, tente fazer você mesmo. O que você tem a perder, além do seu temperamento e uns poucos reboots criativos? 😉

O que eu deveria fazer no próximo treinamento? Um tutorial simples de 3-d? Você pode não gostar disso, porque eu vou entrar em detalhes pequenos de como funciona 🙂 Deixe algumas sugestões para treinamentos futuros de alguma das maneiras discutidas no topo desse treinamento.

Depois da quote costumeira, vou colocar uma lista de BBS’s que sei que têm essa série, atualmente. Se sua BBS a recebe regularmente, não importa onde você esteja, mande uma mensagem para mim e vou adicioná-la à lista. Vamos fazer isso mais conveniente para as pessoas não precisarem de fazer uma ligação a longa distância 😉

        [     Ali estavam sentados, a turma pré-escolar, cercando 
        sua mentora, a professora substituta.
    "Agora, classe, hoje vamos conversar sobre o que você quer
        ser quando crescer. Não é divertido?" A professora
        olha ao redor e pára na criança, silenciosa, 
        separada dos outros e pensativa. "Jonny, por que você
        não começa?" ela o encoraja.
    Jonny olha para os lados, confuso, seu pensamento interrompido.
        Ele se recompõe, e olha para a professora com um olhar
        fixo. "Eu quero programar demos", ele diz, suas palavras
        ficando cada vez mais forte e mais confiantes ao 
        passo que ele fala. "Eu quero escrever algo que 
        vai mudar a percepção da realidade das pessoas. Eu quero
        que elas se afastem do computador deslumbradas, 
        inseguras de seu andar e sua visão. Eu
        quero escrever algo que vai sair da tela
        e pegá-las, fazendo as batidas do coração e
        a respiração ficarem devagar, quase parando. Quero
        escrever algo que quando acabar, elas fiquem relutantes
        de sair, sabendo que nada que experimentarem 
        aquele dia será tão real, tão compreensível, tão bom.   
        Eu quero escrever demos."
    Silêncio. A classe e a professora olham para Jonny, perpelxos.
        É a vez da professora ficar confusa. Jonny fica vermelho,
        sentindo que falta alguma coisa. "Ou isso ou    
        vou querer ser um bombeiro."
                                                                 ]
                                                   - Grant Smith
                                                        14:32
                                                           21/11/93

Vejo vocês na próxima vez,

  • DENTHOR
  • KRULL

Essas BBS’s legais têm a SÉRIE DE TREINAMENTO DE DEMOS ASPHYXIA: (alfabeticamente)

ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍËÍÍÍËÍÍÍÍËÍÍÍÍ»
ºBBS Name                  ºTelephone No.   ºOpen ºMsgºFileºPastº
ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÎÍÍÍÎÍÍÍÍÎÍÍÍ͹
ºASPHYXIA BBS #1           º(031) 765-5312  ºALL  º * º *  º *  º
ºASPHYXIA BBS #2           º(031) 765-6293  ºALL  º * º *  º *  º
ºConnectix BBS             º(031) 266-9992  ºALL  º * º *  º *  º
ºFor Your Eyes Only BBS    º(031) 285-318   ºA/H  º * º *  º *  º
ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÊÍÍÍÊÍÍÍÍÊÍÍÍͼ

Open = Open at all times or only A/H
Msg = Available in message base
File = Available in file base
Past = Previous Parts available

A Nova Krull's HomePage