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)
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
Code:
local Spells = {
FlecheMagique = {
name = 'Fleche Magique',
minRange = 1,
maxRange = 8, <-- HERE
inLine = false,
},
}
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)
Code:
hasLineOfSight(fromCellid, toCellid)
Merci de lire ceci : Lien vers mon dernier post
Attachments
Last edited: