Jump to content

Citizen

Moderators
  • Posts

    1,803
  • Joined

  • Last visited

  • Days Won

    8

Everything posted by Citizen

  1. I think you wanted to do more like this: addEvent("text", true) addEventHandler("text", getRootElement(), function() local count = 0 for k, v in ipairs(getElementsByType("player")) do if getElementDimension ( v ) == 2 then count = count + 1 end end setElementData(source, "players", count) end) Or as a function extension: local _getPlayerCount = getPlayerCount function getPlayerCount( dim ) if dim == nil then return _getPlayerCount() end local count = 0 for k, p in ipairs(getElementsByType("player")) do if getElementDimension(p) == dim then count = count + 1 end end end That you will then call like this: addEvent("text", true) addEventHandler("text", getRootElement(), function() local count = getPlayerCount(2) setElementData(source, "players", count) end) But your code doesn't make sense as it looks like you want to output a message to all players in a specific dimension ? In that case, getting the numbers of players in that dimension won't help you. You will need the list of the players in that dimension instead: function getPlayersInDimension( dim ) local players = {} for k, p in ipairs(getElementsByType("player")) do if getElementDimension(p) == dim then table.insert(players, p) end end return players end And then use it like this: addEvent("text", true) addEventHandler("text", getRootElement(), function( msg ) local players = getPlayersInDimension(2) for k, p in ipairs(players) do outputChatBox(p, msg) end end) Don't forget to send the text message as argument of your triggerServerEvent. More informations on what you try to achieve would be appreciated. Best regards, Citizen
  2. Oui ça aurait fonctionner mais au niveau perfs ça n'aurait pas été terrible. Il faut éviter d'exécuter du code trop compliqué/long a exécuter dans un onClientRender. En plus dans ton cas, la valeur afficher (la liste des véhicules) ne va pas changer souvent pour un joueur (aller, grand maximum 10 fois dans le mois ). Imaginons qu'un joueur fait tourner son jeu à 30FPS, ton code allait boucler sur TOUS les véhicules présent sur le serveur (potentiellement >500 vehs), en récupérant le owner et reconstruisant la table et tout ça 30 fois par seconde !! (et 60fois/s pour 60FPS etc). Tu aurais utilisé pas mal de ressource pour rien et qui aurait pu être utiliser pour autre chose. Il vaut mieux recalculer la table que quand il y en a besoin (lorsque le joueur se connecte, lorsqu'il achète, et lorsqu'il vend ou échange un véhicule). On aurait pu encore améliorer les perfs en évitant de reconstruire le string que retourne getPlayerVehiclesStr pour exactement le même raisons (tu peux le faire si tu veux en mettant le string en global que tu utiliseras directement dans ton dxDrawText). Mais le gain de perfs pour cette fonction sera minime. Le onClientRender est très pratique mais il faut faire attention à ce qu'on lui fait faire. A utiliser avec précaution ! Cordialement, Citizen
  3. Il suffit juste de ne pas faire de table.insert dans ton onClientRender. J'ai séparer ta fonction en 2 dont chacune à son rôle définit: -- S'occupe juste de mettre à jour pVehicles (à appeler lors d'un achat et vente d'un veh) function updatePlayerVehicles() pVehicles = {} for i,v in ipairs (getElementsByType("vehicle")) do local owner = getElementData(v, "owner") local pName = getPlayerName(localPlayer) if (owner == pName) then table.insert(pVehicles, v) end end end addEvent("onPlayerVehiclesChanged", true) addEventHandler("onPlayerVehiclesChanged", localPlayer, updatePlayerVehicles) -- Va retoruner "Sanchez | Monser 3" function getPlayerVehiclesStr() local vehStr = "" for k, v in ipairs( pVehicles ) do local vehName = getVehicleName(v) vehStr = vehStr + vehName if k < #pVehicles then vehStr = vehStr + " | " end end return vehStr end La première se charge juste de mettre à jour la table pVehicles et va contenir la liste des véhicules (et non pas leurs noms) que le joueur possède. Cette fonction est à appeler à chaque fois qu'il y aura une modification dans la liste des véhicules possédés par le joueur. Comme j'imagine que ce genre de modification sera réalisé du côté serveur, j'ai fait en sorte que tu puisses appeler cette fonction, qui est du côté client via la ligne suivante: triggerClientEvent(player, "onPlayerVehiclesChanged", player) la variable player doit évidemment être le joueur qui à réalisé la modification (achat, vente). La deuxième se charge de construire ton "tableau" en parcourant la liste des véhicules et en les mettant à la suite dans un string et en les séparant via un pipe ("|"). Ça sera la méthode à appeler dans ton onClientRender: function Infos() --[...]-- local vehStr = getPlayerVehiclesStr() --[...]-- dxDrawText("Véhicules: "..vehStr, screenW * 0.3968, screenH * 0.5664, screenW * 0.6032, screenH * 0.5924, tocolor(255, 255, 255, 255), 1.00, "default", "left", "center", false, false, false, false, false) --[...]-- end Avec ces modifications là, ça devrait fonctionner comme tu le souhaites.
  4. Nan ce code ne fait pas ce que j'ai dit. Ton code de transfert ouvre le xml côté client via un xmlLoadFile. Cette fonction va te charger que le node racine et ne va jamais charger l'intégralité du fichier (imagine un .xml de 4Go sur un serveur qui n'a que 2Go de RAM ... C'est le crash assuré). Et c'est à chaque fois que tu demanderas de lire ou écrire que le système va lire le fichier (mais toujours de façon à ne pas tout charger). Et comme c'est des types d’éléments spéciaux relatifs au PC qui le fait tourner (il contient des données internes comme l'emplacement du fichier sur l'ordinateur et qui n'est bien évidement pas copiable car le chemin ne correspondra à rien du côté serveur). Ce qu'il faut faire c'est ouvrir le fichier avec fileOpen, le lire morceaux par morceaux (ouai toujours l'histoire du cas où le fichier ferait 4Go, tu ne veux pas charger l'intégralité du fichier d'un coup). C'est exactement ce que fait l'exemple du wiki: Bon lui il lit des morceaux (buffer) de 500 Octets, tu peux te permettre plus que ça, genre 5Ko par 5Ko (5Ko = 5 * 1024 Octets). C'est le contenu de buffer que tu dois envoyer à ton serveur via un triggerServerEvent car il contient réellement le texte de ton fichier (enfin un morceau seulement). Donc au lieu du outputChatBox, tu utilises ton trigger. Du côté serveur, un premier trigger doit te dire de se préparer à recevoir des données en créant et ouvrant un fichier (fileCreate/fileOpen) en mode écriture, tu envois un trigger au client pour dire qu'il est prêt à recevoir et tu attends les morceaux via les triggers. A chaque trigger de l'event, tu fileWriteles morceaux que le client t'envois. Le client termine par envoyer un trigger pour dire que c'est finit, à ce moment là tu fileSaveet fileUnload. Mais je le répète, ne fait pas ça pour ton système de carshop. Sinon ... qu'a donné le test ?
  5. Si tu sauvegardes le xml du côté client, le xml est stocké sur le PC du joueur qui run le script en question. Hors ton système de chargement est du côté serveur et spawn les véhicules en chargeant le fichier xml dans le dossier cars de la ressource qui elle est sur le serveur. Si tu veux transférer un fichier côté client vers le serveur, il faut que le client ouvre le fichiers, transfert son contenu via un ou plusieurs triggerServerEvent que tu réceptionnes côté serveur pour ensuite écrire ce contenu dans un fichier que le serveur aura créé et ouvert en écriture. Mais je te déconseille fortement de faire ça car le client pourrait envoyer n'importe quoi via une version custom du jeu trafiqué et surtout tu pourrait avoir des conflits si des clients t'envois au même moment 2 versions de ce fichier modifié. C'est aussi pourquoi les sauvegardes dans un fichier .xml sont déconseillés bien que rapide et simple à mettre en place. Il faudrait que tu vérifies également si tu appelles bien un xmlUnloadFile après le chargement des véhicules.
  6. Hmmmm ça me paraît bizarre en effet ... Déjà première chose, ta fonction saveVehicule ne retournant rien, ta variable save vaudra toujours nil et t'affichera ton erreur dans la chatbox même si la sauvegarde a fonctionnée. Corrige ta fonction à la fin comme ceci: return xmlSaveFile(file) and xmlUnloadFile(file) Ta variable save vaudra true si les 2 fonctions ont renvoyées toutes les deux true, false si au moins l'une des deux a renvoyée false ou nil. Du coup la question stupide mais qui peut s'avérer être correcte si tu ne t'es basé que sur ton message d'erreur: Es-tu bien sûr qu'aucune ligne supplémentaire n'a été ajouté à ton fichier xml ? Regarde bien parce qu'au moment de la sauvegarde, une nouvelle ligne n'est pas obligatoirement sauvegardée à la fin (et n'est donc pas forcément le dernier tag de la liste. Autre test à faire, c'est d'essayer de rajouter un tag avec juste un attribut pour tester: 1 - Commente tous tes xmlNodeSetAttribute 2 - Ajoute juste cette ligne (après celles commentées): xmlNodeSetAttribute(child, "test", "dfigfjf") 3 - Essaye de sauvegarder un véhicule (en checkant le debugscript 3, ta chatbox pour tes messages et ton xml). Sinon essaye de mettre ta ressource dans le groupe Admin dans l'ACL (normalement y a pas besoin mais vu comme c'est partit, ça ne coûte rien d'essayer). Et oui le tag file dans la meta sert à marquer les fichiers qui doivent être téléchargés par le client. J'attends le retour de tes tests. Cordialement, Citizen
  7. Je ne connais pas du tout vagrant mais en lisant la doc, je vois tout de suite une solution Le transfert de port (Port Forwarding) Pour imager, ça permet de relier directement un port de ta VM à un port de ton vrai PC. Un serveur MTA à besoin de 3 ports pour fonctionner totalement: Port 22003 UDP (pour que les joueurs puissent s'y connecter) Port 22005 TCP (pour que les joueurs puissent télécharger les fichiers de tes ressources) Port 22126 UDP (pour que ton serveur apparaisse dans la liste des serveurs. Optionnel donc) Donc dans le fichier vagrantfile de ton dossier mta sous OSX ajoute les lignes suivantes: config.vm.network "forwarded_port", guest: 22003, host: 22003, protocol: 'udp' config.vm.network "forwarded_port", guest: 22005, host: 22005, protocol: 'tcp' config.vm.network "forwarded_port", guest: 22126, host: 22126, protocol: 'udp' Une fois sauvegardé fait un vagrant up si ta VM n'est pas allumée ou un vagrant reload si elle est allumée. Pour te connecter au serveur utilise l'ip 127.0.0.1:22003 ou localhost:22003. Sources: https://docs.vagrantup.com/v2/getting-s ... rking.html https://docs.vagrantup.com/v2/networkin ... ports.html
  8. Oula "écrase" est un bien grand mot. T'éxagères beaucoup je trouve. La seule chose que samp a fait c'est de rajouter 500 objets sur des ids qui n'étaient pas utilisés et que tu peux donc remplacer par d'autres objets customs. Il t'es donc impossible de les utiliser pour rajouter des skins, des véhicules etc. Que des objets donc ... Ce que MTA veut faire, c'est de fournir des fonctions qui permettrait de rajouter n'importe quel type (objets, véhicules, skins etc) en utilisant n'importe quel id. Et avant de pouvoir faire ça il faut préparer le terrain pour être capable de rajouter à la volée du handling pour les véhicules et plein d'autres choses (expliqué dans le bugtracker) afin qu'il soit loadé correctement. Ça demande énormément de compétences, de savoirs sur le fonctionnement interne de gtasa et pour l'instant personne n'a encore le courage de se lancer dans un tel chantier.
  9. On est pas près de voir cette feature de si tôt. Le ticket bugtracker que tu nous as linké est lié à un autre ticket qui doit être obligatoirement fait avant ! http://bugs.mtasa.com/view.php?id=7728 D'après la liste, ça demande beaucoup de travail dont une partie du code source doit être réécrite car elle n'avait pas été codé dans cette optique.
  10. Je peux vous aider (gratuitement) à régler vos problèmes MySQL si vous êtes coincé. Une fois le problème résolu, je vous laisserai continuer votre serveur de votre côté. Envoyez-moi un skype par MP
  11. Bonjour, Déjà on aurait aimé avoir plus de détails que juste "ça marche pas, trouvez le problème pour moi svp, voici les logs" C'est pas vraiment ce que tu as dit, je sais, mais je le ressent pareil. Ensuite le problème (ligne 329 dans les logs) n'est pas du tout la ligne 4 dans ce que tu as collé, c'est la ligne 7. Prenons juste ce bout de code: local xL, yL, zL = getElementData(source, "lastzombiespawnposition")[3] or getElementData(source, "lastzombiespawnposition")[1] or false, getElementData(source, "lastzombiespawnposition")[2] or false, false if xL and getDistanceBetweenPoints3D(x, y, z, xL, yL, zL) < 50 then return end C'est pareil que d'écrire sous cette forme (que tu comprendras déjà mieux): local xL = getElementData(source, "lastzombiespawnposition")[3] or getElementData(source, "lastzombiespawnposition")[1] or false local yL = getElementData(source, "lastzombiespawnposition")[2] or false local zL = false if xL and getDistanceBetweenPoints3D(x, y, z, xL, yL, zL) < 50 then return end On peut donc constater que quoi qu'il arrive, zL vaudra toujours false lorsqu'on va l'utiliser dans le getDistanceBetweenPoints3Dà la ligne 4 ce qui est interdit vu que la fonction n'accepte que des nombres (d'où le warning qui engendre ensuite l'erreur). Enfait si on réfléchit bien, on comprends que lorsqu'on crée un zombie pour un joueur (source) on va vérifier si le dernier zombie qu'on a spawn pour le joueur est à moins de 50m de distance du joueur en question. Si c'est le cas (le dernier était à moins de 50m) on annule l’exécution du reste de la fonction via le "return". On peut aussi facilement deviner que le getElementData(source, "lastzombiespawnposition") doit nous retourner un tableau de ce style: local pos = { 1200, -2590, 12.3 } qui représente la position x, y et z de la map où le dernier zombie spawné pour le joueur a apparu. Et donc pour accéder à chacun des nombres du tableau, il suffit de faire: local x = pos[1] -- 1200 local y = pos[2] -- -2590 local z = pos[3] -- 12.3 Là le codeur c'est un peu mélangé les pinceaux (ou alors t'as récupérer une version foireuse, ou alors t'as édité sans faire exprès en déplaçant un bout de code qu'il ne fallait pas) et il fallait plutôt l'écrire comme ceci: local xL = getElementData(source, "lastzombiespawnposition")[1] or false local yL = getElementData(source, "lastzombiespawnposition")[2] or false local zL = getElementData(source, "lastzombiespawnposition")[3] or false if xL and getDistanceBetweenPoints3D(x, y, z, xL, yL, zL) < 50 then return end Solution 1: Qu'on peut optimiser en appelant qu'une seule fois le getElementData comme ceci: local lastPos = getElementData(source, "lastzombiespawnposition") local xL = lastPos[1] or false local yL = lastPos[2] or false local zL = lastPos[3] or false if xL and getDistanceBetweenPoints3D(x, y, z, xL, yL, zL) < 50 then return end Solution 2: (Pareil que la Solution 1 mais avec xL, yL et zL mit sur une seule ligne comme au tout début) local lastPos = getElementData(source, "lastzombiespawnposition") local xL, yL, zL = lastPos[1] or false, lastPos[2] or false, lastPos[3] or false if xL and getDistanceBetweenPoints3D(x, y, z, xL, yL, zL) < 50 then return end Choisi l'une ou l'autre des solutions, ce sont exactement les même du point de vu exécutions et résultats. Je te conseille la 1ère vu qu'elle est plus facile à lire et donc à modifier si besoin (toujours anticiper). Cordialement, Citizen
  12. Bonsoir ma2med, J'ai mis un peu de temps parce que je ne suis pas très à l'aise avec les matrices. Ce genre de problème j'ai l'habitude de les régler via de la trigonométrie mais les matrices sont bien plus rapides à utiliser dès qu'on les a compris. Voici le code: addEventHandler("onClientPreRender", root, function() for k, veh in ipairs (getElementsByType("vehicle")) do local vehmodel = getElementModel(veh) if vehmodel == 431 then -- BUS local offsetX, offsetY, offsetZ = 1.3073, -4.400024414, 0.59999961853 -- position relative du début de la ligne par rapport au veh local x, y, z = getPositionFromElementOffset(veh, offsetX, offsetY, offsetZ) -- position absolue du début de la ligne par rapport au veh local x2, y2, z2 = getPositionFromElementOffset(veh, offsetX, offsetY+2, offsetZ) -- position absolue de la fin de la ligne par rapport au veh local faceX, faceY, faceZ = getPositionFromElementOffset(veh, offsetX+5, offsetY, offsetZ) -- position absolue vers laquelle l'image doit être orienté dxDrawMaterialLine3D(x, y, z, x2, y2, z2, img, 0.75, tocolor(255,255,255,255), faceX, faceY, faceZ) end end end) -- Taken from [url=https://wiki.multitheftauto.com/wiki/GetElementMatrix]https://wiki.multitheftauto.com/wiki/GetElementMatrix[/url] example function getPositionFromElementOffset(element, offX, offY, offZ) local m = getElementMatrix ( element ) -- Get the matrix local x = offX * m[1][1] + offY * m[2][1] + offZ * m[3][1] + m[4][1] -- Apply transform local y = offX * m[1][2] + offY * m[2][2] + offZ * m[3][2] + m[4][2] local z = offX * m[1][3] + offY * m[2][3] + offZ * m[3][3] + m[4][3] return x, y, z -- Return the transformed point end Explications: La fonction getPositionFromElementOffset (que j'ai copiée collée) permet de calculer une position x, y, z dans la map de GTA à partir d'un offset x, y, z d'un élément donné. Elle va utiliser la matrice de l’élément qui contient en gros la position x, y, z et la rotation rx, ry, rz de l'élément dans la map de GTA. J'ai donc dû calculer 3 positions absolues (absolue = position dans la map de GTA et relative = position par rapport au centre de l'élément): - la 1ère est la position absolue x, y, z du début de la ligne - la 2ème est la position absolue x, y, z de la fin de la ligne - la 3ème est la position absolue x, y, z vers laquelle l'image doit être orientée (l'image est placée sur la droite du bus donc il faut orienter la face de l'image en direction d'une position x, y, z qui doit être encore plus à droite du bus (d'où le offsetX + 5). J'ai également ajusté l'offsetX de ton code initial car on pouvait voir un espace entre l'image et le bus. Je l'ai donc passé de 1.3400488281 à 1.3073. Et en bonus, j'ai changé le code pour que l'image s'affiche sur tous les bus, même si on est pas en train de le conduire (j'ai donc viré le getPedOccupiedVehicle pour faire une boucle for sur tous les véhicules et j'exécute le code sur tous les 431 (= bus)). Résultat: J'ai placé le bus de façon à ne pas être à 0° et je l'ai également penché de travers pour prouver que ça suit bien la rotation du bus (ce que ne pouvait pas faire ton code initial même orienté à 0°).
  13. Well if you follow the steps carefully instead of clicking next next next without modifying the path then yeah you will get this error. The regedit trick is only to "update" your MTA Script Editor to point on a new MTA version installed (you will need to create the mta account again and make the integration again). Here is a video I just made using the last mta sa version (1.5): If you still get stuck tell me where is your mta installed and what is your operating system (I mean what version of windows and if you are in 32 or 64 bits). Regards, Citizen
  14. Two solutions: 1: Reinstall MTA Script Editor (MTASE) and make sure you specify the correct Multi Theft Auto: San Andreas folder and the correct Multi Theft Auto: San Andreas server folder the installation wizzard will ask you. 2: open the registry of your computer and look for the Multi Theft Auto: Script Editor key. I'm on a 64bit operating system so the path to the key is: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Multi Theft Auto: Script Editor (on 32bits, the key might be located at: HKEY_LOCAL_MACHINE\SOFTWARE\Multi Theft Auto: Script Editor) You should see something similar to this: Just modify the MTAClientDir and MTAServerDir to match your current MTA installation. And as you can see, I'm using the MTA San Andreas 1.5 with MTASE and it works. Note: You should not trust others about downloading a custom/fixed version of MTASE like MihăiţÂ did because there is no need to ! (specially if they do not provide a virustotal scan ...)
  15. Si tu comprends pas la partie qui utilise le _rankGrid et table.hasKey, c'est pas grave parce j'ai dû pondre du code supplémentaire un peu hardcore pour que ça marche avec le seul bout de code que tu as montré. Si on prends l'exemple du système GUI, tu crées tes gui element que tu crèes, cache, montre et update selon tes besoins, et au moment où tu as besoin. En aucun cas tu auras des guiCreateXXX dans un onClientRender mais le code que tu avais était dans un onClientRender donc pour éviter de te demander d'autres parties de ton code qu'on aurait dû en plus changer pour faire propre, je me suis forcer à faire un peu de hack pour éviter de spammer le dxCreateText qui devait quand même se recréer si un joueur a quitté ou rejoins le serveur (donc si le nombre de players est différent du nombre de dxText qu'on a créé pour ça). Après avec tous les commentaires que j'ai placés, tu devrais normalement comprendre les fonctions suivantes: - dxCreateText - dxRenderEverything - isPosInsideBox - isClickInsideDXElement et - onClientDXClickWatcher Si ce n'est pas le cas, demande moi ici ce lesquels tu n'as pas compris, j'essayerai d'expliquer de façon plus détaillée. Ça j'en étais quasiment certains mais j'ai quand même laissé le bénéfice du doute. Enfait, les valeurs que tu as calculées pour x, ly, left et top ne sont pas corrects. Pour bien visualiser, un dxDrawText va tout d'abord créer un rectangle (ou aussi une bounding box) dont tu définis les coordonnées de son angle en haut à gauche (top et left sur le wiki) et les coordonnées de son angle en bas à droite (right et bottom sur le wiki). Ensuite une fois qu'il a créé son rectangle, il va placer son texte en fonction de l'alignement horizontal et vertical que tu lui as spécifié (alignX et alignY). Donc visuellement ton texte paraît bien placé et paraît ne pas occuper une grosse partie de ton écran, mais l'élément dans son entièreté en occupe une énorme. Et comme j'utilise les coordonnées et les dimensions de l'élément (donc les coordonnées et les dimensions de la bounding box) pour déterminer si un clic souris à été fait sur lui, si la bounding box prends tout l'écran, le onClientDXClick va être trigger sur tout l'écran. Pour corriger tout ça, il juste corriger la bounding box. Colle ce code n'importe où et tu pourras visualiser la bounding box de chaque dxDrawText réalisé dans ton script. local _dxDrawText = dxDrawText -- On copie la fonction originale de MTA local dxDrawText = function(text, left, top, right, bottom, color, scale, font, alignX, alignY, clip, wordBreak, postGUI) dxDrawRectangle(left, top, right-left, bottom-top, tocolor(200, 0, 0, 150)) -- on affiche la bounding box en rouge semi transparent _dxDrawText(text, left, top, right, bottom, color, scale, font, alignX, alignY, clip, wordBreak, postGUI) -- et on appelle normalement le vrai _dxDrawText end Je peux te faire un mini-cours sur skype, ça me sera plus simple et plus rapide pour t'expliquer et montrer des choses. Là je mets un temps fou à écrire toutes mes explications ^^. Ajoute-moi quand tu pourras (check tes MP). J'en profiterai pour t'expliquer dans le détail le process de mon code précédent (genre comment et pourquoi il fonctionne ).
  16. Ouai je voulais écrire createElement, dxCreateElement n'existe pas et j'utilise les balises [ wiki][ /wiki] qui met le lien automatiquement en fonction du nom que tu mets, et donc forcément, la page de wiki dxCreateElement n'éxiste pas. Les dxDraw servent juste à afficher quelque chose à l'écran, c'est des fonctions ultra basiques qui ne stockent ou ne créent aucune donnée. Les fonctions de GUI en revanche sont représentés par un élément que tu dois créer via guiCreateWindow, guiCreateButtonetc. Une fois que ton élément est créé, MTA se charge de l'afficher avec ses propres fonctions "guiDraw" (j'explique de façon simple, mais la logique globale reste correcte). Tu peux ensuite interagir avec tes éléments GUI avec les guiSetPosition, onClientGUIClicketc. Dans ton cas tu n'utilises que les fonctions de draw et tu n'as donc aucun élément avec lequel tu pourrais interagir. Dans ton code c'est quoi left et top ? tu les as réellement définit plus haut ? oO Parce que si t'as juste recopié les noms de variables du wiki, sans les avoir définit ça ne devrait pas marcher ! Voici ma solution (non testée car je ne suis pas sur mon pc). Commence par copier coller ces fonctions: -- Fonction de création d'un élément dxText function dxCreateText(text, left, top, width, height, color, scale, font, alignX, alignY, clip, wordBreak, postGUI) -- On crée l'élément local dxText = createElement("dxText") -- On lui set les données nécessaires au dxDrawText setElementData(dxText, "text", text, false) setElementData(dxText, "position", {x=left, y=top}, false) setElementData(dxText, "size", {w=width, h=height}, false) setElementData(dxText, "color", color, false) setElementData(dxText, "scale", scale, false) setElementData(dxText, "font", font, false) setElementData(dxText, "align", {x=alignX, y=alignY}, false) setElementData(dxText, "clip", clip, false) setElementData(dxText, "wordBreak", wordBreak, false) setElementData(dxText, "postGUI", postGUI, false) -- On retourne l'élement return dxText end -- Fonction de render des dx elements function dxRenderEverything() -- render des dxText: for k, i in ipairs (getElementsByType("dxText")) do local text = getElementData(dxText, "text") local pos = getElementData(dxText, "position") local size = getElementData(dxText, "size") -- dxDrawText ne prend pas de width ni de height, il prend les positions de fin, donc faut les calculer: local right, bottom = pos.x + size.w, pos.y, size.h local color = getElementData(dxText, "color") local scale = getElementData(dxText, "scale") local font = getElementData(dxText, "font") local align = getElementData(dxText, "align") local clip = getElementData(dxText, "clip") local wordBreak = getElementData(dxText, "wordBreak") local postGUI = getElementData(dxText, "postGUI") -- On render dxDrawText(text, pos.x, pos.y, right, bottom, color, scale, font, align.x, align.y, clip, wordBreak, postGUI) end --[[ on pourra rajouter pour un dxButton: for k, i in ipairs (getElementsByType("dxButton")) do -- faire les calculs nécessaires et faire les dxDraw end]] end addEventHandler("onClientRender", root, dxRenderEverything) addEvent("onClientDXClick") -- Fonction qui s'occupe de trigger l'event onClientDXClick function onClientDXClickWatcher(button, state, clickX, clickY) for k, dxText in ipairs (getElementsByType("dxText")) do if isClickInsideDXElement(clickX, clickY, dxText) then triggerEvent("onClientDXClick", dxText, button, state, clickX, clickY) end end --[[ on pourra rajouter pour un dxButton: for k, dxButton in ipairs (getElementsByType("dxButton")) do if isClickInsideDXElement(clickX, clickY, dxButton) then triggerEvent("onClientDXClick", dxButton, button, state, clickX, clickY) end end]] end addEventHandler("onClientClick", root, onClientDXClickWatcher) -- Fonction qui retourne true si les coordonnées d'un clic correspond à la position de l'élément function isClickInsideDXElement(clickX, clickY, dxElem) local dxType = getElementType(dxElem) if dxType == "dxText" then local pos = getElementData(dxElem, "position") local size = getElementData(dxElem, "size") return isPosInsideBox(clickX, clickY, pos.x, pos.y, size.w, size.h) end --[[ on pourra rajouter pour un dxButton: if dxType == "dxButton" then ... return isPosInsideBox(...) end]] end -- Fonction qui retourne true si une coordonnées est à l'intérieur d'un rectangle définit function isPosInsideBox(posX, posY, boxX, boxY, boxW, boxH) return posX >= boxX && posX <= boxX + boxW && posY >= boxY && posY <= boxY + boxH end -- Fonction qui retourne true si un tableau contient une certaine key function table.hasKey(t, key) for k, _ in pairs (t) do if k == key then return true end end return false end Puis remplace exactement les lignes que tu m'as copiées par ceci: for numColonne, player in pairs(players) do --- hack if not _rankGrid then _rankGrid = {} -- Va juste nous aider à ne créer qu'un seul dxElement par ligne (et donc par player) vu qu'on est dans un onClientRender elseif #_rankGrid ~= #players then -- Si la liste de player à changée, il faut update for player, dxElem in pairs(_rankGrid) do destroyElement(dxElem) -- On supprime tout avant recréation pour ne pas avoir de doublons end _rankGrid = {} end --- local w, h = 880, 450; local x, y = (scr[1]/2)-(w/2)+155, (scr[2]/2)-(h/2)+110; ly = y + numColonne + 15 username = string.gsub(getPlayerName(player), "_", " ") local text = username -- de base on prend son username local rank = tonumber(getElementData(player, "rankfaction")) or 0 if rank == 6 then text = username.." (Leader)" -- mais s'il est rang 6, on rajoute " (Leader)" au username end -- Au lieu de draw directement, on crée un dxText -- dxDrawText(text, x+155, ly, left, top, white, 1.2, "default", "center", "center", false, false, true) if not table.hasKey(_rankGrid, player) then -- Une seule création du dxText avec son onClientDXClick handler local dxText = dxCreateText(text, x, ly, left-x, top-ly, white, 1.2, "default", "center", "center", false, false, true) setElementData(dxText, "player", player, false) -- On stock une donnée dans le dxText, ce qu'on ne pouvait pas faire avec un simple dxDrawText addEventHandler("onClientDXClick", dxElem, testHandler, false) --- hack _rankGrid[player] = dxText --- end end et enfin le testHandler auquel j'ai attaché l'event onClientDXClick: --- TEST --- function testHandler(button, state, clickX, clickY) if button ~= "left" then return end -- je fais rien si c'est pas le clic gauche de la souris qui a produit le trigger local text = getElementData(source, "text") local player = getElementData(source, "player") local realPlayerName = getPlayerName(player) outputChatBox("You clicked on \""..text.."\" (Real name: "..realPlayerName..")") -- Tu peux utiliser player comme bon te semblera end Cette solution est censée (sauf erreur dans mon code) faire exactement le même rendu que ton précédent code mais si tu auras maintenant un élément avec lequel tu pourras intéragir (en faisant un setElementData pas exemple) et si tu cliques gauche sur une ligne, tu devrais voir apparaître un message dans la chatbox qui contient le texte de la ligne ainsi que le nom original du player (parce que j'ai vu que tu replaçais les _ par des espaces au moment de l'affichage et surtout pour montrer que j'arrive bien à récupérer le joueur depuis la ligne cliquée sans faire de calcul ou de getPlayerFromName). Pour ton problème de positionnement vertical, il faut que tu définisses la hauteur d'une ligne en pixels (par exemple lineH = 40). Puis il faut que tu saches à quel Y de l'écran ta liste doit commencer (par exemple startY = 363). Une fois que tu as ces 2 donées, tu peux facilement calculer la position Y de chaque ligne dans ton for que tu m'as montré en faisant: local lineY = startY + (numColonne * lineH) (D'ailleurs numLigne serait un nom plus approprié, et pourrait éviter des erreurs à cause d'un mal entendu).
  17. Tu es en train de regarder un event d'une ressource spécifique (à savoir dxGui), si tu n'utilises pas dxGui, tu n'as effectivement aucune chance de pouvoir utiliser un des events de cette ressource. Pourrais-tu nous montrer à quoi ressemble la création de (je suppose) ton dx button ainsi que son affichage ? Si tu ne crées pas d'élement (dxCreateElement) pour représenter d'une manière logique ton bouton et que tu fais juste des dxDraw, on va être incapable de faire un onClientDXClick de façon simple.
  18. Ouai j'étais également en train de le refaire de façon propre mais j'ai ensuite vu que tu avais résolu le problème. On triche un peu en faisant comme ça mais si ça te va comme ça ...
  19. Désolé du retard de ma réponse. Je vois que tu as passé ton titre de topic en Résolu est-ce bien le cas ? Le problème était la réinitialisation de la variable start qui est seulement accessible à l'endroit où elle à été créée. Faire un start = getTickCount() va donc fonctionner que si c'est fait en "interne" comme tu dis. Le faire dans une autre ressource ne va pas modifier le start de la bonne ressource et va juste créer une nouvelle variable start de son côté mais qui n'est pas utilisée. Le plus simple était donc de créer une nouvelle fonction exportée qui va se charger de faire le start = getTickCount() en interne et que tu appelleras à chaque fois que tu veux remettre le loading à 0%.
  20. Désolé de ne pas avoir pu te répondre plus tôt. Ce dont tu as besoin, c'est la fonction getRealTime() qui prend le temps (date, heures, minutes et secondes) de la machine qui fait tourner le script (comme c'est un script serveur, il prendra celui du serveur). Il retourne un tableau avec des valeurs dont sa représentation sous forme de timestamp. Le timestamp est utilisé pour représenter une date sous forme de nombre de secondes écoulées depuis le 1er Janvier 1970 00:00:00. Ainsi, si on prends 10 en tant que timestamp, il représente la date du 1er Janvier 1970 00:00:10 soit, 10 secondes après le nouvel an 1970. Je te laisse imaginer que le timestamp de la date actuelle est donc un très gros chiffre (Pour info, le timestamp du 14 Octobre 2015 00:00:00 est 1444860000) Ta colonne en base de donnée (MySQL ?) devra être du type BIGINT non signé (unsigned) pour être sûr d'être capable de stocker les gros timestamp futurs. Et voici la logique qui permet de tracker le temps de jeu total d'un joueur sur ton serveur en utilisant les timestamps: function connexion() local totalPlayTime = -- Ici tu récupères depuis la DB ou tu l'assignes à 0 si pas de valeur setElementData(player, "totalPlayTime", totalPlayTime) -- on le garde en mémoire pendant toute la session -- on prend le timestamp actuel et on le garde en mémore pendant toute la session local startTimestamp = getRealTime().timestamp setElementData(player, "startTimestamp", startTimestamp) end function deconnexion() local totalPlayTime = getPlayerTotalPlayTime(player) -- Ici sauvegarder totalPlayTime en DB end function getPlayerTotalPlayTime(player) -- on prend le timestamp du moment où il s'est connecté local startTimestamp = getElementData(player, "startTimestamp") or 0 -- on prend le timestamp actuel local finishTimestamp = getRealTime().timestamp -- on calcule le nombre de secondes jouées sur le serveur pour cette session local diffTimestamp = finishTimestamp - startTimestamp -- on récupère son temps de jeu total qu'il avait en se connectant local totalPlayTime = getElementData(player, "totalPlayTime") -- on ajoute son temps de jeu de cette session totalPlayTime = totalPlayTime + diffTimestamp return totalPlayTime end Je te laisse rajouter les events dont tu as besoin et remplacer les variables (genre player) et les morceaux de code manquants relatifs à ta DB. Si tu veux l'afficher dans une fenêtre de statistiques, dans les données que tu vas envoyer au client via un triggerClientEvent tu appelleras getPlayerTotalPlayTime avec en paramètre le client et tu enverras la valeur que te retourne cette méthode. Et du côté client, comme un humain n'est pas capable de convertir un timestamp en date de tête, tu pourras effectivement utiliser FormatDate, mais tu ne n'utiliseras donc cette fonction que pour de l'affichage, pas pour faire de la sauvegarde ou des calculs de durées comme te le proposait Wumbaloo, c'est le rôle du timestamp ça. Voilà, bonne chance et n'hésite pas à revenir poser des questions si besoin. Cordialement, Citizen
  21. Gamemode volé au serveur owlgaming, aucune aide ne te sera apporté. Si c'est "un ami" qui te l'as donné, demande lui de l'aide directement. Topic fermé.
  22. Oui tu aurais pu utiliser ça, c'est juste que ça tiens en 1 seule ligne mais au niveau performance, la différence est plus qu'insignifiante. Je pense quand même que ton if est plus performant que le string.format
  23. %02d signifie que je veux afficher un nombre (`d`) avec au moins deux chiffres (`2`) et rajouter des zeros si ce n'est pas le cas (`0`). Exemples: string.format("%02d", 5) -- "05" string.format("%02d", 10) -- "10" string.format("%02d", 123) -- "123" getRealTime utilise la date du système qui fait tourner le script dans lequel il est utilisé. Donc une date récupéré depuis un système en GMT+2, convertit en timestamp et sauvegardé en db, sera aussi en GMT+2 lorsqu'on fera la manipulation inverse si le système reste en GMT+2.
  24. Ok je vois, je t'ai fais la fonction de conversion en français et je me suis permis de rajouter l'année et les minutes: local _months = {"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"} local _weekdays = {"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"} function timeToFr(t) local year = t.year+1900 local monthFr = _months[t.month+1] local weekdayFr = _weekdays[t.weekday+1] local day = t.monthday local hours = t.hour -- à toi de voir si tu veux le forcer sur 2 chiffres local minutes = string.format("%02d", t.minute) -- force sur 2 chiffres return string.format("%s %s %s %s %sh%s", weekdayFr, day, monthFr, year, hours, minutes) end Exemple d'utilisation: local date = getRealTime(1444464349) outputChatBox(timeToFr(date)) -- Samedi 10 Octobre 2015 10h05
  25. Bah oui, c'est le résultat attendu, quel est le problème ? Je veux bien comprendre que c'est pas le résultat que tu voulais, mais dans ce cas facilite moi la tâche en me disant exactement le résultat que tu souhaites avoir avec ce timestamp. Si tu veux pas le numéro du mois en chiffre mais en lettre, il va falloir que tu l'as convertisse, c'est pas compliqué mais ça dépends de la langue utilisé sur ton serveur. Mais avec ce qu'on a déjà, il est facile de faire un affichage comme ceci: 10/10/2015 10:05 Si ton problème est que le mois devrait être 10 et non pas 9, c'est parce que les mois sont compté à partir de 0 (Janvier) jusqu'à 11 (Décembre). J'attends ta réponse pour savoir réellement quel est le problème avec ça.
×
×
  • Create New...