Peças
Certo vamos finalmente falar sobre a parte principal do mod: a instalação de novas peças no modelo do carro.
Como na BMS Sound, inicialmente o Tuning Mod teria a instalação de peças como objetos do mapa grudados no carro, no entanto na GTA Forums DK22Pac me disse que eu na verdade devia "criar uma cópia do modelo como um atomic e adicionar no clump do veículo".
Eu criei um tópico perguntando como fazendo a mínima ideia do que diabos é isto, e o Wesser me deu um código que salvou a minha vida. Foi o kickstart para o Tuning Mod ficar bom.
Código: Selecionar tudo
{$CLEO}
0000: NOP
const
CARCOMPS_EXTRA1 = 2
CARCOMPS_EXTRA2 = 4
end
const
PAD1 = 0
end
const
LEFTSHOULDER1 = 4
end
const
MODELTYPE_BOAT = 5
MODELTYPE_BIKE = 9
MODELTYPE_BMX = 10
end
const
_HOOK_004C96F5_CModelCars__cloneData = 29@
_HOOK_004C9763_CModelCars__cloneData = 28@
_getModelPtr = 0x00403DA0
_rwFrameClone = 27@
_rwFrameAddChild = 26@
_rwFrameDestroy = 25@
_rwFrameFindNodeByName = 0x004C5400
_rwFrameGetNodeName = 0x0072FB30
_rpClumpAddAtomic = 24@
_rpClumpRemoveAtomic = 23@
_rpClumpFindFrameById = 0x004C53C0
_rpAtomicClone = 22@
_rpAtomicSetFrame = 21@
_rpAtomicDestroy = 20@
_rpAtomicSetCarMatFX = 0x004C9410
end
0A9F: 31@ = current_thread_pointer
0A8E: 30@ = 31@ + 0x10 // CScriptThread.m_iBaseAddress
0A8D: 30@ = read_memory 30@ size 4 virtual_protect 0
0A8F: _HOOK_004C96F5_CModelCars__cloneData = 30@ - @HOOK_004C96F5_CModelCars__cloneData
0A8F: _HOOK_004C9763_CModelCars__cloneData = 30@ - @HOOK_004C9763_CModelCars__cloneData
0A8E: 0@ = 0x00537338 + 0x01
0A8E: 1@ = 0@ + 0x04
0A8D: 2@ = read_memory 0@ size 4 virtual_protect 1
0A8E: _rwFrameClone = 1@ + 2@
0A8E: 0@ = 0x004C9269 + 0x01
0A8E: 1@ = 0@ + 0x04
0A8D: 2@ = read_memory 0@ size 4 virtual_protect 1
0A8E: _rwFrameAddChild = 1@ + 2@
0A8E: 0@ = 0x004C4473 + 0x01
0A8E: 1@ = 0@ + 0x04
0A8D: 2@ = read_memory 0@ size 4 virtual_protect 1
0A8E: _rwFrameDestroy = 1@ + 2@
0A8E: 0@ = 0x004C6CC1 + 0x01
0A8E: 1@ = 0@ + 0x04
0A8D: 2@ = read_memory 0@ size 4 virtual_protect 1
0A8E: _rpClumpAddAtomic = 1@ + 2@
0A8E: 0@ = 0x004C6CBA + 0x01
0A8E: 1@ = 0@ + 0x04
0A8D: 2@ = read_memory 0@ size 4 virtual_protect 1
0A8E: _rpClumpRemoveAtomic = 1@ + 2@
0A8E: 0@ = 0x004C44EE + 0x01
0A8E: 1@ = 0@ + 0x04
0A8D: 2@ = read_memory 0@ size 4 virtual_protect 1
0A8E: _rpAtomicClone = 1@ + 2@
0A8E: 0@ = 0x004C450A + 0x01
0A8E: 1@ = 0@ + 0x04
0A8D: 2@ = read_memory 0@ size 4 virtual_protect 1
0A8E: _rpAtomicSetFrame = 1@ + 2@
0A8E: 0@ = 0x004C446D + 0x01
0A8E: 1@ = 0@ + 0x04
0A8D: 2@ = read_memory 0@ size 4 virtual_protect 1
0A8E: _rpAtomicDestroy = 1@ + 2@
// Make CModelCars__cloneData cloning the primary extra frame to copy also the node name.
0A8E: 0@ = 0x004C96F5 + 0x05
0A8F: 1@ = _HOOK_004C96F5_CModelCars__cloneData - 0@
0A8C: write_memory 0x004C96F5 size 1 value 0xE9 virtual_protect 1
0A8C: write_memory 0x004C96F6 size 4 value 1@ virtual_protect 1
0A8E: 0@ = _HOOK_004C96F5_CModelCars__cloneData + 0x08
0A8E: 1@ = 0@ + 0x04
0A8F: 2@ = _rwFrameClone - 1@
0A8C: write_memory 0@ size 4 value 2@ virtual_protect 1
0A8E: 0@ = _HOOK_004C96F5_CModelCars__cloneData + 0x0F
0A8E: 1@ = 0@ + 0x04
0A8F: 2@ = 0x004C9715 - 1@
0A8C: write_memory 0@ size 4 value 2@ virtual_protect 1
0A8C: write_memory 0x004C971F size 1 value 0x24 virtual_protect 1
0A8C: write_memory 0x004C972A size 1 value 0x28 virtual_protect 1
0A8C: write_memory 0x004C9735 size 1 value 0x34 virtual_protect 1
0A8C: write_memory 0x004C9738 size 1 value 0x20 virtual_protect 1
// Do the same thing for the secondary extra frame.
0A8E: 0@ = 0x004C9763 + 0x05
0A8F: 1@ = _HOOK_004C9763_CModelCars__cloneData - 0@
0A8C: write_memory 0x004C9763 size 1 value 0xE9 virtual_protect 1
0A8C: write_memory 0x004C9764 size 4 value 1@ virtual_protect 1
0A8E: 0@ = _HOOK_004C9763_CModelCars__cloneData + 0x08
0A8E: 1@ = 0@ + 0x04
0A8F: 2@ = _rwFrameClone - 1@
0A8C: write_memory 0@ size 4 value 2@ virtual_protect 1
0A8E: 0@ = _HOOK_004C9763_CModelCars__cloneData + 0x0F
0A8E: 1@ = 0@ + 0x04
0A8F: 2@ = 0x004C9783 - 1@
0A8C: write_memory 0@ size 4 value 2@ virtual_protect 1
0A8C: write_memory 0x004C978D size 1 value 0x24 virtual_protect 1
0A8C: write_memory 0x004C9798 size 1 value 0x28 virtual_protect 1
0A8C: write_memory 0x004C97A3 size 1 value 0x34 virtual_protect 1
0A8C: write_memory 0x004C97A6 size 1 value 0x20 virtual_protect 1
while true
if
00DD: actor $PLAYER_ACTOR driving_car_with_model #BF400
then
03C0: 0@ = actor $PLAYER_ACTOR car
0441: 1@ = car 0@ model
if
00E1: player PAD1 pressed_key LEFTSHOULDER1 // Secondary Fire
then
0AA7: call_function _getModelPtr num_params 1 pop 1 iId 1@ pclInfo 2@
2@ += 0x5C // CModelCars.m_paclVehicleStruct
0A8D: 2@ = read_memory 2@ size 4 virtual_protect 0
0A8E: 3@ = 2@ + 0x030C // CVehicleStruct.m_iNumExtras
0A8D: 3@ = read_memory 3@ size 1 virtual_protect 0
if
3@ > 0
then
0A8E: 4@ = 2@ + 0x3C // CModelCars.m_eType
0A8D: 4@ = read_memory 4@ size 4 virtual_protect 0
0A97: 5@ = car 0@ struct
0A8E: 6@ = 5@ + 0x0018 // CVehicle.m_clEntity.m_pstRpClump
0A8D: 6@ = read_memory 6@ size 4 virtual_protect 0
if and
4@ >= MODELTYPE_BOAT
4@ <> MODELTYPE_BIKE
4@ <> MODELTYPE_BMX
then
0A8E: 7@ = 6@ + 0x04 // SRpClump.m_stObject.m_pstParent
0A8D: 7@ = read_memory 7@ size 4 virtual_protect 0
else
0AA7: call_function _rpClumpFindFrameById num_params 2 pop 2 iHierarchyId 1 pstRpClump 6@ pstRwFrame 7@
if
7@ == 0x00000000
then
0A8E: 7@ = 6@ + 0x04 // SRpClump.m_stObject.m_pstParent
0A8D: 7@ = read_memory 7@ size 4 virtual_protect 0
end
end
0A8E: 8@ = 2@ + 0x02F4 // CVehicleStruct.m_apstExtraAtomics (array_sizeof = 6)
0A8E: 9@ = 5@ + 0x0438 // CVehicle.m_cPrimaryExtraId
0A8D: 10@ = read_memory 9@ size 1 virtual_protect 0
if
88B7: not test 10@ bit 7
then
0A90: 11@ = 10@ * 0x04 // sizeof(SRwFrame *)
005A: 11@ += 8@
0A8D: 11@ = read_memory 11@ size 4 virtual_protect 0
0A8E: 12@ = 11@ + 0x04 // SRpAtomic.m_stObject.m_stObject.m_pstParent
0A8D: 12@ = read_memory 12@ size 4 virtual_protect 0
if
11@ <> 0x00000000
then
0AA7: call_function _rwFrameGetNodeName num_params 1 pop 1 pstRwFrame 12@ pszNodeName 13@
0AA7: call_function _rwFrameFindNodeByName num_params 2 pop 2 pszNodeName 13@ pstRwFrame 7@ pstRwFrame 14@
if
14@ <> 0x00000000
then
0A8E: 15@ = 14@ + 0x90 // SRwFrame.m_stAtomicList.m_pstNext
0A8D: 15@ = read_memory 15@ size 4 virtual_protect 0
15@ -= 0x08 // SRpAtomic
0AA5: call _rpClumpRemoveAtomic num_params 2 pop 2 pstRpAtomic 15@ pstRpClump 6@
0AA5: call _rpAtomicDestroy num_params 1 pop 1 pstRpAtomic 15@
0AA5: call _rwFrameDestroy num_params 1 pop 1 pstRwFrame 14@
end
end
end
0A90: 11@ = CARCOMPS_EXTRA1 * 0x04 // sizeof(SRwFrame *)
005A: 11@ += 8@
0A8D: 11@ = read_memory 11@ size 4 virtual_protect 0
if
11@ <> 0x00000000
then
0A8E: 12@ = 11@ + 0x04 // SRpAtomic.m_stObject.m_stObject.m_pstParent
0A8D: 12@ = read_memory 12@ size 4 virtual_protect 0
0AA7: call_function _rpAtomicClone num_params 1 pop 1 pstRpAtomic 11@ pstRpAtomic 13@
0AA7: call_function _rwFrameClone num_params 1 pop 1 pstRwFrame 12@ pstRwFrame 15@
0AA5: call _rpAtomicSetFrame num_params 2 pop 2 pstRwFrame 15@ pstRpAtomic 13@
0AA5: call _rpClumpAddAtomic num_params 2 pop 2 pstRpAtomic 13@ pstRpClump 6@
0AA5: call _rwFrameAddChild num_params 2 pop 2 pstRwChild 15@ pstRwFrame 7@
0AA5: call _rpAtomicSetCarMatFX num_params 2 pop 2 iMaterialId 0 pstRpAtomic 13@
0A8C: write_memory 9@ size 1 value CARCOMPS_EXTRA1 virtual_protect 0
end
0A8E: 9@ = 5@ + 0x0439 // CVehicle.m_cSecondaryExtraId
0A8D: 10@ = read_memory 9@ size 1 virtual_protect 0
if
88B7: not test 10@ bit 7
then
0A90: 11@ = 10@ * 0x04 // sizeof(SRwFrame *)
005A: 11@ += 8@
0A8D: 11@ = read_memory 11@ size 4 virtual_protect 0
if
11@ <> 0x00000000
then
0A8E: 12@ = 11@ + 0x04 // SRpAtomic.m_stObject.m_stObject.m_pstParent
0A8D: 12@ = read_memory 12@ size 4 virtual_protect 0
0AA7: call_function _rwFrameGetNodeName num_params 1 pop 1 pstRwFrame 12@ pszNodeName 13@
0AA7: call_function _rwFrameFindNodeByName num_params 2 pop 2 pszNodeName 13@ pstRwFrame 7@ pstRwFrame 14@
if
14@ <> 0x00000000
then
0A8E: 15@ = 14@ + 0x90 // SRwFrame.m_stAtomicList.m_pstNext
0A8D: 15@ = read_memory 15@ size 4 virtual_protect 0
15@ -= 0x08 // SRpAtomic
0AA5: call _rpClumpRemoveAtomic num_params 2 pop 2 pstRpAtomic 15@ pstRpClump 6@
0AA5: call _rpAtomicDestroy num_params 1 pop 1 pstRpAtomic 15@
0AA5: call _rwFrameDestroy num_params 1 pop 1 pstRwFrame 14@
end
end
end
0A90: 11@ = CARCOMPS_EXTRA2 * 0x04 // sizeof(SRwFrame *)
005A: 11@ += 8@
0A8D: 11@ = read_memory 11@ size 4 virtual_protect 0
if
11@ <> 0x00000000
then
0A8E: 12@ = 11@ + 0x04 // SRpAtomic.m_stObject.m_stObject.m_pstParent
0A8D: 12@ = read_memory 12@ size 4 virtual_protect 0
0AA7: call_function _rpAtomicClone num_params 1 pop 1 pstRpAtomic 11@ pstRpAtomic 13@
0AA7: call_function _rwFrameClone num_params 1 pop 1 pstRwFrame 12@ pstRwFrame 15@
0AA5: call _rpAtomicSetFrame num_params 2 pop 2 pstRwFrame 15@ pstRpAtomic 13@
0AA5: call _rpClumpAddAtomic num_params 2 pop 2 pstRpAtomic 13@ pstRpClump 6@
0AA5: call _rwFrameAddChild num_params 2 pop 2 pstRwChild 15@ pstRwFrame 7@
0AA5: call _rpAtomicSetCarMatFX num_params 2 pop 2 iMaterialId 0 pstRpAtomic 13@
0A8C: write_memory 9@ size 1 value CARCOMPS_EXTRA2 virtual_protect 0
end
end
while 00E1: player PAD1 pressed_key LEFTSHOULDER1 // Secondary Fire
wait 0
end
end
end
wait 0
end
:HOOK_004C96F5_CModelCars__cloneData
hex
8B0C24 // mov ecx, dword ptr [esp]
8B51 04 // mov edx, [ecx+SRpAtomic.m_stObject.m_stObject.m_pstParent]
52 // push edx
E8 00000000 // call rwFrameClone
8BF8 // mov edi, eax
E9 00000000 // jmp 4C9715h
end
:HOOK_004C9763_CModelCars__cloneData
hex
8B0C24 // mov ecx, dword ptr [esp]
8B51 04 // mov edx, [ecx+SRpAtomic.m_stObject.m_stObject.m_pstParent]
52 // push edx
E8 00000000 // call rwFrameClone
8BF8 // mov edi, eax
E9 00000000 // jmp 4C9783h
end
Com o tempo estudando o código dele e aprendendo sobre manipulação de memória, especialmente o uso de classes por CLEO, eu estive cada vez mais entendendo o que aquele código fazia, hoje o entendo completamente.
A parte mais importante do código é esta aqui:
Código: Selecionar tudo
0AA7: call_function _rpAtomicClone num_params 1 pop 1 pstRpAtomic 11@ pstRpAtomic 13@
0AA7: call_function _rwFrameClone num_params 1 pop 1 pstRwFrame 12@ pstRwFrame 15@
0AA5: call _rpAtomicSetFrame num_params 2 pop 2 pstRwFrame 15@ pstRpAtomic 13@
0AA5: call _rpClumpAddAtomic num_params 2 pop 2 pstRpAtomic 13@ pstRpClump 6@
0AA5: call _rwFrameAddChild num_params 2 pop 2 pstRwChild 15@ pstRwFrame 7@
Ele simplesmente faz um clone de um RpAtomic e RwFrame, aplica o RpAtomic clonado ao novo RwFrame, adiciona o RpAtomic no RpClump e adiciona o RwFrame como child de outro RwFrame.
Ham?
Esses nomes estranhos são da engine RenderWare. Mas não são tão estranhos assim.
RwFrame é um "frame", no sentido de espaço. Não há uma tradução para português, mas imagine uma caixa com coisas dentro.
Ele é o encarregado de armazenar o conteúdo desta caixa, a sua caixa "filha", a próxima caixa, assim como a matrix que indica a posição do global, local, tamanho, rotação etc.
Código: Selecionar tudo
00000000 RwFrame struc ; (sizeof=0xA4)
00000000 object RwObject ?
00000008 inDirtyListLink RwLLLink ?
00000010 modelling RwMatrix ?
00000050 ltm RwMatrix ?
00000090 objectList RwLinkList ?
00000098 child dd ? ; RwFrame *
0000009C next dd ? ; RwFrame *
000000A0 root dd ? ; RwFrame *
000000A4 RwFrame ends
RpAtomic é "atômico". Eu gosto de imaginar "composição dos átomos", ou seja, é onde o modelo está, com a sua cor, suas propriedades etc. É o objeto visível, e cada RwFrame pode ter mais de um RpAtomic (mas sinceramente é chato trabalhar assim, tanto que VehFuncs e ImVehFt remove isto do jogo para ter melhor controle das peças).
Ele é o encarregado de guardar o lado visível do objeto, como por exemplo um RpGeometry, que como o nome diz, guarda a geometria do RpAtomic, o que inclui o RpMaterialList, que tem vários RpMaterial, que tem RwTexture etc. Até mesmo a mesh do objeto está ali no RpGeometry.
Um RpAtomic também armazena o seu RpClump, o pipeline (se você já abriu o .ini do SkyGfx sabe o que é!), assim como o pai, que a partir do pai nós temos um RwFrame.
Ou seja, a partir de um RpAtomic você tem o controle de todo o modelo.
Código: Selecionar tudo
00000000 RpAtomic struc ; (sizeof=0x70)
00000000 object RwObjectHasFrame ?
00000014 repEntry dd ? ; > RwResEntry *
00000018 geometry dd ? ; > RpGeometry *
0000001C boundingSphere RwSphere ?
0000002C worldBoundingSphere RwSphere ?
0000003C clump dd ? ; RpClump *
00000040 inClumpLink RwLLLink ?
00000048 renderCallback dd ?
0000004C interpolator RpInterpolator ?
00000060 renderFrame dw ?
00000062 pad dw ?
00000064 llWorldSectorsInAtomic RwLinkList ?
0000006C pipeline dd ? ; > RxPipeline *
00000070 RpAtomic ends
RpClump é um "arvoredo", como o nome sugere, ele guarda listas de atômicos, luzes, câmeras etc.
Código: Selecionar tudo
00000000 RpClump struc ; (sizeof=0x2C)
00000000 object RwObject ?
00000008 atomicList RwLinkList ?
00000010 lightList RwLinkList ?
00000018 cameraList RwLinkList ?
00000020 inWorldLink RwLLLink ?
00000028 callback dd ?
0000002C RpClump ends
Cada veículo tem um RpClump que pode ser acessado em
CVehicle+0x18
.
A partir de um RpClump, além de poder acessar todos os objetos contidos, você também pode utilizar funções como:
RwFrame *__cdecl CClumpModelInfo::GetFrameFromName(RpClump *clump, char *name)
(0x004C5400)
Na qual você envia um nome de um node, por exemplo
chassis
, e caso encontrado retornará o RwFrame do
chassis
. É uma função um tanto pesada no entanto.
Agora sim podemos explicar como o
Extra - Add.txt
funciona, que é o arquivo núcleo do Tuning Mod.
Primeiramente precisamos pegar o RwFrame de destino para adicionar a peça. Por exemplo no
chassis
ou alguma porta, depende de onde foi selecionado.
Esta parte está muito bagunçada, mas foi necessário para cuidar de todas as possibilidades: há vários veículos sem
chassis
, então eu tenho que pegar o primeiro node, mas há barcos onde o primeiro node é por exemplo o motor... Tem que cuidar disso tudo.
A partir do ID de um modelo nós conseguimos o
CBaseModelInfo
, que são as informações básicas do modelo. (0x004C5940)
Código: Selecionar tudo
CVehicleModelInfo *__cdecl CModelInfo::GetModelInfo(char *modelName, __int16 *pIndex)
Código: Selecionar tudo
00000000 CBaseModelInfo struc ; (sizeof=0x20)
00000000 vmt dd ? ; offset
00000004 m_dwKey dd ?
00000008 m_wUsageCount dw ?
0000000A m_wTxdIndex dw ? ; index of entry in the TexDictionaryPool
0000000C m_nAlpha db ?
0000000D m_n2dfxCount db ?
0000000E m_w2dfxIndex dw ?
00000010 m_wObjectInfoIndex dw ?
00000012 m_wFlags dw ?
00000014 m_pColModel dd ? ; offset
00000018 m_fDrawDistance dd ? ; float
0000001C m_pRwObject dd ? ; union { RpAtomic * atomic; RpClump * clump; }
00000020 CBaseModelInfo ends
Perceba que em
CBaseModelInfo+0x1C
temos o RpClump do modelo, a partir de lá nós conseguimos o controle de todos os RpAtomic e RwFrame do modelo.
Após pegar eu faço checagens para determinar que aquela peça foi instalada pelo Tuning Mod. Eu descubro isto checando se o modelo não tem colisão e se o draw distance é
100.0
.
Sendo uma peça do Tuning Mod, será aplicado correções para pintar o material e aplicar pipeline de veículo, assim possibilitando a renderização de specular highlighting, uso de texturas do vehicle.txd etc.
Para pegar o RpAtomic a partir do ID de um modelo é simples, já até mostrei acima, o problema vem quando estamos falando de uma peça de um carro, assim como uma peça extra, mas também foi relativamente simples, só que horrível de se ler:
Na nova versão do Tuning Mod (v2.2>) há suporte para multi-nodes, ou seja, em vez de somente 1 RwFrame e 1 RpAtomic, serão vários! E pior: temos que respeitar a ordem hierárquica dos RwFrame.
Isto quer dizer que temos que pegar todos os RwFrame e RpAtomic e tirar um clone de todos, e pior, respeitando a hierarquia!
Para isto é necessário usar
recursividade.
Deve estar certo, eu fiz ontem (foi um desafio louco) e não testei totalmente.
Acho muito interessante esse código, pois é bem mindblow, especialmente porque o RwFrame de destino se altera a cada child. Agradecimentos ao Fabio pela ideia de recursão com NextChild/NextFrame.
O código que faz o clone do modelo e coloca na peça do carro está aqui:
Semelhante ao código original do Wesser.
Perceba que o RwFrame também é posicionado conforme necessário (a posição foi anteriormente calculada, por exemplo se for no
chassis
a posição inicial da peça será por base da dimensão do modelo do carro, para que ela não apareça enfiada na carroceria). E deixei de usar
RwFrameClone
pois ele também clona as childs, algo que não desejo, além de causar problemas em clonar o
chassis
Gerenciar
Sabendo como isto funciona será muito mais fácil entendermos o resto do mod, por exemplo a interface "Gerenciar".
Para criar a lista de peças eu simplesmente faço uma recursão rodando todos os RwFrame e pegando o nome de cada um deles.
Como segurança eu também checo se a quantia de peças passou do limite de 9999. Isto pode acontecer caso causar um loop infinito, mas aparentemente isso nunca aconteceu.
Perceba também que os RwFrame sem nomes eu os corrigi colocando o nome
auto
seguido de um número. No momento o Tuning Mod é dependente do nome do frame, portanto todos os RwFrame precisam de nome único. É um problemão que eu ainda procurarei uma solução.
Mas algo interessante do funcionamento desta lista de peças é que há um tipo de scroll dependente do item selecionado.
Isto funciona com uma variável que define o início dos nomes para ser mostrados (ou seja, o nome que será mostrado no topo da lista, em vez de sempre começar do início, irá começar de um número variável)
E como o valor da variável é definida?
Simples assim! Um tanto mindblow, mas é só isto.
Se você precisar fazer uma lista grande com funcionalidade de scroll, está aí uma dica.
Escolher peça
Chegamos na PIOR parte de todo o mod! O arquivo
ChoosePart.txt
Esta é a parte mais complicada do mod, a que mais me deu dores de cabeça.
O problema disso tudo é que existe infinitas variações do que pode acontecer — o jogador pode estar selecionando peças por um .ini, ou por ctrl+v, ou por IDs manualmente, ou digitando o nome da peça. É por ID ou é por nome. Ou não é uma peça e sim um veículo, ou então é a peça de um veículo.
As possibilidades de situações é uma árvore com vários galhos e ramos, e temos que cuidar de todas elas. O meu objetivo principal é que o mod nunca cause crash em nenhuma situação, mesmo que o jogador tenha feito coisas erradas, instalado peça com problemas e tudo mais.
De fato antes de publicar o mod eu fiz um código que roda absolutamente todos os modelos do jogo nesta interface, instalando e desinstalando todas as peças, um
stress test que demorou um tempão, não deu nenhum crash e aparentemente nenhum vazamento de memória, comprovando que o mod consegue lidar com absolutamente todos os modelos do jogo.
Esta parte do mod não era tão difícil no início, mas quanto mais eu colocava coisas, pior ficava, principalmente ao adicionar a possibilidade de carregar peças de um carro, ou seja, você carregar um carro e em seguida escolher uma peça de um carro para colocar no seu, algo totalmente inesperado para o código do mod, eu nunca imaginaria que eu faria isso, portanto o código encheu de lixo e gambiarras para isto ser possível.
O mod também iniciou utilizando arquivos .ini, isto é, você colocava cada peça em uma key do .ini, mas eu decidi melhorar isto, não precisar mais de keys, e para isto me foi necessário na verdade não tratar mais os .ini como arquivos .ini, e sim como um arquivo normal, lendo cada byte.
Se trabalhar com a leitura do arquivo já era ruim, imagine lidar com diferentes configurações (como uma configuração onde você diz o veículo e a peça dele, seja o ID ou nome do veículo, ou o nome ou número ordem da peça dele), e lidar com os erros das pessoas para evitar que o mod cause crash em alguma má configuração.
Lidar com a leitura do
CTRL+V
...
Lidar com a atualização da peça e caso a atualização tenha sido falha, retornar a peça anterior. Lembrando também que temos que determinar se está mesmo lidando com uma peça e não com um carro no momento.
Na lógica parece algo simples "é só criar uma peça ali, tirar ela, colocar outra", mas você vai entrando em dezenas de pequenos detalhes e possibilidades, e tudo vira uma dor de cabeça. Quando percebe o seu código já tem quase 2 mil linhas.
No início o código tava bonito, com o tempo foi cada vez estragando mais, mas pelo menos está em ótimo funcionamento, e é melhor não mexer com quem está quieto!
Mesmo que seja interessante, não o use como referência para uma boa lógica. De fato o código tem que ser reescrito com lógica repensada (de novo, eu já o reescrevi várias vezes).
Instalação da peça
Finalmente, uma das únicas partes do mod que eu tenho orgulho e encorajo você de dar uma olhada no código-fonte.
Não é perfeita, mas a lógica ficou muito boa.
O código da instalação da peça é centenário: O código é o mesmo desde a Loja BMS Sound em 2012!!!
Obviamente super diferente, mas ainda com as semelhanças e mesma lógica:
if then else
em cada tecla, começando da mais complexa para a mais simples, testando cada combinação a partir da outra tecla.
Perceba que em vez de usar a alteração da variável direto no código, eu preferi usar um scm_func, pois lá eu tenho melhor controle do valor a ser aumentado ou diminuído.
Note que a velocidade é baseada num cálculo médio de delta-time, assim a velocidade se torna independente de FPS, mas o cálculo de delta-time não pode ser calculado enquanto a peça está se movendo. O código acima em comparação com a BMS Sound é o código onde a média de delta-time é calculada, perceba que só se calcula enquanto não está pressionando nenhum botão e tenha uma boa quantia de frames calculados somente 10 frames já é o bastante).
Isto é, se você soltar as teclas com 30 FPS, o mod calculará a média de delta-time a cada 10 frames para definir a velocidade de movimento e assim a velocidade não ser alta ou devagar caso o seu FPS esteja alto ou baixo.
São coisas que quando você vai criar um mod nem imagina que terá que fazer, e quando se depara com o problema é obrigado a usar a criatividade.
Enfim, e sobre a interface, também mostrado acima, há funções que definem o texto de cada tecla.
Repetindo uma parte aqui:
Ou seja, ao segurar
X
, o texto onde fica a ferramenta da tecla
S
é alterado para o texto de ID 322, na qual no
Tuning Mod.fxt
é
_TM_322 [S] Reset. Tamanho
. E no
Z
é aplicado
-1
para mostrar nada.
É deste modo que a interface de instalação se altera a cada tecla pressionada.
Após todo o processamento dos comandos, ao renderizar a interface, os valores guardados são lidos e mostrados conforme.
Outra coisa interessante da instalação da peça é a deformação: é nada mais nada menos que a alteração da matrix do RwFrame.
As rotações funcionam por quaternion, ou seja, não são XYZ e sim XYZW. O modo de deformação é nada mais nada menos que a alteração de cada elemento do quaternion.
Salvar e carregar
Oh nãooooooo, isto não!!!
A parte mais chata do desenvolvimento do Tuning Mod: salvar e carregar os veículos! Sem dúvida alguma, é o que mais atrapalha em adicionar novas funcionalidades ao mod, tudo o que eu tenho que adicionar de novo também tenho que adicionar no salvamento e carregamento do mod.
Mas enfim, falando sobre o funcionamento, foi criado com base num código de salvar e carregar veículos por arquivos criado pela Thayná. Eu simplesmente usei o mesmo código, incluindo informações adicionais sobre peças etc.
Todos os arquivos têm que ser salvos e lidos exatamente do mesmo jeito, byte por byte. Isto é, foi criado de maneira linear, se pular ou faltar 1 byte, todo o resto do carregamento irá se estragar.
A solução para isto seria eu fazer um header no arquivo, assim o header diria onde cada informação está (onde está armazenado as cores do veículo, onde está a suspensão, o tamanho da roda, as peças adicionais, os nomes das peças, as cores das peças etc etc...). Mas até hoje não tive coragem de fazer isso, quem sabe logo farei.
O modo atual de funcionamento, como se pode ver, fica incompatível com carros salvos em versões antigas. Por exemplo se eu coloco uma informação adicional entre a cor e paintjob, arquivos .tmv da versão antiga ficarão incompatíveis. Para resolver eu utilizei uma gambiarra: guardar um número de versão do arquivo logo no primeiro byte (ou seja, para saber qual a versão do arquivo .tmv, basta você ler o primeiro byte do arquivo). Assim sabendo qual foi a versão do Tuning Mod que salvou o arquivo, eu posso lidar com isso pulando os bytes adicionais da nova versão.
Fazer isso polui muito o código, não faça, após 2 versões o seu código já ficará uma merda de se ler e lidar, prefira utilizar header!
Foi difícil eu fazer esse código ficar estável, mas quando consegui eu implementei salvamento automático, que foi muitíssimo bom, pois agora o mod podia crashar que o jogador não iria mais perder sua criação! O salvamento automático é chamado em todas as mudanças de tela, quase nem se percebe, mas há um "Autosaving..." no topo da tela.
Como eu disse, fazer o salvamento e carregamento das informações é realmente muito chato, e também difícil quando o assunto são as peças adicionais: cada peça adicionada é necessário ter as informações do que ela é, de onde ela veio etc para guardar no arquivo e refazer todo o processo de readicionar aquela peça, e pior, respeitando a posição dela! Pois a posição que ela se encontra é importante, dependente da ordem de uma peça com alpha por exemplo poderá esconder o carro atrás da peça (é assim que o
Holes funciona!) e devido as peças serem adicionadas em ordem inversa no veículo (pense um pouco, ao pegar as peças, salvar e carregar elas voltarão em uma ordem inversa), eu tive até que ler todas as peças, guardar na memória para re-adicioná-las em ordem invertida no veículo.
Agora a minha atual preocupação está sobre as peças multi-nodes, afinal, não será só uma peça, e sim uma peça com várias outras peças filhas! E precisará ter a funcionalidade de alterar o nome da peça filha no Gerenciar. Eu ainda estou pensando em como diabos eu vou resolver esses problemas.
Ainda tem o
GSX do Fabio que possibilita salvar os dados na garagem, eu poderia usar, mas é mais outro problema de salvar/carregar para eu enfrentar (o que um já era chato demais). E eu não quero deixar de usar arquivos para usar somente o GSX pois compartilhar os carros por arquivos é legal.
Mas santa porra, parem de me pedir para salvar o carro em .dff! Isso é totalmente sem noção, nem o ZModeler salva um .dff direito, imagine eu, in-game, por cleo. Para você ter noção, o jogo não armazena nem o nome do arquivo .dff, ou seja, nem o nome do .dff do carro é possível saber in-game e você vai querer que eu salve um arquivo .dff inteiro in-game? ("então como que eu digito o nome do .dff do carro e sabe?" é porque é utilizado
hash).
Ainda tem muita coisa o que explicar, mas acho que o principal já está aí. Podem perguntar nos comentários por mais informações sobre algo já dito ou não dito aqui.