Sku v42.11 - notes de version

Maître de vol : Sku nomme le prochain point de vol et peut vous y faire descendre

Simple : Sur un trajet en taxi chez le maître de vol, Sku annonce désormais quel point de vol arrive ensuite, et vous pouvez y descendre au lieu de voler jusqu'à la destination. La touche pour cela est CTRL-MAJ-E par défaut ; comme toutes les autres touches de Sku, elle est réassignable, listée sous « Navigation et points de passage ». Il y a deux annonces par étape parce qu'elles répondent à des questions différentes : le prochain arrêt au décollage et après chaque saut, et « atterrissage anticipé possible » environ 800 mètres avant. Avant le dernier arrêt, Sku reste silencieux - y atterrir en avance revient simplement à arriver. L'ensemble peut être désactivé sous « Maître de vol ». Uniquement les taxis des maîtres de vol, jamais les véhicules.

Technique : Nouveau module SkuCore/taxi.lua. La route est enregistrée, pas devinée : hooksecurefunc sur TakeTaxiNode se déclenche pendant que la carte des taxis est encore ouverte, la seule fenêtre où GetNumRoutes, TaxiGetNodeSlot et TaxiNodeName répondent encore. La liste des arrêts est donc exacte, et chaque arrêt qui y figure est réellement accessible pour ce personnage. Un curseur de proximité la fait avancer à mesure que le taxi survole chaque nœud ; la recherche du nœud le plus proche ne subsiste que comme repli signalé pour un /reload en plein vol, filtré par faction et par découverte. Trois particularités du client sont consignées dans l'en-tête du fichier pour ne pas avoir à être redécouvertes : la demande vous fait toujours atterrir au point de vol SUIVANT (comme le dit aussi

TAXI_CANCEL_DESCRIPTION), ce qui explique pourquoi le curseur de route est la bonne réponse ; IsPossessBarVisible renvoie false pendant que PossessButton2 est affiché - le seul élément fiable est MainMenuBarVehicleLeaveButton, lu avec IsVisible, et il est présent pendant tout le vol ; et PLAYER_CONTROL_LOST se déclenche alors qu'UnitOnTaxi est encore false, de sorte que tout nettoyage branché dessus effacerait des données fraîchement enregistrées. Chaque point d'entrée revérifie UnitOnTaxi - le même test que Blizzard utilise pour choisir TaxiRequestEarlyLanding plutôt que VehicleExit. Diagnostics : lignes « taxi: » dans SkuDebugLog et /skutaxi.

Les invitations de groupe ne sont plus refusées silencieusement

Simple : Fermer le menu de Sku - pour quelque raison que ce soit : sortir des sacs avec Échap, ouvrir une autre fenêtre, un passage en combat - refusait une invitation de groupe en attente, et la personne qui vous invitait voyait un refus. Pour un joueur voyant, il ne se passe absolument rien à ce moment-là ; la boîte de dialogue reste simplement là pendant ses 60 secondes. C'était donc une perte que seuls les utilisateurs de Sku subissaient. L'invitation n'est plus répondue à la fermeture du menu ; elle quitte simplement l'écran et reste accessible sous « Local ».

Technique : StaticPopupDialogs["PARTY_INVITE"].OnHide appelle DeclineGroup à moins que dialog.inviteAccepted ne soit défini - et OnHide se déclenche bel et bien sur un simple frame:Hide() (OnHide XML -> GameDialogMixin:OnHide -> StaticPopup_OnHide -> dialogInfo.OnHide). Le OnHide du menu de Sku exécute exactement ce StaticPopup1:Hide(). Nouveau : SkuCore:NeutralizePopupHide(frame), appelé juste avant : pour PARTY_INVITE il pose inviteAccepted afin que le OnHide de Blizzard saute le refus, et marque le cadre pour que notre propre hook de masquage distingue un masquage neutralisé d'une vraie réponse - de simples champs sur un cadre non sécurisé, aucune contamination ; OnShow réinitialise inviteAccepted, de sorte qu'un affichage ultérieur véritable n'est pas affecté. Il n'existe pas d'API « invitation en attente » sur ce client, le cache est donc le nôtre : alimenté par PARTY_INVITE_REQUEST, abandonné sur PARTY_INVITE_CANCEL, sur tout masquage non neutralisé de la boîte de dialogue (une vraie acceptation, un refus ou une expiration) et par sa propre échéance. Pour mémoire, toutes les boîtes de dialogue dont le OnHide est destructeur ont été auditées : PARTY_INVITE (corrigé ici), LEVEL_GRANT_PROPOSED (DeclineLevelGrant), EQUIP_BIND et apparentées (CancelPendingEquip - pas d'indicateur d'esquive, demande un autre correctif) et QUIT (CancelLogout, sans danger).

Les demandes en attente restent accessibles dans le menu Local

Simple : Quand vous quittez le menu de Sku avec Échap, une boîte de dialogue ouverte disparaît de l'écran - une invocation, la demande de libérer l'esprit, une offre de résurrection, une vérification de préparation. Jusqu'ici elle était perdue jusqu'au prochain /reload, alors même qu'elle restait valide sur le serveur pendant tout ce temps. De telles demandes apparaissent désormais comme une entrée à part directement sous « Local », nommée d'après la demande ; DROITE ou ENTRÉE affiche à nouveau la vraie fenêtre, et Sku y descend pour que vous utilisiez les boutons habituels.

Technique : Nouveau module SkuCore/pendingPrompts.lua. Les demandes sont réémises à partir de l'ÉTAT, pas du cadre : l'invocation via C_SummonInfo, la résurrection via ResurrectGetOfferer, la libération après la mort via GetReleaseTimeRemaining plus les options de résurrection personnelle de C_DeathInfo, la vérification de préparation via GetReadyCheckTimeLeft/Status. Un :Hide() brut n'atteint que StaticPopup_OnHide et jamais OnCancel, rien n'était donc jamais refusé ni libéré - PLAYER_ENTERING_WORLD est le seul endroit où Blizzard réaffiche DEATH et CONFIRM_SUMMON, d'où le « perdu jusqu'au /reload ». Tant que sa propre boîte de dialogue est à l'écran, l'entrée est supprimée, elle ne fait donc jamais doublon avec le nœud issu du balayage de la popup. Il est important de noter que ces entrées sont PASSIVES : le prédicat « quelque chose est-il ouvert » dans CheckFrames a un double rôle - il maintient un menu ouvert en vie et il force l'ouverture du menu via SlashFunc. Les demandes en attente n'alimentent que le premier (tPendingOnly), sans quoi une demande accessible en permanence arracherait le menu à chaque mise à jour des sacs ; le même raisonnement les tient à l'écart de combatMenuHasWindow. La première forme était imbriquée, avec ses propres feuilles d'action - fonctionnel, mais trop de navigation. C'est maintenant un unique nœud plat par demande : kind="action" avec dynamic=true, uniquement pour que le gestionnaire de touches achemine DROITE vers OnSelect, plus une surcharge d'OnSelect qui contourne la mécanique de descente générique ; les vrais boutons arrivent par le balayage normal de la fenêtre réaffichée.

Échange : ce qui se trouve dans la fenêtre d'échange est désormais annoncé en direct

Simple : Faire un clic droit sur un objet pour le mettre dans la fenêtre d'échange était complètement silencieux, et son entrée dans le menu des sacs restait inchangée. Sku dit maintenant « <objet> dans l'emplacement d'échange N » ou « emplacement d'échange N vidé », y compris pour le côté de votre partenaire d'échange, ce qui n'était auparavant ni visible ni audible. Les listes « Vos objets » et « Objets du partenaire » se rafraîchissent d'elles-mêmes ; l'entrée « Actualiser » n'est plus nécessaire pour cela. Tant qu'un objet est dans l'échange, son entrée de sac porte un préfixe « en échange », prononcé avant le nom de l'objet, et à l'instant où le marqueur apparaît sur l'entrée où vous vous trouvez, Sku dit seulement « en échange » - il ne vous relit pas tout le nom de l'objet. Et après un échange terminé, l'objet que vous avez donné disparaît désormais de façon fiable de la liste des sacs, au lieu de seulement parfois.

Technique : La mécanique existante de vente et de banque ne pouvait pas s'appliquer ici : mettre un objet dans la fenêtre d'échange ne le retire pas du sac. Le client se contente de poser isLocked sur l'emplacement (le propre ContainerFrame de Blizzard réagit à ITEM_LOCK_CHANGED en désaturant simplement l'icône), aucun BAG_UPDATE n'est donc déclenché et il n'y avait rien à quoi les gestionnaires conditionnés puissent réagir. Les objets ne quittent les sacs que lorsque l'échange S'ACHÈVE - et ce BAG_UPDATE fait la course avec le CheckFrames de TRADE_CLOSED, alors que Sku.tBagPostAction avait déjà expiré 2,5 s après le clic droit. Perdre cette course était le symptôme « parfois il reste dans la liste ». Nouveau :

Sku.tBagSettleWindow, une seconde barrière plus étroite dans SkuCore:BAG_UPDATE_DELAYED, armée pour 3 s par TRADE_CLOSED, qui pilote le SkuBagIdleRefresh SILENCIEUX - pas la confirmation parlée, puisque aucun curseur n'est ancré sur l'objet échangé - plus un repli d'1 s pour la course inverse.

TRADE_PLAYER_ITEM_CHANGED et TRADE_TARGET_ITEM_CHANGED sont enregistrés pour la première fois, dédoublonnés par emplacement selon l'objet et la quantité et conditionnés à TradeFrame:IsVisible pour que les salves de fin d'échange restent silencieuses. Les deux partagent SkuCore:TradeMenuRefresh, une reconstruction silencieuse temporisée. Le marqueur « en échange » est un préfixe : dans une liste de sac il suit le numéro de position, dans la liste « tous les objets » il est ajouté après le tri (comme le préfixe « nouveau »), de sorte que le marqueur transitoire ne déplace jamais un objet hors de sa place alphabétique. L'annonce est accrochée au réancrage silencieux de SkuBagIdleRefresh, qui compare le marqueur sur l'entrée ciblée avant et après la reconstruction et ne parle que s'il est apparu - auto-dédoublonnant, puisque les rafraîchissements suivants le voient des deux côtés.

SkuCore:TradePartnerName est désormais la source unique du nom du partenaire, y compris pour l'annonce existante de l'argent échangé.

Combat : Sku annonce quand un sort a été interrompu

Simple : Sous Hostile -> Incantation, « Annoncer les interruptions » existe désormais séparément pour votre cible actuelle et pour tous les ennemis - exactement comme les annonces d'incantation elles-mêmes. Cela permet d'entendre les interruptions sur votre propre cible sans entendre toutes celles du raid. Quand vous ou votre familier interrompez, vous entendez « Vous avez interrompu le sort » avec le nom du sort ; quand quelqu'un de votre groupe interrompt, « sort interrompu ». Seule l'annonce pour votre cible actuelle est activée par défaut, tous les ennemis reste à activer vous-même ; celles et ceux qui l'avaient désactivée restent tranquilles.

Technique : SPELL_INTERRUPT depuis le journal de combat via le nouvel événement du répartiteur SKU_SPELL_INTERRUPT. L'ancien interrupteur unique outputInterrupts est remplacé par yourTargetInterrupts et allEnemiesInterrupts ; seul

yourTargetInterrupts est prérempli depuis l'ancienne valeur là où c'était nil, allEnemiesInterrupts démarre désactivé. Avec l'interrupteur global désactivé, une interruption n'est annoncée que si le destGUID de l'incantation interrompue est votre cible actuelle ; avec les deux activés, elle n'est toujours annoncée qu'une fois. « Vous » signifie une affiliation de source MINE, ce qui couvre votre propre familier (le Blocage de sort du chasseur corrompu, par exemple).

Combat : les annonces d'incantation peuvent être limitées aux sorts interruptibles

Simple : Nouveau réglage « N'annoncer que les sorts interruptibles » (désactivé par défaut) sous Hostile -> Incantation. Une fois activé, Sku ne signale plus les incantations qu'une interruption n'affecterait de toute façon pas.

Technique : La valeur de retour notInterruptible d'UnitCastingInfo et d'UnitChannelInfo est une donnée morte sur le client Anniversary 2.5.6 - elle revenait nil à chaque incantation tandis que la 9e valeur de retour livrait le bon spellId ; la propre barre d'incantation de Blizzard force l'indicateur à false pour BC et antérieur. L'interruptibilité provient donc d'une liste statique, nouvelle sous SkuDB/uninterruptibleCasts.lua, importée de ClassicCastbars (licence MIT). Elle couvre l'ancien monde ; des tables de fusion SkuTbcAdditions vides sont prêtes pour nos propres trouvailles TBC. La correspondance se fait par spellID, par nom localisé, ou par npcID plus nom.

Ciblage souple : « si aucune cible n'est verrouillée » atteint enfin réellement le client

Simple : Le réglage qui détermine si le ciblage souple est utilisé pour interagir n'a jamais rien fait. La touche d'interaction ne se souciait pas de savoir si vous aviez une cible verrouillée - un cadavre ou une chaise à côté captait l'appui de touche quand même. C'est corrigé, et corrigé dans le client plutôt que dans une émulation. À noter : ces réglages du client sont verrouillés pendant le combat. Sku les maintient donc au repos dans l'état où la suppression est déjà en place, au cas où quelque chose vous saute dessus et que vous ne le cibliez qu'une fois le combat engagé. Deux cas limites subsistent : si vous tuez votre cible, l'état persiste jusqu'à votre prochain changement de cible - pendant ce laps de temps les objets proches ne sont pas adressables (le cadavre que vous avez ciblé se pille normalement). Et si vous entrez en combat en tenant une cible non attaquable, le ciblage souple reste actif pour ce combat. Cela demanderait une écriture pendant le combat et ne peut pas être résolu depuis un addon.

Technique : Les 32 CVars SoftTarget sont toujours enregistrées dans la 2.5.6 (repérées dans WowClassic.exe) - le client n'est donc pas en cause. Les deux branches de l'ancien if/else écrivaient SoftTargetMatchLocked = 0, du code mort identique dans la v32.31 et dans la v41.04 ; le réglage n'a jamais quitté Lua. La 41.05 a ensuite écrit 1, mais a fusionné l'option 2 dans l'option 1, et SoftTargetWithLocked - la CVar qui régit réellement ceci - n'a jamais été écrite par aucune version de Sku. Mesuré sur la 2.5.6.68575, hors combat, un cadavre et une créature vivante à portée, touche d'interaction pressée avec la créature verrouillée comme cible : WithLocked 0 agit sur la créature et ignore le cadavre, 1 et 2 pillent tous deux le cadavre. Seul 0 supprime donc le ciblage souple tant qu'une cible est verrouillée, et il n'existe pas de valeur intermédiaire « seulement si attaquable ». L'option correspond désormais directement à cela : 0 (toujours) -> WithLocked 2, MatchLocked 0 ; 1 (si aucune cible verrouillée) -> WithLocked 0, MatchLocked 1 ; 2 (aucune cible attaquable) -> MatchLocked 2, avec WithLocked basculé entre 0 et 2 sur PLAYER_TARGET_CHANGED. L'état de repos est 0, parce que WithLocked n'agit que tant qu'une cible est verrouillée - s'y reposer ne coûte rien et couvre le seul cas que les CVars ne pourraient autrement jamais atteindre. 2 n'est désormais écrit que pour le seul état qui a véritablement besoin du ciblage souple AVEC une cible verrouillée : une cible que vous ne pouvez pas attaquer - un cadavre que vous pillez, un PNJ à qui vous parlez, un joueur que vous suivez. Supprimés : le ticker de SkuMob qui réécrivait SoftTargetInteract quatre fois par seconde, son doublon dans PLAYER_TARGET_CHANGED et l'état fantôme interactTempDisabled - cette émulation ne pouvait pas fonctionner du tout, puisque les CVars sont protégées en combat. tSetSoftTargetCVar vérifie maintenant chaque écriture en relisant la CVar (comparaison numérique, pour qu'un « 15.000000 » normalisé ne soit pas un faux échec) et réarme la relecture sur

PLAYER_REGEN_ENABLED lorsqu'une écriture est refusée, au lieu de mettre en cache une valeur qui n'a jamais abouti. Une écriture en combat journalise « softTarget refused ... {want=3, got=0} » et lève ADDON_ACTION_BLOCKED.

Menu : ENTRÉE continuait d'agir dans le menu invisible après sa fermeture

Simple : Après avoir sélectionné un point de passage - et dans des cas semblables - le menu se fermait correctement, mais chaque nouvel appui sur ENTRÉE cliquait encore dans le menu invisible : le son de navigation se jouait, la sélection se relançait (« nouvelle navigation »), et ENTRÉE n'ouvrait plus la fenêtre de discussion. En plus de cela, le menu pouvait rester bloqué ouvert si une touche de déplacement était enfoncée au même instant : le point de passage était posé, mais le menu restait ouvert et gardait les touches capturées. Les deux sont corrigés.

Technique : Deux causes distinctes. Premièrement, OnPostSelect continue après l'action qui a fermé le menu : il repointait currentMenuPosition sur le selectTarget - écrasant la remise à la racine faite par la fermeture - et déclenchait son OnEnter ; le OnEnter générique réarmait alors sans condition les liaisons de substitution SKU_KEY_MENULEFTCLICK sur le bouton sécurisé. Les liaisons de substitution sur un cadre MASQUÉ sont pleinement actives, ENTRÉE était donc ressuscitée juste après que OnHide l'avait effacée. Désormais le OnEnter générique n'arme les touches de clic que tant que le bouton sécurisé est affiché, et OnPostSelect s'arrête avant le refocus final quand le menu n'est plus ouvert (le menu de combat sans affichage est exempté via combatMenuActive). Deuxièmement, CloseMenu passe par le même gestionnaire de bascule que l'ouverture, dont les deux barrières de report en cas de déplacement annulaient silencieusement la fermeture - l'une lit les indicateurs de déplacement en direct, l'autre le SkuCore.isMoving mis en cache, ce qui explique pourquoi un chemin passait et l'autre avortait. Les demandes de fermeture contournent maintenant ces barrières ; Hide et ClearOverrideBindings sont sans danger pendant le déplacement, et une fermeture réussie atteint de nouveau les remises à zéro d'openMenuAfter. Par précaution supplémentaire, le ticker de réouverture désarme les indicateurs qui ne peuvent de toute façon jamais se déclencher et ne relance la mise en place de la navigation que lorsque le menu est visiblement ouvert mais insensible aux touches (nouveau : SkuOptions.menuNavKeysBound).

Avertissement de collision pendant la marche automatique vers la cible

Simple : L'avertissement indiquant que vous marchez dans quelque chose ne s'armait que tant que vous mainteniez vous-même une touche de déplacement. Avec la fonction « Interagir avec la cible » du jeu, le moteur vous y conduit sans qu'aucune touche ne soit maintenue - et là, vous pouviez vous coincer sur un obstacle sans vous en rendre compte. Ces déplacements sont désormais couverts eux aussi.

Technique : Un nouvel indicateur EngineMoving, issu de PLAYER_STARTED_MOVING et PLAYER_STOPPED_MOVING, arme l'avertissement basé sur les coordonnées pour les déplacements pilotés par le moteur également. PLAYER_STOPPED_MOVING ne se déclenche pas tant que vous poussez contre un mur - précisément le cas pour lequel l'avertissement existe (démontré par la purge de l'AutoRun périmé). La rotation pure, la chute et le suivi automatique sont exclus ; le suivi a son propre avertissement de collision.

Moins d'erreurs en instance et en raid

Simple : Deux sources d'erreurs qui produisaient des erreurs Lua constantes à l'intérieur des instances sont corrigées - perceptible sous forme de messages d'erreur qui n'apparaissent plus et de combats de boss plus calmes.

Technique : Premièrement, UnitPosition("player") renvoie nil à l'intérieur des instances, des raids et des champs de bataille, si bien que SkuNav:Distance renvoyait nil et que GetUnsortedAvailableQuestsTable plantait sur une concaténation à chaque QUEST_LOG_UPDATE (24 fois en une seule session de raid). La convention existante des 99999 mètres s'applique désormais : la quête reste listée et se trie en dernier. Deuxièmement, ClearOverrideBindings et SetOverrideBindingClick sont protégées en combat, et le pcall qui les entoure ne supprime pas l'événement ADDON_ACTION_BLOCKED (84 fois en un seul combat, plus des avertissements de limite d'exécution de BugGrabber). L'application de la liaison de touche d'AtlasLoot est maintenant sautée pendant le combat et retentée une fois via PLAYER_REGEN_ENABLED.

« Interrompre temporairement le suivi pendant une incantation » est réactivé pour un test

Simple : Ce réglage figurait dans le menu, mais sa fonction était désactivée depuis l'état amont de la v41.06. Il est réactivé, désactivé par défaut, afin d'établir si ce client permet ne serait-ce que de reprendre le suivi automatiquement. Si vous l'activez, attendez-vous à ce que le suivi ne revienne pas de lui-même après une incantation.

Technique : Les corps d'UnfollowOnCast et de FollowOnCast étaient mis en commentaire, pour une raison qui n'a jamais été consignée. Ils répondent maintenant à une question : ce client exécute-t-il FollowUnit depuis un contexte d'événement serveur, c'est-à-dire sans événement matériel, ou est-ce silencieusement bloqué ? Le canal de preuve est la sortie vocale AUTOFOLLOW_END/BEGIN et les lignes « followcast » dans SkuDebugLog, y compris une vérification d'état 0,5 s après l'appel. Corrigé en chemin : dans les gestionnaires CHANNEL_START/STOP, unitTarget et aUnitTarget ne concordaient pas, si bien que l'arrêt et la reprise du suivi ne se déclenchaient jamais pour les sorts à canalisation.

Courrier : après l'envoi d'une lettre, Sku relisait plus tard le texte saisi

Simple : Un certain temps après l'envoi d'une lettre, Sku se mettait à répéter les caractères qui avaient été tapés - parfois longtemps après la fermeture de la boîte aux lettres. Deux bugs indépendants en étaient la cause. Premièrement, une fois que vous aviez ouvert une boîte aux lettres, elle comptait comme ouverte en interne jusqu'au prochain /reload, si bien que chaque courrier arrivant plus tard reconstruisait le menu et réannonçait l'entrée courante - où que vous soyez dans le menu. Si vous vous trouviez sur un champ de lettre, cette entrée était le texte que vous veniez de taper. Deuxièmement, la lecture caractère par caractère pendant la saisie ajoutait chaque caractère à la fin de la file de parole : si vous tapez plus vite que la voix ne parle, cela accumule un retard, et fermer le champ de saisie n'y mettait pas fin - la lecture continuait simplement.

Désormais la lecture suit votre frappe : si elle prend trop de retard, ce qui n'a pas encore été prononcé est abandonné au lieu de traîner. « Envoyé » et la confirmation d'un champ arrivent immédiatement, plus derrière tout ce que vous avez tapé. Et une mise à jour de la boîte de réception n'a d'effet que tant que la boîte aux lettres est réellement ouverte et que vous êtes dans la branche Courrier du menu. Deux petites choses au même endroit : le champ de collage exécutait son action OK n fois à la n-ième ouverture, et les deux champs de saisie de Sku libèrent maintenant le clavier de façon fiable quand ils se ferment.

Technique : MailboxOpenFlag était posé dans MAIL_SHOW mais n'était effacé nulle part ; MAIL_CLOSED l'efface désormais. MAIL_INBOX_UPDATE n'arrive pas seulement quand une boîte aux lettres est ouverte - le serveur l'envoie aussi lorsque du courrier arrive plus tard - et il filait sans contrôle dans le OnUpdate générique

(SkuZOptions/templates.lua). Ce n'est pas un rafraîchissement discret : il jette les enfants du niveau courant, les reconstruit et appelle VocalizeCurrentMenuName. En plus de l'indicateur, nous vérifions maintenant MailFrame:IsVisible() et remontons la chaîne des parents jusqu'au nœud contributeur Local L["Mail"] - ce n'est que là qu'une reconstruction est un rafraîchissement et non une intrusion. L'appel forcé à SlashFunc sans position de menu ne subsiste que pour le cas authentique de l'ouverture de la boîte aux lettres ; un événement d'arrière-plan ne doit jamais forcer l'ouverture du menu.

La lecture de frappe de la v42.08 ajoute au lieu d'écraser à dessein, sans quoi une frappe rapide avale des caractères - mais elle n'avait aucun plafond. À environ 0,4 s par caractère contre quatre à cinq frappes par seconde, la file croît sans limite, et le défilement pousse ce retard dans C_VoiceChat.SpeakText à environ 100 entrées par seconde, là où Sku ne peut plus le rattraper. D'où deux nouvelles méthodes dans SkuVoice-1.0, GetBttsQueueDepth et TrimBttsQueue(aKeep) : seul ce qui ATTEND encore dans la file propre à Sku est abandonné, jamais ce qui a déjà été transmis - c'est pourquoi cela ne peut pas couper un mot en deux, et pourquoi c'est légitime même avec « ne jamais réinitialiser les files », un réglage qui concerne l'interruption d'une parole en cours. Le plafond est de trois caractères en attente. Le retard est abandonné avant l'exécution du rappel OK, et MAIL_SEND_SUCCESS ainsi que la confirmation de champ dans MailEditor parlent maintenant en écrasant plutôt qu'en ajoutant. EditBoxPasteShow attachait son OnEnterPressed via HookScript à CHAQUE appel

fois à la n-ième ouverture ; il vit désormais dans la branche exécutée une seule fois à la création. Les deux champs de saisie appellent ClearFocus lorsqu'ils se masquent, parce que le champ est un petit-enfant du cadre masqué et que le focus clavier resterait sinon sur une zone d'édition invisible.

Niveau d'objet : tous les objets étaient annoncés avec le même chiffre

Simple : Le niveau d'objet dans les descriptions d'objets était faux depuis qu'il existe. Il n'appartenait pas du tout à l'objet que vous étiez en train de lire - il appartenait au dernier objet consulté quelque part dans Sku, généralement des heures plus tôt, et il le restait pour toute la session. C'est pourquoi il lisait le même chiffre sur chaque objet, et pourquoi c'était si souvent un 1 : une clé ou un objet de quête, le genre de chose qui est consultée tôt et qui est réellement de niveau d'objet 1. Un /reload n'aidait pas, car le mauvais chiffre n'était pas une donnée périmée que l'on rafraîchit - c'était simplement le mauvais objet qui était interrogé. Ce même mauvais objet décidait aussi de la qualité ajoutée aux noms d'objets. Les deux sont corrects maintenant. Le niveau d'objet n'est en outre annoncé que pour l'équipement que vous pouvez réellement porter : les clés, les objets de quête, les composants et les consommables sont tous de niveau d'objet 1 par définition, cette ligne ne disait donc rien à leur sujet et elle a disparu. Elle a également disparu des endroits où elle n'avait jamais sa place, comme les descriptions de buffs et les résistances de la feuille de personnage.

Technique : Le GetItem() d'une infobulle est un cache rémanent, pas une description de ce que le cadre affiche : ClearLines() ne le réinitialise pas, et un SetX() qui échoue à remplir non plus - un objet non mis en cache, le conteneur de banque -1, ou toute infobulle de sort, d'aura ou de caractéristique qui n'a aucun objet. SkuScanningTooltip est créée une seule fois à PLAYER_LOGIN et n'est jamais réattribuée, si bien que son GetItem() continue de répondre avec le dernier objet qui s'est jamais résolu à travers elle. En plus de cela, TooltipLines_helper lisait le lien depuis SkuScanningTooltip quelles que soient les régions qu'on lui avait passées, et la plupart des appelants lui passent celles de GameTooltip. Deux défauts plus petits aggravaient le tout : la deuxième valeur de retour de GetSpell(), qui n'est pas un lien, était affectée à ItemLink et transmise à GetDetailedItemLevelInfo ; et un objet non mis en cache produit un lien VIDE, qui est vrai en Lua et passait tout droit à travers la garde « if ItemLink then ». La résolution vit désormais dans SkuUtil - TooltipFirstLine, TooltipItemLink, ItemQualityString, ItemLevel - et ne fait confiance à GetItem() que lorsque le nom qu'il rapporte correspond à la première ligne affichée de l'infobulle, qui est le seul point de vérité disponible. Les trois blocs dupliqués dans LocalMenu et utilities s'y ramènent ; tScanTooltipRegions résout lui-même le lien, la qualité et le niveau d'objet et renvoie le lien validé, de sorte que les appelants en aval obtiennent celui que le texte décrit. La condition « équipable » lit equipLoc depuis GetItemInfo et se rabat sur

C_Item.GetItemInventoryTypeByID tant que le cache d'objets est encore froid.

Français : Sku est désormais disponible en français

Simple : Sku peut désormais être utilisé en français - testé sur un client français. Environ 95 pour cent des textes de menu et d'annonce sont traduits (3063 entrées sur 3220), et tout le reste retombe automatiquement sur l'anglais, il n'y a donc jamais de trou ni d'entrée vide. Deux choses qui manquaient encore ont été ajoutées : les noms de routes et de points de passage de la chaîne de traduction ont leur propre table française de 153 entrées, dont les noms de lieux sont importés d'une capture faite sur un vrai client français plutôt que devinés, et les 158 endroits où du texte est conservé de façon bilingue dans le code portent maintenant aussi une version française. Les noms du JEU - créatures, quêtes, objets, éléments du décor - sont importés en bloc depuis Questie, ce sont donc les propres chaînes françaises de Blizzard et non des traductions automatiques, et elles correspondent à ce qu'affiche un client français, ce qui est précisément ce qui garde les noms de routes et de points de passage cherchables. Les noms de zones viennent directement du client. La sortie vocale n'existe toujours qu'en allemand et en anglais ; toutes les autres langues de client utilisent les fichiers audio anglais.

Pour les joueurs allemands et anglais, rien ne change dans le texte - mais l'utilisation de la mémoire, si : Sku ne construit plus que les tables de noms de sa propre langue, plus l'anglais comme repli, au lieu de toutes les construire systématiquement.

Technique : Sku.Locs est maintenant une liste ORDONNÉE de trois locales, et les noms de routes empaquetés dans SkuNav:LoadDefaultMapData sont découpés par position d'après elle au lieu de l'être par un string.match avec un « (.+) » gourmand de part et d'autre du séparateur. Cette ancienne analyse n'était pas seulement limitée à deux locales : « .+ » est gourmand, donc avec trois champs « a », « b » et « c » les deux premiers seraient arrivés comme un unique champ anglais et seul le dernier comme le champ allemand. Les fichiers ayant moins de champs que de locales restent valides et retombent sur enUS. Sku.LocAudio sépare la locale AUDIO de la locale de données, et deux bugs préexistants sont apparus et ont été corrigés en chemin : SkuAudioFileIndexIntegrated[Sku.Loc] n'était pas protégé et indexait nil sur toute langue de client sans sous-table propre, et GetAudiodata n'avait aucun repli sur la casse alors que l'index enUS stocke ses identifiants en minuscules et que les appelants les passent avec une majuscule - ce qui explique pourquoi l'annonce d'entrée et de sortie de zone est silencieusement morte sur les clients anglais. ChunkLoader a désormais une barrière de locale : auparavant il construisait TOUS les blocs enregistrés quelle que soit la langue du client, si bien qu'un client allemand matérialisait aussi les tables de noms anglaises complètes et inversement. La règle est maintenant « locale active plus enUS », avec objectLookup.deDE structurellement exempté parce que l'objectLookup anglais est DÉRIVÉ de son ensemble de clés - le filtrer naïvement aurait laissé les clients anglais sans aucun nom d'élément du décor. Les quatre fusions .deDE codées en dur sont devenues SkuDBMergeLocales, ce qui corrige au passage un bug latent : un client français aurait obtenu les noms TBC de base mais silencieusement aucun nom WotLK. Après la fusion, les trous de la locale active sont comblés depuis enUS (Questie n'a pas de nom français pour environ 2700 identifiants que Sku connaît) ; délibérément une étape de construction plutôt qu'une métatable __index, parce que pairs() ne traverse pas __index et que plusieurs consommateurs ÉNUMÈRENT ces tables au lieu de les indexer - une métatable aurait corrigé les recherches et laissé discrètement les menus incomplets. deDE et enUS sont exclus du comblement pour que la construction allemande reste identique octet pour octet (vérifié, 37 jeux de données sur 37). Le fichier de locale lui-même est Sku/locales/frFR.lua, généré à partir de fichiers partiels : les entrées non traduites conservent leur valeur anglaise, la table est donc toujours complète et du Lua valide, et AceLocale renvoie de toute façon nil pour de/en - là, le fichier ne coûte qu'une analyse et rien d'autre. Les espaces de début et de fin sont porteurs de sens dans cette table mais ne survivent pas à un aller-retour TSV, ils sont donc réappliqués depuis chaque original anglais pendant l'assemblage, et le validateur contrôle la chaîne FINALE pour le nombre de marqueurs, les espaces en bordure et le nombre de points-virgules. Pour tester sans un second client, il y a /skudebug locale <loc|off>, qui force la locale de DONNÉES et persiste ; elle s'applique à ADDON_LOADED plutôt qu'à PLAYER_LOGIN, parce que la construction différée des routes (mesurée à 1,87 s contre 2,01 s pour PLAYER_LOGIN) décide déjà quels champs de noms sont matérialisés. Trois erreurs bloquantes sur un client forcé en français sont corrigées : maps.lua est une table simple, non découpée en blocs, et ne portait AreaName_lang/Name_lang que pour deDE et enUS - la locale manquante est maintenant remplie une fois à ADDON_LOADED depuis C_Map (GetAreaInfo et GetMapInfo ; 2819 entrées, dont 1672 issues du client), ce qui donne de vrais noms Blizzard sans avoir à livrer de données de noms de zones ; SkuDB.SpellDataTBC imbrique ses locales À L'INTÉRIEUR de chaque entrée, si bien que rien ne les a jamais localisées et que onze endroits levaient une erreur en frFR - la locale active pointe maintenant sur la sous-table anglaise par référence (pas de copie, pas de chaînes supplémentaires), ce qui suffit pour des noms utilisés surtout comme clés de correspondance d'auras ; et SkuDB.objectResourceNames est indexé par nom d'élément du décor et par locale et était lu sans protection pendant la construction du cache de points de passage - un repli anglais y aurait été faux (il n'aurait jamais correspondu et aurait silencieusement transformé chaque minerai et chaque plante en point de passage ordinaire), l'ensemble est donc dérivé à travers les identifiants d'objets à la place.

Toute la branche française a depuis été jouée de bout en bout sur un client français et fonctionne : menus, noms et routes s'affichent en français, et là où aucune traduction n'existe, le repli anglais prend le relais sans trou. La table de routes routeOverrides_frFR.lua et le troisième argument sur chaque site Sku.deEn en font partie. Comme zhCN et ruRU tournent avec Sku.Loc = enUS, la même passe les couvre aussi.

Page de téléchargement : le titre annonçait une version périmée

Simple : Sur la page de téléchargement, le titre de l'addon principal indiquait encore « Version 42.06 » alors que le lien en dessous pointait correctement sur la 42.10. Comme les utilisateurs de lecteur d'écran naviguent par titres, la première chose annoncée dans cette section était la mauvaise version - on aurait cru que la page servait un Sku obsolète. Le titre et le lien concordent désormais.

Technique : release.ps1 réécrivait le href et le texte du lien à chaque publication mais jamais le titre, qui était donc figé depuis la 42.06 (17/07/2026) sur quatre publications. Le titre fait maintenant partie de Set-DocsSkuLink et ne peut plus dériver. Le zip lié était correct depuis le début (vérifié : Sku-42.10.zip contient « ## Version: 42.10 »).

Balises : un son distinct pour le dernier point de passage d'une route

Simple : Il existe désormais un troisième son de balise à côté de ceux des petites et des grandes balises : le son de la dernière balise d'une route. Il ne se joue que pour le dernier point de passage d'une méta-route, vous pouvez donc entendre si le point vers lequel vous marchez est la destination ou seulement une étape de plus. Le réglage se trouve sous Moniteur -> Balise, juste en dessous des sons de petite et de grande balise, et sa valeur par défaut est Balise 3. Suivre un point de passage isolé est inchangé - cela utilise toujours le son petit ou grand selon la taille du point de passage.

Technique : Nouveau réglage de profil SkuNav beaconSoundSetLast (« Beacon 3 » par défaut), déclaré dans le schéma SkuNav et ajouté à la liste blanche explicite des nœuds du sous-menu Balise dans SkuCore/aq.lua - cette liste n'est pas la table args complète, un nouveau nœud d'option doit donc y être nommé aussi, sans quoi il n'apparaît jamais dans le menu. GetBeaconSoundSetName prend un deuxième argument et renvoie le jeu de la dernière balise lorsqu'il est vrai, laissant intact le comportement étroit/large et les appelants à un seul argument (la balise de panique, le hook TomTom). Le nouveau SkuNav:IsLastMetaRouteWp compare le NOM du point de passage à la dernière entrée de metapathFollowingMetapaths[target].pathWps plutôt que de lire metapathFollowingCurrentWp, parce que SelectWP s'exécute avant que cet index ne soit avancé - un test sur l'index serait décalé d'un cran. Passer par le nom couvre aussi le saut en avant avec MoveToWp et le départ en route inverse, qui posent tous l'état de la méta-route avant d'appeler SelectWP.

De meilleurs réglages par défaut pour les nouveaux joueurs

Simple : Installer Sku à neuf part désormais d'une configuration utilisable au lieu de vous laisser trouver l'essentiel d'abord. Quelques touches sont préassignées, la discussion est répartie de façon sensée, et certaines annonces sont actives dès le départ - globalement la façon dont les joueurs expérimentés le configurent de toute manière. Chacun de ces réglages par défaut reste à sa place habituelle dans le menu et peut être modifié comme toujours. Les profils et personnages existants ne sont pas touchés : un réglage par défaut ne s'applique que là où rien n'a jamais été enregistré. Pour récupérer les nouvelles touches, utilisez Assignations de touches Sku -> tout réinitialiser - attention, cela réinitialise vraiment TOUTES les touches, y compris celles que vous avez assignées vous-même.

Technique : Modifications de valeurs par défaut uniquement, dans

SkuZOptions/SkuKeyBinds.lua (skuDefaultKeyBindings), SkuChat/Core.lua et Options.lua (ChatFrameDefaultTabs plus le jeu d'onglets par défaut), SkuCore/Options.lua (SkuCore.defaults) et SkuCore/aqCombat.lua (aqCombatOnLogin). Les valeurs enregistrées survivent, parce que chaque endroit n'écrit que sur nil et qu'AceDB ne remplit les valeurs par défaut que dans les trous. Deux vrais changements de comportement les accompagnent : le lieur dans SkuNav:CreateSkuNavMain n'appliquait jamais que .key, si bien qu'une SECONDE touche assignée par l'utilisateur ne faisait absolument rien pour toutes les actions de navigation (SkuKeyBindsMatchKey l'avait compris depuis toujours) - il passe maintenant par SkuKeyBindsGetKeys et lie les deux. Et comme SkuChat émet un message une fois par onglet où le type est activé, un type de message ayant son propre onglet doit être coupé dans tous les autres onglets, sinon il est lu deux fois.

Hôtel des ventes : acheter plusieurs fois le même objet fonctionne de nouveau

Simple : Acheter plus d'une enchère du même objet achetait la première puis s'arrêtait sur « erreur interne des enchères » ; le second achat n'était jamais proposé à nouveau. Les deux sont corrigés - les achats suivants aboutissent, et le message d'erreur intempestif du serveur n'est plus lu à voix haute pendant qu'une recherche est en cours.