-- -- $Id: test_threat_ai.lua 2007-02-02 13:15:09 stormcrowsx, Biglad & Sanaell -- Implementation of threat system as well as spell casting from DB. This initially started as way for -- me to learn lua and so its a work in progress, but has quickly been evolving to allow the AI to be smarter -- and more aware. -- -- Recent Updates: The code now gets the class of the attacking unit and uses it to place a modifier -- on the damage they have done. I.E. Warriors will get a 20% increase in threat -- that they cause. -- -- Rev .103 2/03/07 -- -NPC's will no longer let you pound them from distance -- -Agro distance was doubled -- -New function added SetTables(LuaAI, unit) which will add any new unit to the -- running threat list and further prevents the script from attempting to access -- data that it doesn't own -- -- Rev .104 -- -Deleted bugs crashed server -- -- Rev .105 2/12/07 -- -Added new function TEST_Threat_AI.ClearThreat(LuaAI, unit) which will -- -clear the threat of the unit that was passed to it, must call with the unit. -- -- Rev .106 2/12/07 -- -Added new function TEST_Threat_AI.CastSpellClass(LuaAI, classID, spellID) -- -this allows us to cast class specific spells such as a mana burn that targets -- -priests -- -This function also has a boolean return value so if a spell is not casted because it can't -- -find that class it will let you know -- -- Rev .107 2/13/07 -- -NPC's will no longer attack each other if they are not hostile to each other, a little -- -more work will allow them to buff those friendly to them. -- -Server will no longer crash if the tank is killed or kicked, the threat is now properly -- -reset on death. -- -- Rev .108 2/13/07 -- -Clearing threat has been updated so that it will truly clear the threat -- -previously this was really hacked up and do a weak reset. -- -Will no longer add NPC's to the threat list unless that npc has done damage to it, -- -Makes the NPC much less jumpy -- -- Rev .109 2/13/07 -- -Biglad's code for loading the spells out of the Database was added. -- -Spells are loaded into the data table as -- -data.spellID1, data.spellID2 .. etc -- -Spawn point is loaded into the data table, plan to use this later to make the AI run -- -back to its spawn point after its ran too far away. -- -New functions added -- - TEST_Threat_AI.GetDBInfo(LuaAI) --Done by BigLad, gets the spells out of the database -- - as well as some additional info such as the creatures spawn point which will be used -- - used in later revisions to make the creature return to its spawn point -- - -- - TEST_Threat_AI.PickRandomSpell(LuaAI) -- This function will -- - randomly pick one of the spells from the database of the creature and return the spell ID for it. -- - will return a 0 if the creature does not have any spells -- -- Rev .110 2/14/07 -- -Using code from biglad implemented the new function DoCastRandomChance that allows randomly casting spells -- - gave it its own function to help keep the code cleaner -- -Added support for update so that the NPC will now run update checking code every 3 seconds to -- - make sure its returned to its spawn point and hasn't chased a PC an absurd distance -- -Creatures will now return to their spawn point when a PC is dead or run out of distance -- - this code may need some additional updating to keep the NPC from running back to its -- - spawn when it still needs to attack another player on the threat list. -- -- Rev .111 2/14/07 -- -NPC's will now reset their mana and HP to max when resetting. -- -Modified the Update code to turn it off when the NPC is not doing anything. This will help -- - this code from being a resource hog when its not needed -- -- Rev .112 2/14/07 -- -The first of probably many revisions having to do with NPC's targetting the next highest -- --on their threat list when the primary has died -- -Updated the Database coding so that it makes sense now :P thanks andrejkw -- -Re-examined the tables to make them a little more effiecent one was cut since it was more or less -- --useless. Also cleaned up the functions that deal with the the tables, setting and clearing threat -- -This file is getting kinda long :\ -- -- -- Rev .113 02/03/07 -- - Spell type chcking added so mob will cast spells on the right target -- - added dobuff/do debuff ONCE in battle -- - added check to make sure script doesnt run on players pets -- -- Rev .114 03/13/07 -- - Added spell cast time store lookup for spell cating times -- - added new function for detecting pets & totems -- - added random change option for heal & damage spells -- -- Rev .115 03/19/07 -- - Added global cool down for all spell cast (5 secs between each cast) -- - added player & Mob level checking for aggro distance -- - added aggro check for high lvl players, if player 20+ levels high than mob, aggro range very small -- - added elite mob check and high cast rate for eilites -- - clean up code a bit, still lots to clean up once script complete -- -- Rev .115a 03/19/07 -- - Last of current bugs fixed WE THINK! -- - Couple of functions optimized and code tweaks by Sanaell -- -- Rev .116 03/21/07 -- - Code update & tweakes -- - Some functions updates/redone -- - more null checking added to fix empty data vaules -- -- Known Issues: See Threat_TODO.txt -- -- -- test_threat_ai.lua $release v.116 -- -- Important Notes:If having problem getting the code to load up the database you can do the following: -- The following code must be inserted into the MasterScript file in order to properly -- interface with the database -- sDatabase = DatabaseMysql() -- sDatabase:Initialize("localhost;3306;username;password;mangos") -- where username and password are insert what you use to connect to your sql database -- if your database uses a name other than mangos this will need to be changes as well -- -- Scripting developed by: -- Stormcrowsx -- Biglad -- Sanaell -- -- Thanks To: -- MangoScript Dev Team - Providing tools neccessary to quickly compile and test scripts -- Hakon, SinnerG, andrejkw & Derex for advise & help :) TEST_Threat_AI = Mango.AIScripts.Create('test_threat_ai'); -- Modifiers for the threat, this helps the warriors, paladins, and druids to tank -- due to the lack of working taunts -- Not working until I find a way to get the classID from the units -- Edit these numbers to change the way agro works -- 1.2 for instance means 20% is added to threat -- 0.8 would mean 20% is knocked off the damage -- Warrior , Paladin , Hunter , Rogue , Priest , Unknow , Shaman , Mage , Warlock , Unknow , Druid TEST_Threat_AI.Modifier = { 1.2,1.1,0.9,0.8,0.9,1,1.0,0.9,0.9,1,1.0}; -- Function prototypes (to keep it clean) local TEST_Threat_AI_Init; local TEST_Threat_AI_MoveInLineOfSight; local TEST_Threat_AI_AttackStart; local TEST_Threat_AI_DamageInflict; local TEST_Threat_AI_OnDeath; local TEST_Threat_AI_SetModifier; local TEST_Threat_AI_LevelAggroCheck; local TEST_Threat_AI_AggroDisCheck; -- local TEST_Threat_AI_AttackStop; function TEST_Threat_AI_IsVisible(LuaAI,unit) return LuaAI:IsVisible_agr(unit,1500) end function TEST_Threat_AI_MobModifier(LuaAI) local issemiuber = LuaAI:GetCreature():isElite(); TEST_Threat_AI.ChanceToCastOnDamage , TEST_Threat_AI.ChanceToHealOnDamage = 30 , 60; if ( issemiuber ) then TEST_Threat_AI.ChanceToCastOnDamage , TEST_Threat_AI.ChanceToHealOnDamage = 40 , 75; end end function TEST_Threat_AI.DoCastRandomChance(LuaAI, target, chance, spellID, casttime1) local letsDoSpell = rand_range(1,100) data.casting = 0 if ( letsDoSpell >= ( 100 - chance ) ) and ( spellID > 0 ) then --Need spell cast time support cast times for spells are in AI system alrdy casttime1 = cast time in ms LuaAI:DoCast(target, spellID); end end function TEST_Threat_AI.CheckPet(LuaAI) if ( LuaAI:GetCreature():isTotem() ) or ( LuaAI:GetCreature():isPet() ) then return true end return false end function TEST_Threat_AI.CheckSpell(LuaAI, mobspellid, spellno) if mobspellid >= 1 then local data = LuaAI:Data() spell , spelltime , casttime = 0 if ( sSpellStore:LookupEntry(mobspellid) ~= nil ) then spell = sSpellStore:LookupEntry(mobspellid) if sCastTimesStore:LookupEntry(spell.CastingTimeIndex) ~= nil then -- need to fix this, something changed in core and spelltime is now null spelltime = 2500 casttime = 2500 --spelltime.CastTime = 2500 --spelltime = sCastTimesStore:LookupEntry(spell.CastingTimeIndex) --if spelltime.CastTime ~= nil then --casttime = spelltime.CastTime --end end end --check to so who the spell is for for i = 0, 3, 1 do if ( spell:EffectImplicitTargetA(i) == TARGET_SINGLE_FRIEND ) then data.targetfriendly = true data.targetfriendlyspell = mobspellid end if ( spell:EffectImplicitTargetA(i) == TARGET_CHAIN_DAMAGE ) or ( spell:EffectImplicitTargetA(i) == TARGET_ALL_ENEMY_IN_AREA ) then data.targetplayer , data.targetplayerspell = true , mobspellid end end --if spell for mob/friendly check type of spell heal or buff if ( data.targetfriendly ) then --check for healing spell for i = 0, 3, 1 do if ( spell:Effect(i) == SPELL_EFFECT_HEAL ) or ( spell:Effect(i) == SPELL_EFFECT_SCRIPT_EFFECT ) or ( spell:Effect(i) == SPELL_EFFECT_HEAL_MAX_HEALTH ) then data.Canheal = true data.castimewaitheal = casttime data.healspell = data.targetfriendlyspell end --check for buff spell if ( spell:Effect(i) == SPELL_EFFECT_APPLY_AURA ) then data.buffspell = true data.dobuffspell = data.targetfriendlyspell end --check spell for self cast or friendly data.targetfriendlymob = true data.targetfriendlyspell = mobspellid if ( spell:EffectImplicitTargetA(i) == TARGET_SELF ) then data.targetself = true data.selfcastonlyspell = data.targetfriendlyspell end end end --if spell for player/group check type of spell damage or debuff if ( data.targetplayer ) then --data.cancastdmgspell = "do"..spellno; --check for healing spell for i = 0, 3, 1 do if ( spell:Effect(i) == SPELL_EFFECT_SCHOOL_DAMAGE ) or ( spell:Effect(i) == SPELL_EFFECT_INSTAKILL ) or ( spell:Effect(i) == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE ) or ( spell:Effect(i) == SPELL_EFFECT_HEALTH_LEECH ) then data.dmgspell = true data.castimewaitdmg = casttime data.dodmgspell = data.targetplayerspell end --check for debuff spell if ( spell:Effect(i) == SPELL_EFFECT_APPLY_AURA ) then data.debuffspell = true data.castimewaitdebuff = casttime data.dodebuffspell = data.targetplayerspell end --check spell for single cast or group data.targetgroup = true data.targetgroupspell = data.targetplayerspell if ( spell:EffectImplicitTargetA(i) == TARGET_CHAIN_DAMAGE ) then data.targetsingleplayer = true data.targetsingleplayerspell = data.targetplayerspell end end end end data.candobuffspell , data.candodebuffspell , data.casting = 0 LuaAI:SetData(data) end function TEST_Threat_AI.GetDBInfo(LuaAI) -- function to pull spell and spawnpoint info local data = LuaAI:Data(); data.SpawnPosX , data.SpawnPosY , data.SpawnPosZ = 0 for i= 1, 4 do data["spellID"..i] = 0 end data.moblevel = 1 if ( not TEST_Threat_AI.CheckPet(LuaAI) ) then creatureGUID = LuaAI:GetCreature():GetGUIDInst(); -- debug for GUID -- LuaAI:GetCreature():MonsterSay("My GUID: " ..creatureGUID,0); queryString = "SELECT `id` , `spawn_position_x` , `spawn_position_y` , `spawn_position_z` FROM `creature` WHERE `guid`=" .. creatureGUID; nameid = sDatabase:Query(queryString) if nameid == nil then LuaAI:GetCreature():MonsterSay("SPELL ERROR! please, tell about it to your Administrator!",0); end if nameid ~= nil then getcreatureID = nameid:Fetch() data.SpawnPosX = getcreatureID[1]:GetInt32() data.SpawnPosY = getcreatureID[2]:GetInt32() data.SpawnPosZ = getcreatureID[3]:GetInt32() local creatureID = getcreatureID[0]:GetString() queryString2 = "SELECT `maxlevel`, `spell1`, `spell2`, `spell3`, `spell4` FROM `creature_template` WHERE `entry`=" .. creatureID; result1 = sDatabase:Query(queryString2); if result1 ~= nil then result = result1:Fetch(); --Store in the data table so we can easily make use of it later data.moblevel = result[0]:GetInt32() for i= 1, 4 do data["spellID"..i] = result[i]:GetInt32() end end for i= 1, 4 do if data["spellID"..i] > 0 then if sSpellStore:LookupEntry(data["spellID"..i]) then TEST_Threat_AI.CheckSpell(LuaAI, data["spellID"..i], i) end end end end end LuaAI:SetData(data) end function TEST_Threat_AI_LevelAggroCheck(LuaAI, player) if ( TEST_Threat_AI.CheckPet(LuaAI) ) then local data = LuaAI:Data(); if player:getLevel() ~= nil then local playerslevel = tonumber(player:getLevel()) local mobslevel = tonumber(data.moblevel) local lvldif = 1 if playerslevel and mobslevel and tonumber(player:getClass()) ~= 0 then lvldif = tonumber((mobslevel - playerslevel)) TEST_Threat_AI_AggroDisCheck(LuaAI, player) if lvldif < -19 then return true end end end end end function TEST_Threat_AI_AggroDisCheck(LuaAI, player) local data = LuaAI:Data(); local playerslevel = player:getLevel() local mobslevel = data.moblevel local lvldif = (mobslevel - playerslevel) TEST_Threat_AI.AgroDistance = 75 local diftst = 0 for i= -10, 10, 5 do if lvldif >= i and lvldif < i + 5 then TEST_Threat_AI.AgroDistance , diftst = TEST_Threat_AI.AgroDistance + 25 , 1 end if ( diftst ~= 1 ) then TEST_Threat_AI.AgroDistance = TEST_Threat_AI.AgroDistance + 50 if lvldif > 10 then TEST_Threat_AI.AgroDistance = 400 end if lvldif < -10 then TEST_Threat_AI.AgroDistance = 75 end end end end --Picks a random spell from its spell listings function TEST_Threat_AI.PickRandomSpell(LuaAI) local data = LuaAI:Data() local spells = { data.spellID1 , data.spellID2 , data.spellID3 , data.spellID4 }; local randSpells = { } local highRoll , highRollerID = 0 , 1; for i=1, 4 do randSpells[i] = 0; if (spells[i] > 0) then randSpells[i] = rand_range(1,100); end if ( randSpells[i] > highRoll ) then highRoll , highRollerID = randSpells[i] , i end end return spells[highRollerID]; end -- Function will allow targetting specfic classes for spells function TEST_Threat_AI.CastSpellClass(LuaAI, classID, spellID) -- List of classID's that are valid to pass to it -- classID = 1 Warrior -- classID = 2 Paladin -- classID = 3 Hunter -- classID = 4 Rogue -- classID = 5 Priest -- classID = 7 Shaman -- classID = 8 Mage -- classID = 9 Warlock -- classID = 11 Druid -- Unknown = 0 was returning this when it was tracking nearby critters, possibly critter classID or npc classID local data = LuaAI:Data() local units = data.UnitList local counter = ( data.TableCounter - 1 ); local indexNum = 0 local found = false; for i=1, counter do if ( not found ) then if (units[i]:getClass() == classID) then found = true; indexNum = i end end end if ( found ) then LuaAI:DoCast(units[indexNum], spellID); return true; end return false; end function TEST_Threat_AI_EraseThreat(LuaAI) local data = LuaAI:Data(); -- List of damage done by units --List of units on our threat list data.threatListDamage = { }; data.UnitList = { }; data.TableCounter = 1 data.candodebuffspell = 0 data.candobuffspell = 0 data.casting = 0 data.StartAttack = false; data.wasAttacked = false; data.update = false; data.TableCounter = 1; --The number of attackers (due to lua indexing starts at 1 not 0) LuaAI:SetData(data) LuaAI:SetUpdateInterval(0) end function TEST_Threat_AI.CheckThreatList(LuaAI) local data = LuaAI:Data(); local threatListDamage = data.threatListDamage local units = data.UnitList local counter = data.TableCounter local newUnits = { }; local newThreatListDamage = { }; local newCounter = 1; if ( counter > 1) then for i = 1, counter - 1 do unit = units[i]; if ( unit:isTargetableForAttack() ) then if ( ( LuaAI:GetCreature():GetDistanceSq(unit) < 1500 ) and ( unit:isInAccessablePlaceFor(LuaAI:GetCreature()) ) ) then table.insert(newUnits, newCounter, unit); table.insert(newThreatListDamage, newCounter, threatListDamage[i]); newCounter = newCounter + 1; end end end data.TableCounter = newCounter; LuaAI:SetData(data); if ( newCounter > 1 ) then data.threatListDamage = newThreatListDamage; data.UnitList = newUnits; return true; end end return false; end function TEST_Threat_AI.SetTables(LuaAI, unit) --Sets up the tables for the threat local data = LuaAI:Data(); local unitID = tonumber(unit:GetGUIDLow()); --Get the unitID local units = data.UnitList; --load the unit table local found = false; --is unit in damage table yet? local counter = data.TableCounter --size of threatlist local threatListDamage = data.threatListDamage; --Damage tables --Search for our unit in the attackers table for i = 1,counter-1 do unitToCheck = units[i] if (tonumber(unitToCheck:GetGUIDLow()) == unitID) then found = true; end end --If we don't find him init tables to include this unit --Currently pulling agro adds to your dmg table if ( not found ) then table.insert(threatListDamage, counter, 75); table.insert(units, counter, unit); counter = counter + 1; --Increase the size of threat list --saves our data collectd data.threatListDamage = threatListDamage; data.UnitList = units; data.TableCounter = counter; LuaAI:SetData(data); end end function TEST_Threat_AI.GetHighDmg(LuaAI) local data = LuaAI:Data() local threatListDamage = data.threatListDamage; --Load in damage table local highDmg = 0; --Highest damage in table local units = data.UnitList; --Load in unit table local highDmgID --Unit who did the most damage (set to 1 but it makes no difference) local counter = data.TableCounter - 1; --Load in table counter (-1 to prevent from accessing data not ours) for j = 1, counter do me , indexNum = threatListDamage[j] , j if ( me > highDmg ) then highDmg , highDmgID = me , units[j]; end end return highDmgID; end function TEST_Threat_AI.CheckAggro(LuaAI) local data = LuaAI:Data() local threatListDamage = data.threatListDamage; --Load in damage table local highDmg = 0; --Highest damage in table local units = data.UnitList; --Load in unit table local highDmgID; --Unit who did the most damage (set to 1 but it makes no difference) local wasCast = false; --Loops through the damage table looking for highest damage --Sets the highest ID if ( TEST_Threat_AI.CheckThreatList(LuaAI) ) then highDmgID = TEST_Threat_AI.GetHighDmg(LuaAI); randomSpell = TEST_Threat_AI.PickRandomSpell(LuaAI) distanceToHighDmg = LuaAI:GetCreature():GetDistanceSq(highDmgID) --This is the basis for the attacking, its really rough and can use some updating but works for now :) if ( not data.wasAttacked ) then TEST_Threat_AI_AggroDisCheck(LuaAI, unit) if (distanceToHighDmg < TEST_Threat_AI.AgroDistance) then LuaAI:DoStartAttack(highDmgID); data.wasAttacked = true; end -- Unit is outside of agro range, pull made by spell or ranged attack else LuaAI:DoStartAttack(highDmgID); data.wasAttacked = true; end else LuaAI:DoStopAttack(); data.candodebuffspell , data.candobuffspell , data.casting = 0 end LuaAI:SetData(data) end -- Takes the classID and returns the modifier for that classes damage -- This is a way to help the rogues not get killed and the tanks to -- tank. function TEST_Threat_AI.SetModifier(ClassID) if ( ClassID ~= nil ) then if ( ClassID > 0 and ClassID < 12 ) then return TEST_Threat_AI.Modifier[ClassID] end end return 1 end function TEST_Threat_AI_Init(LuaAI) -- Initialize AI data data = { }; data.threatListDamage = { }; -- List of damage done by units data.UnitList = { }; --List of units on our threat list data.StartAttack = false; --Used to start the attack once data.TableCounter = 1; --The number of attackers (due to lua indexing starts at 1 not 0) data.wasAttacked = false; data.update = false; LuaAI:SetData(data) -- Get the data from the DataBase TEST_Threat_AI.GetDBInfo(LuaAI); LuaAI:SetUpdateInterval(0) return true end function TEST_Threat_AI_Update(LuaAI) local data = LuaAI:Data(); local AIHP = LuaAI:GetCreature():GetMaxHealth() local AIMANA = LuaAI:GetCreature():GetMaxPower(0) --distanceToSpawn = LuaAI:GetCreature():GetDistanceSq(data.SpawnPosX, data.SpawnPosY, data.SpawnPosZ) if ( ( not LuaAI:GetCreature():isAttacking() ) ) then TEST_Threat_AI.CheckAggro(LuaAI); if ( not TEST_Threat_AI.CheckThreatList(LuaAI) ) then TEST_Threat_AI_Init(LuaAI); LuaAI:GetCreature():SetPower(0, AIMANA); LuaAI:GetCreature():SetHealth(AIHP); LuaAI:DoStopAttack(); end end LuaAI:SetData(data); end function TEST_Threat_AI_MoveInLineOfSight(LuaAI,unit) local data = LuaAI:Data() --LuaAI:GetCreature():MonsterSay("I see you",0); --Will fail to do anything if its not a hostile, future plans to instead add the unit to a friendly list --to allow tracking of hp and what it is attacking. This will allow units to help each other if ( unit:IsHostileTo(LuaAI:GetCreature()) ) and ( not TEST_Threat_AI.CheckPet(LuaAI) ) and ( not TEST_Threat_AI_LevelAggroCheck(LuaAI, unit) ) then data.update = true LuaAI:SetUpdateInterval(500) TEST_Threat_AI.SetTables(LuaAI, unit); TEST_Threat_AI.CheckAggro(LuaAI); -- Check if the guard has to talk or not :) LuaAI:MoveInLineOfSight_agr(unit); --add buff before batle if mob can buff --Check for Buff local data = LuaAI:Data(); if data.bufftimer == nil then data.bufftimer = os.time() end local timenow = os.time() local checkedbufftime = (timenow - data.bufftimer) if checkedbufftime > 300 then data.candobuffspell = 0 end if data.candobuffspell == 0 then if data.buffspell == true then spelltocast = data.dobuffspell --Check buff target if data.targetself == true then LuaAI:DoCast(LuaAI:GetCreature(),spelltocast) spelltarget = LuaAI:GetCreature() else --mob can cast spell on a friendly target --need core support, need to add check for friendly IDs and select target LuaAI:DoCast(LuaAI:GetCreature(),spelltocast) spelltarget = LuaAI:GetCreature() end end data.bufftimer = os.time() data.candobuffspell = 1 data.casttimer = ( os.time() - 10 ) end end LuaAI:SetData(data) end -- Called at each attack of creature by any victim function TEST_Threat_AI_AttackStart(LuaAI,unit) -- Must set tables in case it was pulled at range -- No check for friendly here as it is already attacking a friendly if it got here :) TEST_Threat_AI.SetTables(LuaAI, unit); local data = LuaAI:Data() TEST_Threat_AI.CheckAggro(LuaAI); end function TEST_Threat_AI_DamageInflict(LuaAI,unit,amount_damage) TEST_Threat_AI.SetTables(LuaAI, unit); local data = LuaAI:Data() local unitID = tonumber(unit:GetGUIDLow()); --Load in unitID local threatListDamage = data.threatListDamage; --Again loading in data tables local units = data.UnitList --Loads in the unitlist so we can find the class local indexNum , modifier = 1 --Index of unit that attacked us --Threat Class modifier local counter = data.TableCounter; --Size of threat list if ( not data.casttimer or data.casttimer == 0 ) then data.casttimer = os.time(); end if data.casttimer < os.time() + 5 then data.casting = 0 --reset can cast end --log("%s",tostring(casttimer)) if ( not data.update ) then LuaAI:SetUpdateInterval(500) end --Find the index number of the attacker for i = 1 , counter - 1 do unitToCheck = units[i]; if (tonumber(unitToCheck:GetGUIDLow()) == unitID) then indexNum = i; end end class = units[indexNum]; classID = class:getClass(); --Add in the attackers damage and save it amount_damage = amount_damage * TEST_Threat_AI.SetModifier(classID); local damage = amount_damage + threatListDamage[indexNum]; threatListDamage[indexNum] = damage; data.threatListDamage = threatListDamage; data.wasAttacked = true; LuaAI:SetData(data); haveUnitsLeft = TEST_Threat_AI.CheckThreatList(LuaAI); if ( haveUnitsLeft ) then TEST_Threat_AI.CheckAggro(LuaAI); --Neccessary so it attacks the new target highDmgID = TEST_Threat_AI.GetHighDmg(LuaAI); randomSpell = TEST_Threat_AI.PickRandomSpell(LuaAI); --set spell target based on current random spellid if (data.cancasttimer ~= 0) and (data.cancasttimer ~= 1) then data.cancasttimer = 0 end --Check for heal spell if ( data.Canheal ) and ( data.casting == 0 ) and ( data.cancasttimer == 0 ) then local HP = LuaAI:GetCreature():GetHealth(); local MaxHP = LuaAI:GetCreature():GetMaxHealth(); if ( 100 / MaxHP * HP <= 30 ) then spelltocast = data.healspell --Check heal spell target casttime1 = data.castimewaitheal TEST_Threat_AI_MobModifier(LuaAI); spelltarget = LuaAI:GetCreature(); TEST_Threat_AI.DoCastRandomChance(LuaAI, spelltarget, TEST_Threat_AI.ChanceToHealOnDamage, spelltocast, casttime1) data.casting , data.cancasttimer = 1 data.casttimer = os.time(); end end --Check for damage spell if ( data.dmgspell ) and ( data.casting == 0 ) and ( data.cancasttimer == 0 ) then spelltocast = data.dodmgspell data.casting , data.cancasttimer = 1 data.casttimer = os.time(); --target all group this needs doing. spelltarget = highDmgID casttime1 = data.castimewaitdmg --Check buff target if data.targetsingleplayer == true then casttime1 = data.castimewaitdmg local hidmgornot = rand_range(1,100); spelltarget = unit if hidmgornot <= 95 then --add 5% chance mob will cast on player that just did dmg spelltarget = highDmgID end end end if ( data.debuffspell ) and ( data.casting == 0 ) and ( data.candodebuffspell == 0 ) and ( data.cancasttimer == 0 ) then spelltocast = data.dodebuffspell data.casting , data.cancasttimer , data.candodebuffspell = 1 data.casttimer = os.time(); --Check DeBuff Target if ( data.targetsingleplayer ) then spelltarget = highDmgID casttime1 = data.castimewaitdebuff else --target all group this needs doing. spelltarget = highDmgID end end if spelltarget == nil then spelltarget = highDmgID end if spelltocast == nil then spelltocast = 0 data.casttimer = (os.time() + 1) LuaAI:DoStartAttack(highDmgID); end TEST_Threat_AI_MobModifier(LuaAI); TEST_Threat_AI.DoCastRandomChance(LuaAI, spelltarget, TEST_Threat_AI.ChanceToCastOnDamage, spelltocast, casttime1) else LuaAI:DoStopAttack(); end local timenow1 = (os.time() + 1) if data.casttimer == nil then data.casttimer = os.time(); end local checkedcasttime = ( timenow1 - data.casttimer ) data.cancasttimer = 0 if checkedcasttime < 6 then data.cancasttimer = 1 end LuaAI:SetData(data) end function TEST_Threat_AI_OnDeath(_Creature, killer) LuaAI = _Creature:GetLuaAI() if LuaAI ~= nil then TEST_Threat_AI_EraseThreat(LuaAI); end end -- Assign the functions TEST_Threat_AI.Init = TEST_Threat_AI_Init; TEST_Threat_AI.MoveInLineOfSight = TEST_Threat_AI_MoveInLineOfSight; TEST_Threat_AI.AttackStart = TEST_Threat_AI_AttackStart; TEST_Threat_AI.DamageInflict = TEST_Threat_AI_DamageInflict; TEST_Threat_AI.Update = TEST_Threat_AI_Update; TEST_Threat_AI.OnDeath = TEST_Threat_AI_OnDeath; TEST_Threat_AI.IsVisible = TEST_Threat_AI_IsVisible; TEST_Threat_AI.MobModifier = TEST_Threat_AI_MobModifier; TEST_Threat_AI.LevelAggroCheck = TEST_Threat_AI_LevelAggroCheck; TEST_Threat_AI.AggroDisCheck = TEST_Threat_AI_AggroDisCheck; -- Link the AI to the different scripts -- Note: Only the ones who use the Mango.Scripts method of scripting can use TEST_Threat_AI TEST_Threat_AI:Register('test_threat');