Repository navigation
Expand file tree
/
Copy pathWCS_BrainPetAI.lua
More file actions
2040 lines (1786 loc) · 74.9 KB
/
Copy pathWCS_BrainPetAI.lua
File metadata and controls
2040 lines (1786 loc) · 74.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
if WCS_Brain and WCS_Brain.ENABLED == false then return end
-- ============================================================================
-- SOPORTE EXTENSIBLE PARA NUEVAS MASCOTAS Y HABILIDADES
-- ============================================================================
-- (Moved below PetAI initialization)
--[[
WCS_BrainPetAI.lua v6.7.1
Sistema de IA Inteligente para Mascotas de Warlock
Caracteristicas:
- Mascotas menores: Imp, Voidwalker, Succubus, Felhunter, Felguard
- Demonios mayores: Infernal, Doomguard
- Sistema de Enslave con auto-reenslave
- Soporte party/raid para Fire Shield
Compatible con Lua 5.0 (Turtle WoW)
]]
WCS_BrainPetAI = WCS_BrainPetAI or {}
local PetAI = WCS_BrainPetAI
-- Permite registrar lógica personalizada para futuras mascotas o demonios especiales
PetAI.CustomPetLogic = {}
-- Registrar una nueva mascota personalizada
function PetAI:RegisterCustomPet(petType, logicFunc)
if not petType or type(logicFunc) ~= "function" then return end
self.CustomPetLogic[petType] = logicFunc
end
-- Ejemplo de plantilla para una nueva mascota (puedes copiar y adaptar)
--[[
PetAI:RegisterCustomPet("Fel Imp", function(self)
-- Lógica especÃÂfica para Fel Imp
-- Ejemplo: priorizar Firebolt y Dispel
local abilities = self:ScanPetAbilities()
for i, ab in ipairs(abilities) do
if ab.name == "Firebolt" and not self:IsOnCooldown("Firebolt") then
self:SetCooldown("Firebolt", 2)
return self:ExecuteAbility("Firebolt")
end
if ab.name == "Dispel Magic" and not self:IsOnCooldown("Dispel Magic") then
self:SetCooldown("Dispel Magic", 8)
return self:ExecuteAbility("Dispel Magic")
end
end
return false
end)
]]
-- ============================================================================
-- SISTEMA DE EVENTOS INTERNO PARA MASCOTAS (modular, extensible)
-- Permite registrar y disparar callbacks para eventos clave (daño, muerte, cambio de estado, etc)
-- Uso: PetAI:RegisterEvent(event, callback), PetAI:TriggerEvent(event, ...)
-- ============================================================================
PetAI._eventCallbacks = {}
-- Registra un callback para un evento interno de la IA de mascota
function PetAI:RegisterEvent(event, callback)
if not event or type(callback) ~= "function" then return end
self._eventCallbacks[event] = self._eventCallbacks[event] or {}
table.insert(self._eventCallbacks[event], callback)
end
-- Dispara un evento interno, llamando a todos los callbacks registrados
function PetAI:TriggerEvent(event, ...)
if not event or not self._eventCallbacks[event] then return end
local args = arg
for i, cb in ipairs(self._eventCallbacks[event]) do
if cb then
local ok, err = pcall(function() cb(unpack(args)) end)
if not ok and self.debug then
self:DebugPrint("[EventSystem] Error en callback de '"..event.."': "..tostring(err))
end
end
end
end
-- Ejemplo de integración: disparar eventos en situaciones clave
-- (Puedes expandir esto en los métodos de combate, muerte, cambio de estado, etc)
PetAI.VERSION = "6.7.1" -- Mascotas inteligentes mejoradas + Sistema de ejecución corregido
PetAI.ENABLED = true
PetAI.debug = false
PetAI.lastUpdate = 0
PetAI.updateInterval = 0.5
PetAI.isThinking = false -- Guard para prevenir race conditions
PetAI.cooldowns = {}
PetAI.Enslave = {
enabled = true,
lastDemonName = nil,
enslaveTime = 0,
enslaveDuration = 300,
warningShown = false,
isEnslaved = false,
needReenslave = false
}
PetAI.Config = {
aggressiveMode = false,
autoSacrifice = true,
autoFireShield = true,
emergencyThreshold = 25,
fireShieldRange = 30, -- Rango maximo para Fire Shield (yards)
fireShieldCooldown = 10, -- Cooldown entre aplicaciones de Fire Shield
voidwalkerSacrificeHP = 15, -- HP% del Voidwalker para auto-sacrificio
smartSacrifice = true -- Sacrificio inteligente cuando va a morir
}
-- ============================================================================
PetAI.currentMode = 1 -- 1=Agresivo, 2=Defensivo, 3=Soporte, 4=Guardián
-- Variables para modo Guardián
PetAI.GuardianTarget = nil -- Nombre del jugador a proteger
PetAI.GuardianLastCheck = 0
PetAI.GuardianCheckInterval = 0.5 -- Revisar cada 0.5 segundos
-- Configuración de comportamiento por modo
PetAI.ModeConfig = {
[1] = { -- Agresivo
name = "Agresivo",
attackPriority = "high", -- Prioridad de ataque
defensePriority = "low", -- Prioridad de defensa
supportPriority = "low", -- Prioridad de soporte
autoAttack = true, -- Atacar automáticamente
useOffensive = true, -- Usar habilidades ofensivas
useDefensive = false, -- Usar habilidades defensivas
useSupport = false, -- Usar habilidades de soporte
aggressiveMode = true -- Modo agresivo legacy
},
[2] = { -- Defensivo
name = "Defensivo",
attackPriority = "medium",
defensePriority = "high",
supportPriority = "medium",
autoAttack = true,
useOffensive = false,
useDefensive = true,
useSupport = true,
aggressiveMode = false
},
[3] = { -- Soporte
name = "Soporte",
attackPriority = "low",
defensePriority = "medium",
supportPriority = "high",
autoAttack = false,
useOffensive = false,
useDefensive = true,
useSupport = true,
aggressiveMode = false
},
[4] = { -- Guardián
name = "Guardian",
attackPriority = "high",
defensePriority = "high",
supportPriority = "high",
autoAttack = true,
useOffensive = true,
useDefensive = true,
useSupport = true,
aggressiveMode = false
}
}
-- Función para cambiar el modo de IA
function PetAI:SetMode(mode)
-- Convertir a numero si es string
if type(mode) == "string" then
mode = tonumber(mode)
end
if not mode or mode < 1 or mode > 4 then
self:Print("Modo inválido. Usa 1 (Agresivo), 2 (Defensivo), 3 (Soporte) o 4 (Guardián)")
return false
end
-- Si cambia a modo Guardián sin target asignado, avisar
if mode == 4 and not self.GuardianTarget then
self:Print("Modo Guardián activado. Usa /petguard [nombre] para asignar a quién proteger")
end
self.currentMode = mode
local config = self.ModeConfig[mode]
if not config then
self:Print("ERROR: Configuración de modo no encontrada")
return false
end
-- Actualizar configuración legacy
self.Config.aggressiveMode = config.aggressiveMode
-- Mensaje de confirmación
local modeNames = {
[1] = "|cFFFF0000Agresivo|r",
[2] = "|cFF00FF00Defensivo|r",
[3] = "|cFF00CCFFSoporte|r",
[4] = "|cFFFFD700Guardián|r"
}
self:Print("Modo de IA cambiado a: " .. modeNames[mode])
-- Disparar evento para otros módulos
if self.TriggerEvent then
self:TriggerEvent("MODE_CHANGED", mode, config.name)
end
return true
end
-- Función para obtener el modo actual
function PetAI:GetMode()
return self.currentMode or 1
end
-- Función para obtener el nombre del modo actual
function PetAI:GetModeName()
local config = self.ModeConfig[self.currentMode]
return config and config.name or "Desconocido"
end
-- Función helper para verificar si debe usar habilidades ofensivas
function PetAI:ShouldUseOffensive()
local config = self.ModeConfig[self.currentMode]
return config and config.useOffensive or false
end
-- Función helper para verificar si debe usar habilidades defensivas
function PetAI:ShouldUseDefensive()
local config = self.ModeConfig[self.currentMode]
return config and config.useDefensive or false
end
-- Función helper para verificar si debe usar habilidades de soporte
function PetAI:ShouldUseSupport()
local config = self.ModeConfig[self.currentMode]
return config and config.useSupport or false
end
-- Cache de Fire Shield para evitar spam
PetAI.FireShieldCache = {
lastTarget = nil,
lastTime = 0,
appliedTo = {} -- {unitName = timestamp}
}
function PetAI:Print(msg)
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cff9370DB[PetAI]|r " .. tostring(msg))
end
end
function PetAI:DebugPrint(msg)
if self.debug and DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cff9370DB[PetAI Debug]|r " .. tostring(msg))
end
end
function PetAI:GetPlayerHealthPercent()
local current = UnitHealth("player") or 0
local max = UnitHealthMax("player") or 1
if max == 0 then max = 1 end
return (current / max) * 100
end
function PetAI:GetPetHealthPercent()
if not UnitExists("pet") then return 0 end
local current = UnitHealth("pet") or 0
local max = UnitHealthMax("pet") or 1
if max == 0 then max = 1 end
return (current / max) * 100
end
function PetAI:GetPetManaPercent()
if not UnitExists("pet") then return 0 end
local current = UnitMana("pet") or 0
local max = UnitManaMax("pet") or 1
if max == 0 then max = 1 end
return (current / max) * 100
end
function PetAI:PetHasBuff(pattern)
if not UnitExists("pet") then return false end
if not pattern then return false end
local patternLower = string.lower(pattern)
for i = 1, 32 do
local texture = UnitBuff("pet", i)
if not texture then break end
if string.find(string.lower(texture), patternLower) then
return true
end
end
return false
end
function PetAI:HasImmolationAura()
return self:PetHasBuff("incinerate") or self:PetHasBuff("immolat")
end
function PetAI:ListPetBuffs()
if not UnitExists("pet") then
self:Print("No hay pet activo")
return
end
self:Print("Buffs del pet:")
local count = 0
for i = 1, 32 do
local texture = UnitBuff("pet", i)
if not texture then break end
count = count + 1
self:Print(" " .. tostring(i) .. ": " .. tostring(texture))
end
if count == 0 then
self:Print(" (ninguno)")
end
end
function PetAI:GetPetType()
if not UnitExists("pet") then return nil end
-- Metodo 1: UnitCreatureFamily (El mas confiable para WoW Vanilla/Turtle)
local family = UnitCreatureFamily("pet")
if family then
local famLower = string.lower(family)
if string.find(famLower, "imp") or string.find(famLower, "diablillo") then return "Imp" end
if string.find(famLower, "void") or string.find(famLower, "abisario") then return "Voidwalker" end
if string.find(famLower, "succub") or string.find(famLower, "súcubo") or string.find(famLower, "sucubo") then return "Succubus" end
if string.find(famLower, "felhunter") or string.find(famLower, "sabueso") then return "Felhunter" end
if string.find(famLower, "felguard") or string.find(famLower, "guardia vil") then return "Felguard" end
if string.find(famLower, "infernal") then return "Infernal" end
if string.find(famLower, "doomguard") or string.find(famLower, "apocalíptic") or string.find(famLower, "apocaliptic") then return "Doomguard" end
end
-- Metodo 2: Analisis de libro de hechizos (Fallback hiper robusto y bilingüe)
for i = 1, 10 do
local name = GetPetActionInfo(i)
if name then
local nameLower = string.lower(name)
if string.find(nameLower, "fire") or string.find(nameLower, "fuego") or string.find(nameLower, "pacto") then return "Imp" end
if string.find(nameLower, "torment") or string.find(nameLower, "sacrifi") or string.find(nameLower, "suffer") or string.find(nameLower, "sufrimi") then return "Voidwalker" end
if string.find(nameLower, "seduc") or string.find(nameLower, "lash") or string.find(nameLower, "latigazo") or string.find(nameLower, "soothing") or string.find(nameLower, "beso") then return "Succubus" end
if string.find(nameLower, "devour") or string.find(nameLower, "devorar") or string.find(nameLower, "spell lock") or string.find(nameLower, "bloqueo") or string.find(nameLower, "taint") or string.find(nameLower, "corrupta") then return "Felhunter" end
if string.find(nameLower, "intercept") or string.find(nameLower, "anguish") or string.find(nameLower, "angustia") or string.find(nameLower, "brutal") then return "Felguard" end
end
end
-- Metodo 3: Fallback estricto por nombre (ej. test env)
local petName = UnitName("pet")
if petName then
local pName = string.lower(petName)
if string.find(pName, "imp") then return "Imp" end
if string.find(pName, "void") then return "Voidwalker" end
if string.find(pName, "succ") then return "Succubus" end
end
return "Unknown"
end
function PetAI:IsEnslavedDemon()
if not UnitExists("pet") then return false end
local petType = self:GetPetType()
if petType == "Unknown" then
return true
end
return false
end
function PetAI:ClassifyAbility(name, texture)
if not name then return "unknown" end
local nameLower = string.lower(tostring(name))
if string.find(nameLower, "fire") then return "damage" end
if string.find(nameLower, "bolt") then return "damage" end
if string.find(nameLower, "blast") then return "damage" end
if string.find(nameLower, "strike") then return "damage" end
if string.find(nameLower, "cleave") then return "damage" end
if string.find(nameLower, "lash") then return "damage" end
if string.find(nameLower, "rain") then return "damage" end
if string.find(nameLower, "stun") then return "cc" end
if string.find(nameLower, "stomp") then return "cc" end
if string.find(nameLower, "fear") then return "cc" end
if string.find(nameLower, "seduc") then return "cc" end
if string.find(nameLower, "cripple") then return "debuff" end
if string.find(nameLower, "slow") then return "debuff" end
if string.find(nameLower, "curse") then return "debuff" end
return "utility"
end
function PetAI:ScanPetAbilities()
local abilities = {}
if not UnitExists("pet") then return abilities end
for i = 1, 10 do
local name, subtext, texture = GetPetActionInfo(i)
if name and name ~= "" then
local cat = self:ClassifyAbility(name, texture)
local ab = {}
ab.slot = i
ab.name = tostring(name)
ab.texture = tostring(texture or "")
ab.category = cat
table.insert(abilities, ab)
end
end
return abilities
end
function PetAI:GetMajorDemonTimeRemaining()
if WCS_BrainMajorDemons and WCS_BrainMajorDemons.GetTimeRemaining then
return WCS_BrainMajorDemons:GetTimeRemaining()
end
return nil
end
function PetAI:IsOnCooldown(spellName)
local cd = self.cooldowns[spellName]
if not cd then return false end
return (GetTime() - cd.start) < cd.duration
end
function PetAI:SetCooldown(spellName, duration)
self.cooldowns[spellName] = { start = GetTime(), duration = duration }
end
-- ============================================================================
-- NUEVAS FUNCIONES v6.7.1 - Sistema mejorado de ejecución
-- ============================================================================
-- Tabla de traduccion EN <-> ES para habilidades de mascota
-- Permite que la IA funcione con cliente en Ingles Y Espanol
PetAI.SpellAliases = {
["fire shield"] = "escudo de fuego",
["firebolt"] = "bola de fuego",
["phase shift"] = "cambio de fase",
["sacrifice"] = "sacrificio",
["torment"] = "tormento",
["suffering"] = "sufrimiento",
["consume shadows"] = "consumir sombras",
["seduction"] = "seduccion",
["lash of pain"] = "latigazo de dolor",
["soothing kiss"] = "beso calmante",
["lesser invisibility"] = "invisibilidad menor",
["spell lock"] = "bloqueo de hechizo",
["devour magic"] = "devorar magia",
["tainted blood"] = "sangre corrupta",
["intercept"] = "interceptar",
["anguish"] = "angustia",
["cleave"] = "golpe brutal",
["war stomp"] = "pisoton de guerra",
["cripple"] = "lisiar",
["rain of fire"] = "lluvia de fuego",
["immolation"] = "inmolacion",
}
PetAI.AbilityTextures = {
["fire shield"] = "Spell_Fire_FireArmor",
["firebolt"] = "Spell_Fire_FireBolt",
["phase shift"] = "Spell_Shadow_AuraOfDarkness",
["sacrifice"] = "Spell_Shadow_SacrificialShield",
["torment"] = "Spell_Shadow_GatherShadows",
["suffering"] = "Spell_Shadow_BlackPlague",
["consume shadows"] = "Spell_Shadow_AntiShadow",
["seduction"] = "Spell_Shadow_MindSteal",
["lash of pain"] = "Spell_Shadow_Curse",
["soothing kiss"] = "Spell_Shadow_SoothingKiss",
["lesser invisibility"] = "Spell_Magic_LesserInvisibilty",
["spell lock"] = "Spell_Shadow_MindRot",
["devour magic"] = "Spell_Nature_Purge",
["tainted blood"] = "Spell_Shadow_LifeDrain",
["intercept"] = "Ability_Rogue_Sprint",
["anguish"] = "Spell_Shadow_GatherShadows",
["cleave"] = "Ability_Warrior_Cleave",
["war stomp"] = "Ability_WarStomp",
["cripple"] = "Spell_Shadow_Cripple",
["rain of fire"] = "Spell_Shadow_RainOfFire",
["immolation"] = "Spell_Shadow_Immolation"
}
-- Diccionario Exclusivo de Casteo (Asegura la sintaxis y mayúsculas perfectas para CastSpellByName en ES)
-- Previene los fallos de aliasing (ej: que Firebolt devuelva "Machetazo" en vez de "Bola de Fuego")
PetAI.CastAliases = {
["fire shield"] = "Escudo de fuego",
["firebolt"] = "Bola de Fuego",
["blood pact"] = "Pacto de sangre",
["phase shift"] = "Cambio de fase",
["torment"] = "Tormento",
["consume shadows"] = "Consumir sombras",
["sacrifice"] = "Sacrificio",
["suffering"] = "Sufrimiento",
["lash of pain"] = "Latigazo de dolor",
["seduction"] = "Seducción",
["soothing kiss"] = "Beso tranquilizador",
["lesser invisibility"] = "Invisibilidad menor",
["spell lock"] = "Bloqueo de hechizo",
["devour magic"] = "Devorar magia",
["paranoia"] = "Paranoia",
["cleave"] = "Hender",
["intercept"] = "Interceptar",
["anguish"] = "Angustia",
["demonic frenzy"] = "Frenesí demoníaco",
["enslave demon"] = "Esclavizar Demonio"
}
function PetAI:GetExactCastName(spellName)
if not spellName then return nil end
local s = string.lower(spellName)
local locale = GetLocale()
if locale == "esES" or locale == "esMX" then
if self.CastAliases[s] then return self.CastAliases[s] end
end
-- Fallback al diccionario general (que itera aleatoriamente)
if WCS_SpellLocalization and WCS_SpellLocalization.LocalizeSpellName then
return WCS_SpellLocalization:LocalizeSpellName(spellName) or spellName
end
return spellName
end
-- Obtener slot de habilidad por nombre (bilingue EN/ES + Soporte Texturas Custom Servers)
function PetAI:GetPetAbilitySlot(spellName)
if not UnitExists("pet") or not spellName then return nil end
local s = string.lower(spellName)
-- 1. Preparar alias de idioma
local alias = self.SpellAliases[s] or ""
if alias == "" then
for en, es in pairs(self.SpellAliases) do
if es == s then alias = en break end
end
end
-- 2. Preparar textura (fallback maestro)
local targetTexture = self.AbilityTextures[s]
if not targetTexture and alias ~= "" then
targetTexture = self.AbilityTextures[alias]
end
if targetTexture then targetTexture = string.lower(targetTexture) end
-- 3. Escaneo del libro de macotas
for i = 1, 10 do
local name, _, texture = GetPetActionInfo(i)
if name then
local n = string.lower(name)
-- Match parcial de nombre (soporta sufijos como "(Rango 4)")
if string.find(n, s) or (alias ~= "" and string.find(n, alias)) then
return i
end
-- Match por textura (inmune a cualquier idioma o alteración de server custom)
if targetTexture and texture and string.find(string.lower(texture), targetTexture) then
return i
end
end
end
return nil
end
-- Verificar si la mascota tiene la habilidad
function PetAI:PetHasAbility(spellName)
return self:GetPetAbilitySlot(spellName) ~= nil
end
-- Verificar si se puede castear (habilidad existe + no en CD + suficiente mana)
function PetAI:CanCastPetAbility(spellName)
if not UnitExists("pet") or not spellName then return false end
local slot = self:GetPetAbilitySlot(spellName)
if not slot then
self:DebugPrint("[CanCast] " .. spellName .. " - NO ENCONTRADA")
return false
end
local start, duration, enable = GetPetActionCooldown(slot)
if start and duration and duration > 0 then
local remaining = duration - (GetTime() - start)
if remaining > 0 then
self:DebugPrint("[CanCast] " .. spellName .. " - EN CD (" .. string.format("%.1f", remaining) .. "s)")
return false
end
end
local isUsable, notEnoughMana = GetPetActionInfo(slot)
if notEnoughMana then
self:DebugPrint("[CanCast] " .. spellName .. " - SIN MANA")
return false
end
return true
end
function PetAI:ExecuteAbility(spellName)
if not spellName then return false end
-- El usuario puede no tener la habilidad en la barra, pero intentamos de todos modos
local hasSlot = self:CanCastPetAbility(spellName)
if not hasSlot then
self:DebugPrint("[Execute] " .. spellName .. " no en barra, forzando bypass...")
end
local castName = self:GetExactCastName(spellName)
local slot = self:GetPetAbilitySlot(spellName)
local success = false
-- Prioridad 1: Vanilla nativo por Slot (100% ininmune al idioma si esta en la barra)
if slot then
CastPetAction(slot)
success = true
self:DebugPrint("[Execute] " .. spellName .. " - CastPetAction(" .. slot .. ")")
-- Prioridad 2: ChatFrameEditBox con macro /cast (el motor del macro resuelve mejor los targeteos)
elseif ChatFrameEditBox then
local cmd = "/cast " .. tostring(castName)
ChatFrameEditBox:SetText(cmd)
ChatEdit_SendText(ChatFrameEditBox)
success = true
self:DebugPrint("[Execute] " .. spellName .. " - ChatFrame (" .. cmd .. ")")
-- Prioridad 3: CastSpellByName global fallback
elseif CastSpellByName then
CastSpellByName(castName)
success = true
self:DebugPrint("[Execute] " .. spellName .. " (" .. castName .. ") - CastSpellByName")
end
if success then
self:SetCooldown(spellName, 1.5)
end
return success
end
function PetAI:CastEnslaveDemon()
self:Print("|cffff00ffRe-esclavizando demonio!|r")
local castName = self:GetExactCastName("Enslave Demon")
if CastSpellByName then
CastSpellByName(castName)
return
end
if ChatFrameEditBox then
local cmd = "/cast " .. castName
ChatFrameEditBox:SetText(cmd)
ChatEdit_SendText(ChatFrameEditBox)
end
end
function PetAI:EvaluateEnslaved()
local abilities = self:ScanPetAbilities()
local inCombat = UnitAffectingCombat("pet")
local playerHP = self:GetPlayerHealthPercent()
local numAbs = WCS_TableCount(abilities)
if playerHP < 30 then
for i = 1, numAbs do
local ab = abilities[i]
if ab and ab.category == "cc" and ab.name then
if not self:IsOnCooldown(ab.name) then
self:SetCooldown(ab.name, 15)
self:Print("|cffff6600Enslaved:|r Usando " .. ab.name .. " (emergencia)")
return self:ExecuteAbility(ab.name)
end
end
end
end
if inCombat then
for i = 1, numAbs do
local ab = abilities[i]
if ab and ab.category == "damage" and ab.name then
if not self:IsOnCooldown(ab.name) then
self:SetCooldown(ab.name, 6)
return self:ExecuteAbility(ab.name)
end
end
end
for i = 1, numAbs do
local ab = abilities[i]
if ab and ab.category == "debuff" and ab.name then
if not self:IsOnCooldown(ab.name) then
self:SetCooldown(ab.name, 10)
return self:ExecuteAbility(ab.name)
end
end
end
end
return false
end
function PetAI:CheckEnslaveStatus()
if not self.Enslave.enabled then return end
if self.Enslave.isEnslaved and not UnitExists("pet") then
self:Print("|cffff0000El demonio se escapo!|r Intentando re-esclavizar...")
if self.Enslave.lastDemonName then
TargetByName(self.Enslave.lastDemonName)
self.Enslave.needReenslave = true
end
self.Enslave.isEnslaved = false
end
if self.Enslave.needReenslave and UnitExists("target") then
self:CastEnslaveDemon()
self.Enslave.needReenslave = false
end
if self.Enslave.isEnslaved then
local elapsed = GetTime() - self.Enslave.enslaveTime
local remaining = self.Enslave.enslaveDuration - elapsed
if remaining < 30 and not self.Enslave.warningShown then
self:Print("|cffff6600ADVERTENCIA:|r Enslave expira en 30 segundos!")
self.Enslave.warningShown = true
end
end
end
function PetAI:OnPetChanged()
if UnitExists("pet") then
local petType = self:GetPetType()
if petType == "Unknown" then
self.Enslave.isEnslaved = true
self.Enslave.lastDemonName = UnitName("pet")
self.Enslave.enslaveTime = GetTime()
self.Enslave.warningShown = false
self:Print("|cff00ff00Demonio esclavizado:|r " .. tostring(self.Enslave.lastDemonName))
end
end
end
-- ============================================================================
-- FUNCIONES DE SOPORTE PARA MASCOTAS MENORES
-- ============================================================================
function PetAI:HasFireShield(unit)
if not UnitExists(unit) then return false end
for i = 1, 32 do
local texture = UnitBuff(unit, i)
if not texture then break end
local texLower = string.lower(texture)
if string.find(texLower, "fire_firearmor") or string.find(texLower, "firearmor") then
return true
end
end
return false
end
function PetAI:HasDebuff(unit, pattern)
if not UnitExists(unit) then return false end
for i = 1, 16 do
local texture = UnitDebuff(unit, i)
if not texture then break end
if pattern then
if string.find(string.lower(texture), string.lower(pattern)) then
return true
end
else
return true
end
end
return false
end
function PetAI:GetGroupMembers()
local members = {}
table.insert(members, {unit = "player", name = UnitName("player")})
local numRaid = GetNumRaidMembers()
if numRaid and numRaid > 0 then
for i = 1, numRaid do
local unit = "raid" .. i
if UnitExists(unit) and not UnitIsUnit(unit, "player") then
table.insert(members, {unit = unit, name = UnitName(unit)})
end
end
return members
end
local numParty = GetNumPartyMembers()
if numParty and numParty > 0 then
for i = 1, numParty do
local unit = "party" .. i
if UnitExists(unit) then
table.insert(members, {unit = unit, name = UnitName(unit)})
end
end
end
return members
end
function PetAI:FindMemberNeedingFireShield()
local members = self:GetGroupMembers()
local numMembers = WCS_TableCount(members)
local now = GetTime()
local cfg = self.Config
local cache = self.FireShieldCache
for i = 1, numMembers do
local member = members[i]
if member and UnitExists(member.unit) and not UnitIsDead(member.unit) then
-- Verificar si ya tiene Fire Shield (buff activo)
if not self:HasFireShield(member.unit) then
-- Verificar cache para evitar spam al mismo objetivo
local memberName = member.name or ""
local lastApplied = cache.appliedTo[memberName] or 0
-- Solo aplicar si paso suficiente tiempo desde ultima aplicacion
if (now - lastApplied) > (cfg.fireShieldCooldown or 10) then
-- Verificar rango: player siempre en rango, otros verificar distancia
local inRange = false
if member.unit == "player" then
inRange = true
else
-- CheckInteractDistance: 1=10yd, 2=11yd, 3=10yd, 4=28yd
-- Usamos 4 que es ~28 yards (cercano a 30)
inRange = CheckInteractDistance(member.unit, 4)
end
if inRange then
return member
end
end
end
end
end
return nil
end
-- Marcar que aplicamos Fire Shield a un miembro
function PetAI:MarkFireShieldApplied(memberName)
if not memberName then return end
self.FireShieldCache.appliedTo[memberName] = GetTime()
self.FireShieldCache.lastTarget = memberName
self.FireShieldCache.lastTime = GetTime()
end
-- Limpiar cache de Fire Shield (llamar periodicamente)
function PetAI:CleanFireShieldCache()
local now = GetTime()
local cache = self.FireShieldCache
local expireTime = 60 -- Limpiar entradas mas viejas de 60 segundos
for name, timestamp in pairs(cache.appliedTo) do
if (now - timestamp) > expireTime then
cache.appliedTo[name] = nil
end
end
end
function PetAI:ScheduleTargetRestore(targetName)
if not targetName then return end
if not self.RestoreFrame then
self.RestoreFrame = CreateFrame("Frame")
end
self.RestoreTargetName = targetName
self.RestoreFrame.elapsed = 0
self.RestoreFrame:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + arg1
if this.elapsed > 0.5 then
this:SetScript("OnUpdate", nil)
if PetAI.RestoreTargetName then
TargetByName(PetAI.RestoreTargetName)
PetAI.RestoreTargetName = nil
end
end
end)
end
function PetAI:ScheduleClearTarget()
if not self.ClearFrame then
self.ClearFrame = CreateFrame("Frame")
end
self.ClearFrame.elapsed = 0
self.ClearFrame:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + arg1
if this.elapsed > 0.5 then
this:SetScript("OnUpdate", nil)
ClearTarget()
end
end)
end
function PetAI:ExecutePetAbilityOnTarget(spellName, targetUnit, targetName)
if self:IsOnCooldown(spellName) then
return false
end
local hadTarget = UnitExists("target")
local oldTargetName = nil
if hadTarget then
oldTargetName = UnitName("target")
end
-- 1. Preparar Nombres (Backup Legacy usa Ingles, God-Tier soporta Español)
local castNameES = self:GetExactCastName(spellName)
local castNameEN = spellName
-- 2. Limpiar Pet (Interrumpir Firebolt actual)
PetPassiveMode()
PetFollow()
-- 3. Cambiar Target
if targetUnit == "player" then
TargetUnit("player")
else
TargetUnit(targetUnit)
end
if not UnitExists("target") then
return false
end
-- 4. Ejecutar vía Macro (Inmune a slots de barra de mascota - Secreto del Backup)
if ChatFrameEditBox then
-- Primero intentamos el nombre localizado exacto
local cmd = "/cast " .. tostring(castNameES)
ChatFrameEditBox:SetText(cmd)
ChatEdit_SendText(ChatFrameEditBox)
-- Fallback inmediato al nombre en inglés (Turtle WoW Friendly)
if castNameES ~= castNameEN then
local cmdEN = "/cast " .. tostring(castNameEN)
ChatFrameEditBox:SetText(cmdEN)
ChatEdit_SendText(ChatFrameEditBox)
end
PetAI:DebugPrint("[ExecuteMacro] " .. spellName .. " (" .. castNameES .. ")")
PetAI:Print("|cff00ff00" .. tostring(spellName) .. "|r -> " .. tostring(targetName or targetUnit))
PetAI:SetCooldown(spellName, 4)
end
-- 5. Restaurar Target con Delay (Como en el backup funcional)
if hadTarget and oldTargetName then
self:ScheduleTargetRestore(oldTargetName)
elseif not hadTarget then
self:ScheduleClearTarget()
end
-- Re-activar IA defensiva después del casteo instantáneo
PetDefensiveMode()
return true
end
-- ============================================================================
-- EVALUACION DE MASCOTAS MENORES
-- ============================================================================
function PetAI:EvaluateImp()
local petMana = self:GetPetManaPercent()
-- Verificar modo: Fire Shield es habilidad de SOPORTE
if not self:ShouldUseSupport() then
self:DebugPrint("[Imp] Modo actual no permite habilidades de soporte")
return false
end
-- Limpiar cache periodicamente
self:CleanFireShieldCache()
-- Solo buscar objetivo si tiene mana suficiente
if petMana > 20 then
local member = self:FindMemberNeedingFireShield()
if member then
-- Marcar en cache ANTES de ejecutar
self:MarkFireShieldApplied(member.name)
local success = self:ExecutePetAbilityOnTarget("Fire Shield", member.unit, member.name)
if success then
self:DebugPrint("Fire Shield aplicado a " .. tostring(member.name))
end
return success
end
end
return false
end
function PetAI:EvaluateVoidwalker()
local playerHP = self:GetPlayerHealthPercent()
local petHP = self:GetPetHealthPercent()
local cfg = self.Config
local inCombat = UnitAffectingCombat("player") or UnitAffectingCombat("pet")
-- PRIORIDAD 1: Emergencia del jugador - sacrificio inmediato
if playerHP < (cfg.emergencyThreshold or 25) and inCombat then
-- Verificar modo: Sacrifice es habilidad DEFENSIVA
if not self:ShouldUseDefensive() then
self:DebugPrint("[Voidwalker] Modo actual no permite Sacrifice")
return false
end
self:Print("|cffff0000EMERGENCIA!|r Tu HP: " .. string.format("%.0f", playerHP) .. "% - Sacrificando para salvarte!")
return self:ExecuteAbility("Sacrifice")
end
-- PRIORIDAD 2: Sacrificio inteligente - Voidwalker a punto de morir
-- Mejor convertirse en escudo util que morir sin dar nada
if cfg.smartSacrifice then
local sacrificeThreshold = cfg.voidwalkerSacrificeHP or 15
if petHP < sacrificeThreshold and inCombat then
-- Verificar que el sacrificio sea util (jugador no esta al 100%)
if playerHP < 90 then
self:Print("|cffff6600SACRIFICIO INTELIGENTE!|r Voidwalker HP: " .. string.format("%.0f", petHP) .. "% - Convirtiendome en escudo!")
return self:ExecuteAbility("Sacrifice")
else
self:DebugPrint("Voidwalker moribundo pero jugador al " .. string.format("%.0f", playerHP) .. "% - no sacrifico")