API orientada a objetos
Todo elemento do MTAX também entende a forma orientada a objetos da API. getElementHealth(veiculo) e veiculo.health são a mesma chamada: a segunda custa uma consulta de tabela e acaba chamando exatamente a mesma nativa.
-- estas duas linhas fazem a mesma coisasetElementHealth(veiculo, 750)veiculo.health = 750A camada está sempre ativa. Não há nada para habilitar no mtaxmanifest.lua, e ela existe no servidor e no cliente.
Ela não adiciona funções: é outro caminho até as que a referência de funções já documenta. Quando quiser saber o que um membro faz, procure a nativa para a qual ele encaminha.
Três formatos
Seção intitulada “Três formatos”Métodos de instância
Seção intitulada “Métodos de instância”Um elemento carrega os métodos da própria classe mais tudo que herda das classes-pai:
local veiculo = createVehicle(411, 0, 0, 5)
veiculo:setColor(255, 0, 0) -- setVehicleColor(veiculo, 255, 0, 0)veiculo:getPosition() -- getElementPosition(veiculo) — herdado de Elementveiculo:fix() -- fixVehicle(veiculo)Em geral o self vira o primeiro argumento da nativa, mas existem outras cinco formas de wrapper, e a referência nomeia cada uma na coluna Notas:
| Notas diz | O que o wrapper faz |
|---|---|
| (nada) | native(self, ...) |
| ignora o self | native(...) — a nativa não recebe elemento, então o self é descartado |
| self é o argumento 2 | native(a, self, ...) — DxFont.getHeight |
| self é o argumento 3 | native(a, b, self, ...) — DxFont.getTextWidth |
passa true / false | native(self, true) ou native(self, false) — os seus argumentos são descartados, como em ped:giveJetPack() |
passa "all" | native(self, "all") — os seus argumentos são descartados, como em objeto:getProperties() |
A classe que um elemento usa é decidida pelo tipo em tempo de execução, não pela variável em que você o guardou. Um ped e um jogador aceitam :getHealth(); só o jogador aceita :getPing().
Propriedades
Seção intitulada “Propriedades”veiculo.health = 750veiculo.locked = true
print(veiculo.engineState)Ler uma propriedade chama o getter dela; atribuir chama o setter. Ler algo que não é propriedade devolve nil.
Métodos são consultados antes das propriedades. Um único membro da API inteira é declarado das duas formas: ler ped.canBeKnockedOffBike devolve o método em vez do booleano, então use ped:canBeKnockedOffBike() para ler. A atribuição não é afetada — os setters são consultados primeiro na escrita. As duas nativas dele são só do cliente, então no servidor nenhuma das formas existe.
Atribuir a algo que não é propriedade é recusado — nada é guardado, e um aviso com o nome da classe e da chave vai para o console de debug. O mesmo vale ao atribuir a uma propriedade somente leitura.
veiculo.owner = "Gabriel" -- aviso: Vehicle has no property 'owner'veiculo:setData("owner", "Gabriel") -- faça assimTabelas de classe
Seção intitulada “Tabelas de classe”Toda classe é uma tabela global. Os métodos dela também podem ser chamados de forma plana, e chamar a própria tabela cria o elemento:
Vehicle.create(411, 0, 0, 5)Vehicle(411, 0, 0, 5) -- atalho para Vehicle.createVehicle.getPosition(veiculo) -- igual a veiculo:getPosition()A forma plana funciona para os membros comuns. Ela não funciona para os que ignoram o self: o wrapper deles descarta o argumento 1 incondicionalmente, então Player.spawn(x, y, z) executa spawnPlayer(y, z). Chame esses com : ou passe um primeiro argumento descartável.
Classe(...) encaminha para o create da própria classe — um herdado não vale. Três classes nunca definem um: ColShape, que tem um construtor por forma, DxMaterial, que só existe como base, e Fracture, que nasce pelo fracture de um objeto. Muitas outras não têm create em um dos lados, porque ele só é ligado onde a nativa existe: no servidor isso vale também para Sound, Sound3D, Effect, Light, SearchLight, Projectile, Weapon, SVG, Dui, as quatro classes Engine e toda a família Dx; no cliente, para Connection. Chamar uma classe sem create não devolve nada.
File.create é o único construtor que faz mais do que encaminhar: File(caminho) abre o arquivo quando ele existe e o cria quando não existe.
ColShape.Sphere(0, 0, 3, 10)ColShape.Circle(0, 0, 10)Propriedades vetoriais
Seção intitulada “Propriedades vetoriais”Doze propriedades devolvem um Vector3 em vez de números soltos:
| Classe | Propriedades |
|---|---|
Element | position, rotation, velocity, angularVelocity |
Vehicle | turnVelocity, respawnPosition, respawnRotation |
Object | scale |
Marker | target |
Light | direction |
SearchLight | startPosition, endPosition |
Outras três devolvem formatos diferentes. ColShape.size é um Vector2 para um retângulo, um Vector3 para um cuboide e um número simples para um tubo; ColShape.points é uma tabela de Vector2; Element.boundingBox é o canto mínimo como Vector3 — a leitura de uma propriedade guarda um único valor, então chame getBoundingBox para ter os dois cantos.
local pos = veiculo.position -- Vector3print(pos.x, pos.y, pos.z)
local x, y, z = veiculo:getPosition() -- o método continua devolvendo três númerosA maioria dos métodos mantém os números soltos da nativa, mas alguns os remodelam, e a coluna Notas avisa:
Marker.getTarget,SearchLight.getStartPositionegetEndPositionsempre devolvem umVector3.ColShape.getSize,getPointPositionegetPointsdevolvem os mesmos formatos quesizeepoints.Element.getBoundingBoxdevolve doisVector3, a menos que a chamada receba exatamente seis resultados:local x1, y1, z1, x2, y2, z2 = veiculo:getBoundingBox()continua recebendo números.Light.getDirectione, no cliente,Object.getScaledevolvem umVector3, a menos que a chamada receba exatamente três resultados.
Os setters aceitam o que a nativa por trás aceitar, então veiculo.position = Vector3(0, 0, 5) funciona onde a nativa recebe um vetor. Toda propriedade vetorial acima que tem setter aceita um.
Os getters só voltam a devolver o que a nativa devolveu quando a global Vector3 (ou Vector2) está ausente. Se algum script a tiver substituído, é a substituta que será chamada — e uma substituta que não seja chamável faz a leitura estourar.
Herança
Seção intitulada “Herança”Toda classe, exceto File, descende de Element:
Element├── Ped│ └── Player├── Sound│ └── Sound3D├── DxMaterial│ ├── DxTexture│ │ ├── DxRenderTarget│ │ └── DxScreenSource│ └── DxShader└── Blip · Building · ColShape · Connection · Dui · DxFont · Effect · EngineCOL · EngineDFF · EngineIMG · EngineTXD · Light · Marker · Object · Pickup · Projectile · SearchLight · SVG · Vehicle · Water · Weapon
FileUm elemento cujo tipo não tem classe própria — a raiz, a raiz de um resource, um dummy — cai em Element. Um arquivo não tem classe-pai nenhuma, então atende aos membros de File e a nada de Element. Sound3D é a única classe que não é escolhida só pelo tipo do elemento: no cliente, um som tocado em 3D recebe Sound3D, e qualquer outro som recebe Sound.
Quando uma classe e a classe-pai dela declaram o mesmo membro, a classe vence:
| Membro | Na classe-pai | Sombreado por |
|---|---|---|
create | Element.create | as 29 classes que definem o próprio, mais Player.create sobre Ped.create, Sound3D.create sobre Sound.create e o par render target / screen source sobre DxTexture.create |
getType / type | getElementType | Pickup (a propriedade type só no servidor), Light, Projectile |
getRadius / radius | getElementRadius, que já é só do cliente | ColShape, Light |
destroy | destroyElement | Dui.destroy |
Propriedades sombreiam igualzinho aos métodos, e essa é a pegadinha mais provável: no servidor pickup.type é o tipo do pickup, não a string "pickup", e light.radius é o raio da luz, não o do elemento. Pickup declara a propriedade type só para o servidor, então no cliente pickup.type cai em Element.type e devolve "pickup", sim. getElementType(pickup) devolve "pickup" nos dois lados.
Um membro também pode ser declarado duas vezes na mesma classe, com um lado diferente em cada uma. São exatamente cinco. Player.spawn, Player.toggleControl, Player.setHudComponentVisible e Player.showHudComponent recebem o jogador no servidor e não recebem elemento nenhum no cliente; Object.getScale devolve um Vector3 no cliente e três números no servidor. Mesmo nome, uma ligação por lado. Os demais membros de HUD são declarações únicas, só do cliente, e não existem no servidor.
Quando um nome é declarado duas vezes para o mesmo lado, vence a primeira declaração cuja nativa existe. Vehicle.getHelicopterRotorSpeed, setHelicopterRotorSpeed e helicopterRotorSpeed são declarados tanto sobre getHelicopterRotorSpeed/setHelicopterRotorSpeed quanto sobre os antigos getVehicleRotorSpeed/setVehicleRotorSpeed; os dois pares são nativas do cliente, então o primeiro sempre vence e a segunda declaração nunca é ligada.
Disponibilidade
Seção intitulada “Disponibilidade”Um membro só é ligado onde a nativa dele existe. Element.clone envolve cloneElement, que é só do servidor, então é um membro de servidor; Element.getBoundingBox envolve uma nativa só do cliente e é um membro de cliente.
Um membro também pode ser fixado em um escopo mais estreito que o da nativa. fadeCamera é compartilhada, mas Player.fadeCamera é declarado só no servidor e Camera.fade só no cliente; Weapon.setAmmo é só do cliente embora setWeaponAmmo seja compartilhada. A coluna Lado da referência já leva as duas regras em conta, então leia ela em vez de deduzir pela nativa.
Getters e setters são ligados de forma independente, então uma propriedade pode existir pela metade. Cinco existem, e cada uma avisa isso nas Notas: Ped.choking, Ped.jetpack e Player.name não podem ser escritas no cliente; Vehicle.nitroActivated e Water.level não podem ser lidas no servidor. Atribuir a uma das três primeiras no cliente gera o aviso Property 'name' is read-only, que é verdade ali mas dá a entender que a propriedade é somente leitura em todo lugar.
Buscar um membro que não está lá devolve nil, e chamar nil é o erro que aparece. Uma classe estática em que nada foi ligado é outra história: ela nunca vira global, então Camera.fade() no servidor dá attempt to index a nil value (global ‘Camera’).
Handles que não são elementos
Seção intitulada “Handles que não são elementos”Dois valores carregam membros sem serem elementos: um resource e um timer. Ambos são userdata com metatable própria, então se usam como um elemento mesmo que nenhuma tabela de classe os liste.
local este = getThisResource()este:getName() -- o mesmo que getResourceName(este)este.state -- o mesmo que getResourceState(este)
local t = setTimer(tique, 1000, 0)t:setPaused(true) -- o mesmo que setTimerPaused(t, true)t.paused -- truetostring(t) -- "timer: 4"resource | timer | |
|---|---|---|
| Métodos | getName · getState · getRootElement · getExportedFunctions · call, mais os do servidor getMapRootElement · start · stop · restart · getInfo · getLoadTime · getLastStartTime · getLoadFailureReason · getOrganizationalPath | destroy · kill · reset · isValid · getDetails · isPaused · setPaused |
| Propriedades | name · state · rootElement · exportedFunctions, mais as do servidor loadTime · lastStartTime · loadFailureReason · organizationalPath — todas somente leitura | valid (somente leitura) · paused |
== | O mesmo resource compara igual. | O mesmo timer compara igual. |
tostring | resource: meu-resource | timer: 4 |
Eles se diferenciam dos elementos em dois pontos que vale conhecer:
- Um membro que só existe no servidor simplesmente não aparece no cliente — a mesma regra que a coluna Lado descreve para as classes de elemento.
- Escrever em um campo desconhecido gera aviso em vez de falhar em silêncio. Num timer você recebe
Timer has no property 'x', ouProperty 'valid' is read-onlyquando a propriedade é somente leitura. Um resource não tem nenhuma propriedade gravável.
As classes estáticas Timer e Resource são a outra metade da história: Timer.create(...) e Resource.getThis() são de onde esses valores vêm.
Um handle de arquivo não se encaixa em nenhum dos dois casos. No cliente ele é um elemento, atendido pela classe File; no servidor é um número simples, sem membros, então ali chame a classe de forma plana — File.read(handle, 16).
O que a camada não muda
Seção intitulada “O que a camada não muda”- Elementos continuam sendo userdata, assim como os handles
resourceetimeracima. As tabelas de classe são tabelas de consulta, não wrappers, e nenhum objeto é alocado por elemento. - Você não pode guardar campos próprios em um elemento. Use
setElementData/:setDatapara isso. - Classes estáticas são tabelas comuns, então atribuir um campo desconhecido a uma delas mantém o valor. Só as cinco que têm propriedades —
DiscordRPC,Camera,EngineStreaming,Nui,Voice— carregam metatable e avisam; as outras cinco aceitam em silêncio. Só os elementos recusam a escrita. tostring(elemento)imprime o tipo e o endereço no formato que o%pda plataforma produz —vehicle: 0000023F8C41A920no Windows. Qualquer coisa que não seja um elemento nosso imprimeelement: <invalid>.- Destruir um elemento esvazia o userdata por trás dele. A partir daí toda leitura OOP devolve
nile toda escrita OOP é descartada sem aviso nenhum, então verifiqueisElementantes de mexer em um elemento que você guardou.