Crâ [AI Experiment] Cra air flèche magique : LOSseeker

    Publicités

Users Who Are Viewing This Thread (Total: 0, Members: 0, Guests: 0)

Lmoony

Membre
Apr 7, 2019
28
56
214
30
Line Of Sight Seeker

Ces derniers jours je me suis un peu amusé avec l'API de flaty et ses possibilités cachées.

Mon but
Donner la capacité au bot d'utiliser plus intelligemment ses PM's en adéquation avec les lignes de vues existantes.
Donc je souhaitais faire un bot qui, s'il a, à sa portée une case depuis laquelle il peut taper, il s'y rendra et utilisera un sort, ici, flèche magique.
Puis reculera d'autant de PM qu'il lui reste.

Le bot a ainsi moins de chance de tourner en rond sur des maps truffée d'obstacles pour la ligne de vue.
Il bénéficie également d'un meilleur maintient de la distance. Ne se déplaçant que du nombre de case requise pour taper, il économise ses PM afin de reculer lorsque son tour se termine

Cela ne semble pas grand chose, mais en fait, c'est un comportement déjà radicalement moins aléatoire et plus stable qu'une IA basique.

Une IA basique classiquement essaye d'utiliser un sort et si elle n'y arrive pas, établit un chemin jusqu'à l'entité la plus proche, s'y engouffre de tous ses PMs et re-tente une attaque.
Et ce, même si en reculant d'une case elle pouvais trouver une ligne de vue très avantageuse.

La mienne, en revanche, cherchera le chemin de poids le plus faible vers une ligne de vue disponible a portée de déplacement.

A priori, je pensais que la méthode
Code:
fight:isVisible(fromCellId, toCellId, isDiagonal)
Servait a dire si oui ou non la ligne de vue était présente entre deux cellId. Mais ce n'est visiblement pas le cas. A vrai dire, je ne sais pas trop a quoi sert cette fonction.

J'ai donc développé la mienne :
Code:
function floatAlmostEquals(a, b)
    if (a ~= b) then
        return math.abs(a - b) < 0.0001
    end
    return true
end

function getCellsIdBetween(from, to)
    if from == to then
        return {}
    end
    if not(IsValidCellid(from)) or not(IsValidCellid(to)) then
        return {}
    end
    local fromCoord = CellidToCoord(from)
    local toCoord = CellidToCoord(to)
    local deltaX = toCoord.x - fromCoord.x
    local deltaY = toCoord.y - fromCoord.y
    local t = math.sqrt(deltaX * deltaX + deltaY * deltaY)
    local tDeltaX = deltaX / t
    local tDeltaY = deltaY / t
    local stepX
    if tDeltaX < 0 then
        stepX = -1
    else
        stepX = 1
    end
    local stepY
    if tDeltaY < 0 then
        stepY = -1
    else
        stepY = 1
    end
    local MaxX = 0.5 * math.abs(1 / tDeltaX)
    local MaxY = 0.5 * math.abs(1 / tDeltaY)
    local startX = fromCoord.x
    local startY = fromCoord.y
    local endX = toCoord.x
    local endY = toCoord.y
    local ret = {}
    while startX ~= endX or startY ~= endY do
        if floatAlmostEquals(MaxX, MaxY) then
            MaxX = MaxX + 0.5 * math.abs(1 / tDeltaX)
            MaxY = MaxY + 0.5 * math.abs(1 / tDeltaY)
            startX = startX + stepX
            startY = startY + stepY
        elseif MaxX < MaxY then
            MaxX = MaxX + 0.5 * math.abs(1 / tDeltaX)
            startX = startX + stepX
        else
            MaxY = MaxY + 0.5 * math.abs(1 / tDeltaY)
            startY = startY + stepY
        end
    table.insert(ret, CoordToCellid({x = startX, y = startY}))
    end
    return ret
end

function hasLineOfSight(fromCellid, toCellid)
    local account = fight._account
    local game = account.Game
    local map = game.Map
    local fightGame = game.Fight
    local mapData = map.Data
    local cellsToCheck = getCellsIdBetween(fromCellid, toCellid)
    for i, v in ipairs(cellsToCheck) do
        if not(mapData:IsLineOfSight(v)) or (not(fightGame:IsCellFree(v)) and v ~= toCellid)then
            return false
        end
    end
    return true
end
Cette IA ne joue qu'avec flèche magique. Et nécessite d'entrer manuellement la PO max totale du sort (avec les boosts d'équipements donc) Ici :
Code:
local Spells = {
    FlecheMagique = {
        name    = 'Fleche Magique',
        minRange    = 1,
        maxRange    = 8, <-- HERE
        inLine        = false,
    },
}
(Les méthodes de l'API permettant de récupérer la PO actuelle ne fonctionnant pas, on est obligé de faire ainsi)

Naturellement, n'hésitez pas a me poser des questions ou me rapporter des bugs éventuels

Les idées d'amélioration fusent dans ma tête, et elles sont bien sur, innombrables. Il ne s'agit là que d'un premier jet.
Malheureusement, la motivation seule ne fera pas tout car l'on reste fort limité par le manque d'information procurée par l'API quant à l'environnement du jeu.

Je poste ici le code complet, s'il y en a que cela intéresse.
Code:
DofusClass="9" -- CRA
CellArray = {}
CellArrayInit = 0

local Spells = {
    FlecheMagique = {
        name    = 'Fleche Magique',
        minRange    = 1,
        maxRange    = 8,
        inLine        = false,
    },
}

function floatAlmostEquals(a, b)
    if (a ~= b) then
        return math.abs(a - b) < 0.0001
    end
    return true
end

function getCellsIdBetween(from, to)
    if from == to then
        return {}
    end
    if not(IsValidCellid(from)) or not(IsValidCellid(to)) then
        return {}
    end
    local fromCoord = CellidToCoord(from)
    local toCoord = CellidToCoord(to)
    local deltaX = toCoord.x - fromCoord.x
    local deltaY = toCoord.y - fromCoord.y
    local t = math.sqrt(deltaX * deltaX + deltaY * deltaY)
    local tDeltaX = deltaX / t
    local tDeltaY = deltaY / t
    local stepX
    if tDeltaX < 0 then
        stepX = -1
    else
        stepX = 1
    end
    local stepY
    if tDeltaY < 0 then
        stepY = -1
    else
        stepY = 1
    end
    local MaxX = 0.5 * math.abs(1 / tDeltaX)
    local MaxY = 0.5 * math.abs(1 / tDeltaY)
    local startX = fromCoord.x
    local startY = fromCoord.y
    local endX = toCoord.x
    local endY = toCoord.y
    local ret = {}
    while startX ~= endX or startY ~= endY do
        if floatAlmostEquals(MaxX, MaxY) then
            MaxX = MaxX + 0.5 * math.abs(1 / tDeltaX)
            MaxY = MaxY + 0.5 * math.abs(1 / tDeltaY)
            startX = startX + stepX
            startY = startY + stepY
        elseif MaxX < MaxY then
            MaxX = MaxX + 0.5 * math.abs(1 / tDeltaX)
            startX = startX + stepX
        else
            MaxY = MaxY + 0.5 * math.abs(1 / tDeltaY)
            startY = startY + stepY
        end
    table.insert(ret, CoordToCellid({x = startX, y = startY}))
    end
    return ret
end

function InitCellsArray()
    local b = 0
    local startX = 0
    local startY = 0
    local cell = 0
    local a = 0

    while (a < 20) do
        b = 0
        while (b < 14) do
            CellArray[cell] = {x = startX + b, y = startY + b}
            cell = cell + 1
            b = b + 1
        end
        startX = startX + 1
        b = 0
        while (b < 14) do
            CellArray[cell] = {x = startX + b, y = startY + b}
            cell = cell + 1
            b = b + 1
        end
        startY = startY - 1
        a = a + 1
    end
end

function CellidToCoordFlaty(cellId)
    local account = fight._account
    local game = account.Game
    local map = game.Map
    local mapData = map.Data

    return {x = mapData.Cells[cellId].X, y = mapData.Cells[cellId].Y}
end

function CellidToCoord(cellid)
    if (not(CellArrayInit == 1)) then
        InitCellsArray()
    end
    CellArrayInit = 1
    if (IsValidCellid(cellid)) then
        return CellArray[cellid]
    end
    return -1
end

function CoordToCellid(coord) -- Ankama version
    return math.floor((((coord.x - coord.y) * 14) + coord.y) + ((coord.x - coord.y) / 2))
end

function IsValidCellid(cellid)
    return (cellid >= 0 and cellid < 560)
end

function Manhattan_distance(fromCellid, toCellid)
    local fromCoord = CellidToCoord(fromCellid)
    local toCoord = CellidToCoord(toCellid)

    return (math.abs(toCoord.x - fromCoord.x) + math.abs(toCoord.y - fromCoord.y))
end

function GetTargetsInRange(cellId, range)
    local myCellId = fighter:getCellId()
    local entities = fight:getAllEntities()
    local targetsInRange = {}
    for i = 0, fight:getEntitiesCount() - 1 do
        if (Manhattan_distance(cellId, entities[i].CellId) <= range) then
            table.insert(targetsInRange, entities[i])
        end
    end
    table.sort(targetsInRange, function(a, b)
            return (fight:getDistance(cellId, a.CellId) < fight:getDistance(cellId, b.CellId))
        end)
    return targetsInRange
end

function TryAttack(targets, spell)
    for i, v in ipairs(targets) do
        local spellReturn = fight:launchSpellInCell(v.CellId, spell.name)
        if spellReturn == 1 then
            return 1
        end
    end
    return -1
end

function getPositionsWithLOSInRange(cellid, range)
    local account = fight._account
    local game = account.Game
    local map = game.Map
    local mapData = map.Data
    local fightGame = game.Fight
    local coord = CellidToCoordFlaty(cellid)
    local ret = {}
    for i = -range, range do
        for j = -range, range do
            local newCoord = {x = coord.x + i, y = coord.y + j}
            if math.abs(newCoord.x - coord.x) + math.abs(newCoord.y - coord.y) <= range then
                local newCellid = CoordToCellid(newCoord)
                if IsValidCellid(newCellid) and fight:getDistance(fighter:getCellId(), newCellid) <= fighter:getMP() and fightGame:IsCellFree(newCellid) and mapData:IsWalkable(newCellid) and hasLineOfSight(newCellid, cellid) then
                    table.insert(ret, newCellid)
                end
            end
        end
    end
    table.sort(ret, function(a, b)
            return (fight:getDistance(fighter:getCellId(), a) < fight:getDistance(fighter:getCellId(), b))
        end)
    return ret
end

function hasLineOfSight(fromCellid, toCellid)
    local account = fight._account
    local game = account.Game
    local map = game.Map
    local fightGame = game.Fight
    local mapData = map.Data
    local cellsToCheck = getCellsIdBetween(fromCellid, toCellid)
    for i, v in ipairs(cellsToCheck) do
        if not(mapData:IsLineOfSight(v)) or (not(fightGame:IsCellFree(v)) and v ~= toCellid)then
            return false
        end
    end
    return true
end

function Main()
    local account = fight._account
    local game = account.Game
    local character = game.Character
    local stats = character.Stats
    local targetsInRange = GetTargetsInRange(fighter:getCellId(), Spells.FlecheMagique.maxRange + stats.Range.Total)
    local targetsInPotentialRange = GetTargetsInRange(fighter:getCellId(), Spells.FlecheMagique.maxRange + stats.Range.Total + fighter:getMP()) -- no way to retrieve actual PO with API. bug
    local keepTrying = true

    while keepTrying do
        targetsInRange = GetTargetsInRange(fighter:getCellId(),  Spells.FlecheMagique.maxRange + stats.Range.Total)
        targetsInPotentialRange = GetTargetsInRange(fighter:getCellId(),  Spells.FlecheMagique.maxRange + stats.Range.Total + fighter:getMP())
        if (#targetsInPotentialRange == 0) then
            fight:moveToWardCell(fight:getNearestEnemy())
            keepTrying = false
        elseif (#targetsInPotentialRange > 0 and #targetsInRange == 0) then
            local posWithLOSInRange = getPositionsWithLOSInRange(targetsInPotentialRange[1].CellId,  Spells.FlecheMagique.maxRange + stats.Range.Total)
            fight:moveToCell(posWithLOSInRange[1])
        elseif (#targetsInRange > 0) then
            if TryAttack(targetsInRange, Spells.FlecheMagique) == -1 then
                if (fighter:getMP() == 0) then
                    keepTrying = false
                else
                    local posWithLOSInRange = getPositionsWithLOSInRange(targetsInRange[1].CellId,  Spells.FlecheMagique.maxRange + stats.Range.Total)
                    if (#posWithLOSInRange > 0) then
                        util:debug(tostring(#posWithLOSInRange))
                        fight:moveToCell(posWithLOSInRange[1])
                    else
                        keepTrying = false
                    end
                end
            end
        else
            keepTrying = false
        end
        if (fighter:getAP() < 3) then
            keepTrying = false
        end
    end
    if (fighter:getMP() > 0) then
        fight:moveFarthestCell(fight:getNearestEnemy())
    end
    fighter:passTurn()
end

Edit : À priori, la méthode
Code:
fight:test(spellName, fromCellid, toCellid)
Permettrait de faire quelque chose d'équivalent plus facilement et en invoquant moins de code que pour
Code:
hasLineOfSight(fromCellid, toCellid)

Merci de lire ceci :
Lien vers mon dernier post
 

Attachments

  • LOSseeker.lua
    6.7 KB · Views: 544
  • LOSseeker3.lua
    4.3 KB · Views: 819
Last edited:
  • Like
  • Love
  • Wow
Reactions: 99.99%, Ismanya, raxar and 3 others

Lucide

Membre actif
Sep 2, 2010
228
3,313
1,009
PhotonPay
Discord
lucide0001
Excellent travail, félicitations !
Concernant ta question sur isVisible, je pensais - à tord ou à raison, que la valeur retournée correspondait à si l'entité était visible ou invisible (ie. Invisibilité du Sram).
 

Lmoony

Membre
Apr 7, 2019
28
56
214
30
Version 2 de LOSseeker.
  • Légèrement plus lente au calcul que la dernière, elle explore néanmoins plus de possibilités.
  • Généralement plus simple à la compréhension et nettement plus facilement adaptable a d'autres classes/sorts
  • Compatible avec FlatyCloud.
Elle utilise a fond le potentiel de la fonction fight:test() qui, elle, a l'avantage de manipuler tout un tas de donnée inhérentes aux sorts et auxquelles nous n'avons pas accès.

Pour le moment, elle est particulièrement lente lorsqu'aucun ennemi n'est possiblement attaquable dans le tour courant.
En effet, elle tentera de calculer toutes les positions possibles (a portée de déplacement) pour attaquer une cibles a l'aide la fonction fight:test, jusqu'a en trouver une valide. Si elle n'en trouve aucune pour l'entitée testée, elle va continuer avec l'entitée suivante.
La fonction fight:test étant relativement lourde en calcul, ceci explique donc cela.

Il existe cependant des solutions.
Par exemple prendre en compte la portée du sort pour ainsi éviter des calculs inutiles si l'ennemi n'est pas a portée.
Cela réduirait grandement l'adaptabilité du code ci-dessus et c'est pourquoi je ne l'ai pas fait.
Libre a vous de vous y essayer.

S'il était possible de connaître certaines informations à propos du sort comme la portée minimum ou maximum, si lancé en ligne, s'il nécessite la ligne de vue,... Il serait possible d'éviter d'invoquer la fonction fight:test a des endroit où elle n'a pas lieu d'être appelée et ainsi éviter énormément de calculs inutiles

N'hésitez pas à me partager vos impressions/constats de bugs,...

Edit : La fonction fight:test ne prenant pas en compte les buff/debuff de PO. Lors d'un débuff, il est possible que l'IA se retrouve bloquée hors de portée, convaincue de pouvoir tirer depuis sa position.
En cas de buff, elle n'en profite jamais.
En attendant que le soucis soit réglé, préférez les sorts à portée non-modifiable.
 
Last edited:
  • Like
  • Love
Reactions: Doomsday123, Ariadne21, Dauradeau and 5 others

Lmoony

Membre
Apr 7, 2019
28
56
214
30
J'ai upload une version moins lourde niveau calcul pour le serveur. Merci de télécharger LOSseeker3 plutot.

Ici, pas de grosse différence avec la précédente version, si ce n'est qu'elle applique des filtres afin de ne pas calculer trop longtemps. Et donc, alléger la charge serveur.


Code:
SpellToUse = "Fleche Magique"
SpellRange = 8

Changer ces premieres lignes avec le sort que vous voulez. 2 contraintes temporaires cependant.
  • Si vous risquez de croiser des monstres qui peuvent retirer de la portee, merci de choisir un sort a portee non-modifiable. Sinon le bot risque de bloquer certains tours.
  • Le sort que vous entrez ici ne doit pas avoir de portee minimum. Comme fleche magique, par exemple
Enfin, pensez bien a changer cette ligne avec la bonne valeur.
Code:
DofusClass="9" -- CRA

Code:
DofusClass="9" -- CRA

CellArray = {}
CellArrayInit = 0
SpellToUse = "Fleche Magique"
SpellRange = 8

function InitCellsArray()
    local b = 0
    local startX = 0
    local startY = 0
    local cell = 0
    local a = 0

    while (a < 20) do
        b = 0
        while (b < 14) do
            CellArray[cell] = {x = startX + b, y = startY + b}
            cell = cell + 1
            b = b + 1
        end
        startX = startX + 1
        b = 0
        while (b < 14) do
            CellArray[cell] = {x = startX + b, y = startY + b}
            cell = cell + 1
            b = b + 1
        end
        startY = startY - 1
        a = a + 1
    end
end

function CellIdToCoord(cellId)
    if (not(CellArrayInit == 1)) then
        InitCellsArray()
    end
    CellArrayInit = 1
    if (IsCellIdValid(cellId)) then
        return CellArray[cellId]
    end
    return nil
end

function TableFilter(tbl, func)
     local newtbl= {}
     for i, v in pairs(tbl) do
         if func(v) then
             table.insert(newtbl, v)
         end
     end
     return newtbl
 end

function CoordToCellId(coord)
    return math.floor((((coord.x - coord.y) * 14) + coord.y) + ((coord.x - coord.y) / 2))
end

function IsCellIdValid(cellId)
    return (cellId >= 0 and cellId < 560)
end

function ManhattanDistanceCellId(fromCellId, toCellId)
    local fromCoord = CellIdToCoord(fromCellId)
    local toCoord = CellIdToCoord(toCellId)
    return (math.abs(toCoord.x - fromCoord.x) + math.abs(toCoord.y - fromCoord.y))
end

function ManhattanDistanceCoord(fromCoord, toCoord)
    return (math.abs(toCoord.x - fromCoord.x) + math.abs(toCoord.y - fromCoord.y))
end

function GetAllEntitiesArray()
    local entities = fight:getAllEntities()
    local ret = {}

    for i = 0, fight:getEntitiesCount() -1 do
        table.insert(ret, entities[i])
    end
    return ret
end

function GetReachableCellIds()
    local coord = CellIdToCoord(fighter:getCellId())
    local mp = fighter:getMP()
    local ret = {}

    for i = -mp, mp do
        local d = mp - math.abs(i)
        if d == 0 then
            local newCoord = {x = coord.x + i, y = coord.y}
            local newCellId = CoordToCellId(newCoord)
            if IsCellIdValid(newCellId) then
                table.insert(ret, newCellId)
            end
        else
            for j = d * -1, d do
                local newCoord = {x = coord.x + i, y = coord.y + j}
                local newCellId = CoordToCellId(newCoord)
                if IsCellIdValid(newCellId) then
                    table.insert(ret, newCellId)
                end
            end
        end
    end
    return ret
end

function GetCellIdToAttackFrom(cellIdToAttack, spellName, spellMaxRange)
    local reachableCellIds = GetReachableCellIds()

    reachableCellIds = TableFilter(reachableCellIds, function(a)
        return IsCellIdValid(a) and IsCellIdValid(cellIdToAttack) and ManhattanDistanceCellId(a, cellIdToAttack) <= spellMaxRange + fighter:getRange()
    end)
    if #reachableCellIds < 1 then
        return nil
    end
    if #reachableCellIds >= 2 then
        table.sort(reachableCellIds, function(a, b)
            return (fight:getDistance(fighter:getCellId(), a) < fight:getDistance(fighter:getCellId(), b))
        end)
    end
    for _, v in ipairs(reachableCellIds) do
        if fight:test(spellName, v, cellIdToAttack) then
            return v
        end
    end
    return nil
end

function PickATarget(entitiesToPickFrom, spellName, spellMaxRange)
    local filteredEntities = TableFilter(entitiesToPickFrom, function(a)
        return IsCellIdValid(a.CellId) and ManhattanDistanceCellId(fighter:getCellId(), a.CellId) <= spellMaxRange + fighter:getRange() + fighter:getMP()
    end)
    if #filteredEntities < 1 then
        return nil
    end
    if #filteredEntities >= 2 then
        table.sort(filteredEntities, function(a, b)
            return (fight:getDistance(fighter:getCellId(), a.CellId) < fight:getDistance(fighter:getCellId(), b.CellId))
        end)
    end
    for _, v in ipairs(filteredEntities) do
        local bestOption = GetCellIdToAttackFrom(v.CellId, spellName, spellMaxRange)
        if bestOption ~= nil then
            return v, bestOption
        end
    end
    return nil
end

function Main()
    local target
    local bestOption
    while true do
        target, bestOption = PickATarget(GetAllEntitiesArray(), SpellToUse, SpellRange)
        if target == nil then
            fight:moveToWardCell(fight:getNearestEnemy())
            break
        else
            if bestOption ~= fighter:getCellId() then
                fight:moveToCell(bestOption)
            end
            fight:launchSpellInCell(target.CellId, SpellToUse)
        end
        if (fighter:getAP() < 3) then
            break
        end
    end
    fight:moveFarthestCell(fight:getNearestEnemy())
    fighter:passTurn()
end
 
Last edited:
  • Love
  • Like
Reactions: kerlson and maxime-59244

MrBahsco

Membre
Mar 24, 2012
22
15
904
30
Line Of Sight Seeker

Ces derniers jours je me suis un peu amusé avec l'API de flaty et ses possibilités cachées.

Mon but
Donner la capacité au bot d'utiliser plus intelligemment ses PM's en adéquation avec les lignes de vues existantes.
Donc je souhaitais faire un bot qui, s'il a, à sa portée une case depuis laquelle il peut taper, il s'y rendra et utilisera un sort, ici, flèche magique.
Puis reculera d'autant de PM qu'il lui reste.

Le bot a ainsi moins de chance de tourner en rond sur des maps truffée d'obstacles pour la ligne de vue.
Il bénéficie également d'un meilleur maintient de la distance. Ne se déplaçant que du nombre de case requise pour taper, il économise ses PM afin de reculer lorsque son tour se termine

Cela ne semble pas grand chose, mais en fait, c'est un comportement déjà radicalement moins aléatoire et plus stable qu'une IA basique.

Une IA basique classiquement essaye d'utiliser un sort et si elle n'y arrive pas, établit un chemin jusqu'à l'entité la plus proche, s'y engouffre de tous ses PMs et re-tente une attaque.
Et ce, même si en reculant d'une case elle pouvais trouver une ligne de vue très avantageuse.

La mienne, en revanche, cherchera le chemin de poids le plus faible vers une ligne de vue disponible a portée de déplacement.

A priori, je pensais que la méthode
Code:
fight:isVisible(fromCellId, toCellId, isDiagonal)
Servait a dire si oui ou non la ligne de vue était présente entre deux cellId. Mais ce n'est visiblement pas le cas. A vrai dire, je ne sais pas trop a quoi sert cette fonction.

J'ai donc développé la mienne :
Code:
function floatAlmostEquals(a, b)
    if (a ~= b) then
        return math.abs(a - b) < 0.0001
    end
    return true
end

function getCellsIdBetween(from, to)
    if from == to then
        return {}
    end
    if not(IsValidCellid(from)) or not(IsValidCellid(to)) then
        return {}
    end
    local fromCoord = CellidToCoord(from)
    local toCoord = CellidToCoord(to)
    local deltaX = toCoord.x - fromCoord.x
    local deltaY = toCoord.y - fromCoord.y
    local t = math.sqrt(deltaX * deltaX + deltaY * deltaY)
    local tDeltaX = deltaX / t
    local tDeltaY = deltaY / t
    local stepX
    if tDeltaX < 0 then
        stepX = -1
    else
        stepX = 1
    end
    local stepY
    if tDeltaY < 0 then
        stepY = -1
    else
        stepY = 1
    end
    local MaxX = 0.5 * math.abs(1 / tDeltaX)
    local MaxY = 0.5 * math.abs(1 / tDeltaY)
    local startX = fromCoord.x
    local startY = fromCoord.y
    local endX = toCoord.x
    local endY = toCoord.y
    local ret = {}
    while startX ~= endX or startY ~= endY do
        if floatAlmostEquals(MaxX, MaxY) then
            MaxX = MaxX + 0.5 * math.abs(1 / tDeltaX)
            MaxY = MaxY + 0.5 * math.abs(1 / tDeltaY)
            startX = startX + stepX
            startY = startY + stepY
        elseif MaxX < MaxY then
            MaxX = MaxX + 0.5 * math.abs(1 / tDeltaX)
            startX = startX + stepX
        else
            MaxY = MaxY + 0.5 * math.abs(1 / tDeltaY)
            startY = startY + stepY
        end
    table.insert(ret, CoordToCellid({x = startX, y = startY}))
    end
    return ret
end

function hasLineOfSight(fromCellid, toCellid)
    local account = fight._account
    local game = account.Game
    local map = game.Map
    local fightGame = game.Fight
    local mapData = map.Data
    local cellsToCheck = getCellsIdBetween(fromCellid, toCellid)
    for i, v in ipairs(cellsToCheck) do
        if not(mapData:IsLineOfSight(v)) or (not(fightGame:IsCellFree(v)) and v ~= toCellid)then
            return false
        end
    end
    return true
end
Cette IA ne joue qu'avec flèche magique. Et nécessite d'entrer manuellement la PO max totale du sort (avec les boosts d'équipements donc) Ici :
Code:
local Spells = {
    FlecheMagique = {
        name    = 'Fleche Magique',
        minRange    = 1,
        maxRange    = 8, <-- HERE
        inLine        = false,
    },
}
(Les méthodes de l'API permettant de récupérer la PO actuelle ne fonctionnant pas, on est obligé de faire ainsi)

Naturellement, n'hésitez pas a me poser des questions ou me rapporter des bugs éventuels

Les idées d'amélioration fusent dans ma tête, et elles sont bien sur, innombrables. Il ne s'agit là que d'un premier jet.
Malheureusement, la motivation seule ne fera pas tout car l'on reste fort limité par le manque d'information procurée par l'API quant à l'environnement du jeu.

Je poste ici le code complet, s'il y en a que cela intéresse.
Code:
DofusClass="9" -- CRA
CellArray = {}
CellArrayInit = 0

local Spells = {
    FlecheMagique = {
        name    = 'Fleche Magique',
        minRange    = 1,
        maxRange    = 8,
        inLine        = false,
    },
}

function floatAlmostEquals(a, b)
    if (a ~= b) then
        return math.abs(a - b) < 0.0001
    end
    return true
end

function getCellsIdBetween(from, to)
    if from == to then
        return {}
    end
    if not(IsValidCellid(from)) or not(IsValidCellid(to)) then
        return {}
    end
    local fromCoord = CellidToCoord(from)
    local toCoord = CellidToCoord(to)
    local deltaX = toCoord.x - fromCoord.x
    local deltaY = toCoord.y - fromCoord.y
    local t = math.sqrt(deltaX * deltaX + deltaY * deltaY)
    local tDeltaX = deltaX / t
    local tDeltaY = deltaY / t
    local stepX
    if tDeltaX < 0 then
        stepX = -1
    else
        stepX = 1
    end
    local stepY
    if tDeltaY < 0 then
        stepY = -1
    else
        stepY = 1
    end
    local MaxX = 0.5 * math.abs(1 / tDeltaX)
    local MaxY = 0.5 * math.abs(1 / tDeltaY)
    local startX = fromCoord.x
    local startY = fromCoord.y
    local endX = toCoord.x
    local endY = toCoord.y
    local ret = {}
    while startX ~= endX or startY ~= endY do
        if floatAlmostEquals(MaxX, MaxY) then
            MaxX = MaxX + 0.5 * math.abs(1 / tDeltaX)
            MaxY = MaxY + 0.5 * math.abs(1 / tDeltaY)
            startX = startX + stepX
            startY = startY + stepY
        elseif MaxX < MaxY then
            MaxX = MaxX + 0.5 * math.abs(1 / tDeltaX)
            startX = startX + stepX
        else
            MaxY = MaxY + 0.5 * math.abs(1 / tDeltaY)
            startY = startY + stepY
        end
    table.insert(ret, CoordToCellid({x = startX, y = startY}))
    end
    return ret
end

function InitCellsArray()
    local b = 0
    local startX = 0
    local startY = 0
    local cell = 0
    local a = 0

    while (a < 20) do
        b = 0
        while (b < 14) do
            CellArray[cell] = {x = startX + b, y = startY + b}
            cell = cell + 1
            b = b + 1
        end
        startX = startX + 1
        b = 0
        while (b < 14) do
            CellArray[cell] = {x = startX + b, y = startY + b}
            cell = cell + 1
            b = b + 1
        end
        startY = startY - 1
        a = a + 1
    end
end

function CellidToCoordFlaty(cellId)
    local account = fight._account
    local game = account.Game
    local map = game.Map
    local mapData = map.Data

    return {x = mapData.Cells[cellId].X, y = mapData.Cells[cellId].Y}
end

function CellidToCoord(cellid)
    if (not(CellArrayInit == 1)) then
        InitCellsArray()
    end
    CellArrayInit = 1
    if (IsValidCellid(cellid)) then
        return CellArray[cellid]
    end
    return -1
end

function CoordToCellid(coord) -- Ankama version
    return math.floor((((coord.x - coord.y) * 14) + coord.y) + ((coord.x - coord.y) / 2))
end

function IsValidCellid(cellid)
    return (cellid >= 0 and cellid < 560)
end

function Manhattan_distance(fromCellid, toCellid)
    local fromCoord = CellidToCoord(fromCellid)
    local toCoord = CellidToCoord(toCellid)

    return (math.abs(toCoord.x - fromCoord.x) + math.abs(toCoord.y - fromCoord.y))
end

function GetTargetsInRange(cellId, range)
    local myCellId = fighter:getCellId()
    local entities = fight:getAllEntities()
    local targetsInRange = {}
    for i = 0, fight:getEntitiesCount() - 1 do
        if (Manhattan_distance(cellId, entities[i].CellId) <= range) then
            table.insert(targetsInRange, entities[i])
        end
    end
    table.sort(targetsInRange, function(a, b)
            return (fight:getDistance(cellId, a.CellId) < fight:getDistance(cellId, b.CellId))
        end)
    return targetsInRange
end

function TryAttack(targets, spell)
    for i, v in ipairs(targets) do
        local spellReturn = fight:launchSpellInCell(v.CellId, spell.name)
        if spellReturn == 1 then
            return 1
        end
    end
    return -1
end

function getPositionsWithLOSInRange(cellid, range)
    local account = fight._account
    local game = account.Game
    local map = game.Map
    local mapData = map.Data
    local fightGame = game.Fight
    local coord = CellidToCoordFlaty(cellid)
    local ret = {}
    for i = -range, range do
        for j = -range, range do
            local newCoord = {x = coord.x + i, y = coord.y + j}
            if math.abs(newCoord.x - coord.x) + math.abs(newCoord.y - coord.y) <= range then
                local newCellid = CoordToCellid(newCoord)
                if IsValidCellid(newCellid) and fight:getDistance(fighter:getCellId(), newCellid) <= fighter:getMP() and fightGame:IsCellFree(newCellid) and mapData:IsWalkable(newCellid) and hasLineOfSight(newCellid, cellid) then
                    table.insert(ret, newCellid)
                end
            end
        end
    end
    table.sort(ret, function(a, b)
            return (fight:getDistance(fighter:getCellId(), a) < fight:getDistance(fighter:getCellId(), b))
        end)
    return ret
end

function hasLineOfSight(fromCellid, toCellid)
    local account = fight._account
    local game = account.Game
    local map = game.Map
    local fightGame = game.Fight
    local mapData = map.Data
    local cellsToCheck = getCellsIdBetween(fromCellid, toCellid)
    for i, v in ipairs(cellsToCheck) do
        if not(mapData:IsLineOfSight(v)) or (not(fightGame:IsCellFree(v)) and v ~= toCellid)then
            return false
        end
    end
    return true
end

function Main()
    local account = fight._account
    local game = account.Game
    local character = game.Character
    local stats = character.Stats
    local targetsInRange = GetTargetsInRange(fighter:getCellId(), Spells.FlecheMagique.maxRange + stats.Range.Total)
    local targetsInPotentialRange = GetTargetsInRange(fighter:getCellId(), Spells.FlecheMagique.maxRange + stats.Range.Total + fighter:getMP()) -- no way to retrieve actual PO with API. bug
    local keepTrying = true

    while keepTrying do
        targetsInRange = GetTargetsInRange(fighter:getCellId(),  Spells.FlecheMagique.maxRange + stats.Range.Total)
        targetsInPotentialRange = GetTargetsInRange(fighter:getCellId(),  Spells.FlecheMagique.maxRange + stats.Range.Total + fighter:getMP())
        if (#targetsInPotentialRange == 0) then
            fight:moveToWardCell(fight:getNearestEnemy())
            keepTrying = false
        elseif (#targetsInPotentialRange > 0 and #targetsInRange == 0) then
            local posWithLOSInRange = getPositionsWithLOSInRange(targetsInPotentialRange[1].CellId,  Spells.FlecheMagique.maxRange + stats.Range.Total)
            fight:moveToCell(posWithLOSInRange[1])
        elseif (#targetsInRange > 0) then
            if TryAttack(targetsInRange, Spells.FlecheMagique) == -1 then
                if (fighter:getMP() == 0) then
                    keepTrying = false
                else
                    local posWithLOSInRange = getPositionsWithLOSInRange(targetsInRange[1].CellId,  Spells.FlecheMagique.maxRange + stats.Range.Total)
                    if (#posWithLOSInRange > 0) then
                        util:debug(tostring(#posWithLOSInRange))
                        fight:moveToCell(posWithLOSInRange[1])
                    else
                        keepTrying = false
                    end
                end
            end
        else
            keepTrying = false
        end
        if (fighter:getAP() < 3) then
            keepTrying = false
        end
    end
    if (fighter:getMP() > 0) then
        fight:moveFarthestCell(fight:getNearestEnemy())
    end
    fighter:passTurn()
end

Edit : À priori, la méthode
Code:
fight:test(spellName, fromCellid, toCellid)
Permettrait de faire quelque chose d'équivalent plus facilement et en invoquant moins de code que pour
Code:
hasLineOfSight(fromCellid, toCellid)

Merci de lire ceci :
Lien vers mon dernier post


Il y aurait t'il moyen de gérer le cas ou un mob est eu CaC et nous tacle ? Car pour le moment tu pars du principe que tu de-tacles l'ennemie.
Admettons ce n'est pas le cas et on est bloqué au CaC du mob. Pourrais tu créer une petite fonction "fleche de recul + move_dans_la_direction_opposee" ?
Où du moins indiquer où dans le script rajouter ça ? Ton script est trop avancé pour mon petit niveau de compréhension ^^'
Merci chef ! A par ça ton script fonctionne pas mal du tout :p
 

Ismanya

Membre
Aug 4, 2019
20
23
214
Discord
Coucou#6221
Il y aurait t'il moyen de gérer le cas ou un mob est eu CaC et nous tacle ? Car pour le moment tu pars du principe que tu de-tacles l'ennemie.
Admettons ce n'est pas le cas et on est bloqué au CaC du mob. Pourrais tu créer une petite fonction "fleche de recul + move_dans_la_direction_opposee" ?
Où du moins indiquer où dans le script rajouter ça ? Ton script est trop avancé pour mon petit niveau de compréhension ^^'
Merci chef ! A par ça ton script fonctionne pas mal du tout :p

Pour le coup je pense que tu peux rajouter une fonction simple genre :
function SpellFlecheDeReculEnnemi14
if (fighter:isHandToHand())) and (fight:getCurrentTour() == 1 or fight:getCurrentTour() % 1 == 0) then
fight:launchSpellInCell(fight:getNearestEnemy(), "Fleche de Recul") (ou Liberation selon tes goûts)
end
end
 
  • Love
Reactions: Bykiwi2711

MrBahsco

Membre
Mar 24, 2012
22
15
904
30
Pour le coup je pense que tu peux rajouter une fonction simple genre :
JavaScript:
function SpellFlecheDeReculEnnemi14()
    if (fighter:isHandToHand())) and (fight:getCurrentTour() == 1 or fight:getCurrentTour() % 1 == 0) then
        fight:launchSpellInCell(fight:getNearestEnemy(), "Fleche de Recul") (ou Liberation selon tes goûts)
    end
end

Super ! Et j'appelle où la fonction ? Dans le main ? Dans une autre sous fonction?
 

MrBahsco

Membre
Mar 24, 2012
22
15
904
30
Super ! Et j'appelle où la fonction ? Dans le main ? Dans une autre sous fonction?
:up:

Fix apporté par Lmoony en MP !

Pour ceux que ça intéresse, rempalcez le Main() par celui-ci :
JavaScript:
function Main()
    local target
    local bestOption
    local nearest = fight:getNearestEnemy()

    if nearest ~= nil and nearest ~= -1 then
        if fighter:isHandToHand() then
            fight:launchSpellInCell(nearest, "Fleche de Recul")
        end
    end
    while true do
        target, bestOption = PickATarget(GetAllEntitiesArray(), SpellToUse, SpellRange)
        if target == nil then
            fight:moveToWardCell(fight:getNearestEnemy())
            break
        else
            if bestOption ~= fighter:getCellId() then
                fight:moveToCell(bestOption)
            end
            fight:launchSpellInCell(target.CellId, SpellToUse)
        end
        if (fighter:getAP() < 3) then
            break
        end
    end
    fight:moveFarthestCell(fight:getNearestEnemy())
    fighter:passTurn()
end
Voila, ça devrait fonctionner ! :bravo:
 
Last edited:

kojejerome

Nouveau membre
Jul 10, 2019
4
0
201
27
Bonjour, tout d'abord merci pour l'IA, c'est super sympas! J'aurais une simple question, est il possible d'ajouter un sort à lancer en + que 'fleche magique', genre de temps en temps lance 'fleche de recul' ? Ou ça va impliquer un gros changement dans les functions? Merci.
 

Lmoony

Membre
Apr 7, 2019
28
56
214
30
Les autres IA que j'ai écrites le font.
SpellPriorityAI utilises les sorts que tu veux pour la classe que tu veux
SpellZoneAI utilises les sorts que tu veux pour la classe que tu veux et possède une gestion intelligente des sorts de zones

Ces 2 autres IA ne sont rien d'autre que les versions améliorées de celle-ci.

En revanche, il est nécessaire de posséder un accès à FlatyCloud pour utiliser ces IA.
Si cela t'intéresse je t'invite à te rendre sur le discord du bot (section rejoindre-le-cloud)

Si malgré cela, tu veux rester sur cette version absolument, je t'indique qu'il est largement possible de faire fonctionner cette IA avec plusieurs sorts (moyennant quelques changements). En revanche, je ne puis m'en occuper moi-même pour le moment. Il faudra donc le faire toi-même ou t'armer de patience et me le rappeler dans quelques semaines.
 
Last edited:

kojejerome

Nouveau membre
Jul 10, 2019
4
0
201
27
Les autres IA que j'ai écrites le font.
SpellPriorityAI utilises les sorts que tu veux pour la classe que tu veux
SpellZoneAI utilises les sorts que tu veux pour la classe que tu veux et possède une gestion intelligente des sorts de zones

C'est 2 autres IA ne sont rien d'autre que les versions améliorées de celle-ci.

En revanche, il est nécessaire de posséder un accès à FlatyCloud pour utiliser ces IA.
Si cela t'intéresse je t'invite à te rendre sur le discord du bot (section rejoindre-le-cloud)

Si malgré cela, tu veux rester sur cette version absolument, je t'indique qu'il est largement possible de faire fonctionner cette IA avec plusieurs sorts (moyennant quelques changements). En revanche, je ne puis m'en occuper moi-même pour le moment. Il faudra donc le faire toi-même ou t'armer de patience et me le rappeler dans quelques semaines.
Merci de ta réponse rapide, au plaisir de voir des personnes aussi impliqué et à l'écoute, pour le moment je vais me pencher sur FlatyCloud, mais à coté de cela, j'ai hâte de potasser un peu plus le lua pour ouvrir d'autre possibilité personnel, ( je dois faire le tour des fonctions de Flaty, et l'environnement de dofus ) , j'aimerai mettre mon nez dedans avec passion surtout ( j'adore me creuser la tête ) , en attendant j'ai déjà les bases sur le développement web (php,js), j'admire ton taf, j'aimerai en faire autant sur le lua !
 

Desert593

Membre
Apr 2, 2020
7
2
23
30
Discord
Eazy-Freak #1434
Salut,
Comment on se sert de l'IA ? je l'ai mis dans Flaty dans l'onglet combat, mais mon crâ n'attaque pas :/
 

sulfatiix

Membre
Apr 5, 2020
19
28
4
25
Super script, il est parfait pour les monstres de métier même pour un bas niveau, il gère très bien.
J'ai seulement rajouté un deuxième passage de tour et j'ai l'impression que ça règle le problème de lorsqu'il ne passe pas son tour.
En tout cas merci beaucoup parce que c'est du boulot ton script !
Edit: @malusdefou tu peux télécharger LOSseeker.lua, j'ai la version normale de flatybot et ca fonctionne bien
 
Last edited:

kalysipert

Nouveau membre
May 31, 2014
1
0
421
Salut! petite question, j'aimerais bien adapter ton script pour un lancer de pièce de l'enu, malheureusement, il n'attaque que 2 fois (au lieu de 3 avec 6pa). Pourrais-tu m'indiquer la ligne de code à changer ? (j'ai beau chercher j'ai du mal à m'y retrouver ..)
Merci!
 

layona

Membre
Jan 27, 2015
18
0
562
Hello ! J'ai essayé ton IA mais je rencontre un soucis. Lorsque le mob arrive au cac mon personnage ne cesse de tourner autour de lui, de passer ses tours. Je test ça sur un cra air fleche magique.
Post automatically merged:

Hello !

Après avoir regardé ton code de plus pret, je viens de remarquer que lorsqu'il ne reste plus qu'un seul mob dans le combat, la valeur de target passe à nil (Alors que normalement elle devrait être à 1.

tu pourrais me dire comment changer la valeur svp ?
Post automatically merged:

 
Last edited:
Thread starter Similar threads Forum Replies Date
S Intelligence artificielle 0
Ismanya Intelligence artificielle 0