Skip to content

Commit

Permalink
persist forbidden edges in a tinydb json file
Browse files Browse the repository at this point in the history
  • Loading branch information
Hexalgo committed Nov 30, 2024
1 parent 043c57c commit 9fa5a91
Show file tree
Hide file tree
Showing 10 changed files with 403 additions and 64 deletions.
2 changes: 1 addition & 1 deletion pydofus2/com/DofusClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def onInGame(self):

def crash(self, event, message, reason=DisconnectionReasonEnum.EXCEPTION_THROWN):
KernelEventsManager().send(
KernelEvent.ClientStatusUpdate, ClientStatusEnum.CRASHED, {"reason": reason, "message": message}
KernelEvent.ClientStatusUpdate, ClientStatusEnum.CRASHED, {"reason": str(reason), "message": message}
)
self._crashed = True
self._shutdownReason = reason
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def __repr__(self):

if __name__ == "__main__":
# cr = GroupItemCriterion('((Qo>3613&PO<11044,1&Qo<3597)')
cr = GroupItemCriterion("QF>1423,0")
print(cr)
cr = GroupItemCriterion("(Qo>3613&PO<11044,1&Qo<3597)")
print(cr.text)
print(cr.operators)
print("number of criterias ", len(cr.criteria))
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ def text(self) -> str:
readableCriterionValue: str = str(self._criterionValue)
readableCriterionRef: str = I18n.getUiText("ui.common.level")
if self._operator.text == ItemCriterionOperator.SUPERIOR:
return I18n.getUiText("ui.common.minimumLevelCondition", [(self._criterionValue + str(1))])
return I18n.getUiText("ui.common.minimumLevelCondition", [(self._criterionValue + 1)])
if self._operator.text == ItemCriterionOperator.INFERIOR:
return I18n.getUiText("ui.common.maximumLevelCondition", [(self._criterionValue - str(1))])
return I18n.getUiText("ui.common.maximumLevelCondition", [(self._criterionValue - 1)])
return readableCriterionRef + " " + self._operator.text + " " + readableCriterionValue

def clone(self) -> IItemCriterion:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import heapq
from typing import List, Union
import os
import threading
import time
from typing import Callable, List, Optional, Union

from tinydb import TinyDB

from pydofus2.com.ankamagames.dofus.datacenter.world.MapPosition import MapPosition
from pydofus2.com.ankamagames.dofus.kernel.Kernel import Kernel
from pydofus2.com.ankamagames.dofus.modules.utils.pathFinding.world.Edge import Edge
from pydofus2.com.ankamagames.dofus.modules.utils.pathFinding.world.MapMemoryManager import MapMemoryManager
Expand All @@ -14,10 +18,13 @@
from pydofus2.com.ankamagames.jerakine.pathfinding.Pathfinding import PathFinding
from pydofus2.com.ankamagames.jerakine.types.positions.MapPoint import MapPoint

__dir__ = os.path.dirname(os.path.abspath(__file__))
FORBIDDEN_EDGES_FILE = os.path.join(__dir__, "forbidden_edges.json")
FORBIDDEN_EDGES_LOCK = threading.Lock()


class AStar(metaclass=Singleton):
DEBUG = False
_forbiddenSubareaIds = list[int]()
_forbiddenEdges = list[Edge]()
HEURISTIC_SCALE: int = 1
INDOOR_WEIGHT: int = 0
Expand All @@ -29,22 +36,47 @@ def __init__(self):
self.openList = list[Node]()
self.openDic = dict()
self.iterations: int = 0
self.worldGraph: WorldGraph = None
self.worldGraph = WorldGraph()
self.destinations: set[Vertex] = None
self.running = None

def addForbiddenEdge(self, edge: Edge) -> None:
self._forbiddenEdges.append(edge)

def resetForbiddenEdges(self) -> None:
self._forbiddenEdges.clear()
self.db = TinyDB(FORBIDDEN_EDGES_FILE)
self.edges_table = self.db.table("forbidden_edges")
self._initialize_forbidden_edges()

def _initialize_forbidden_edges(self):
"""Initialize forbidden edges from database once WorldGraph is available"""
with FORBIDDEN_EDGES_LOCK:
edges_data = self.edges_table.all()
forbidden_edges = []

for edge_data in edges_data:
src = self.worldGraph.getVertex(edge_data["src_mapId"], edge_data["src_zoneId"])
dst = self.worldGraph.getVertex(edge_data["dst_mapId"], edge_data["dst_zoneId"])
edge = self.worldGraph.getEdge(src, dst)
if edge:
forbidden_edges.append(edge)

self._forbiddenEdges = forbidden_edges

def addForbiddenEdge(self, edge: Edge, reason: str = None) -> None:
with FORBIDDEN_EDGES_LOCK:
Logger().warning(f"Adding edge {edge} to forbidden list for reason : {reason}")
edge_data = {
"src_mapId": edge.src.mapId,
"src_zoneId": edge.src.zoneId,
"dst_mapId": edge.dst.mapId,
"dst_zoneId": edge.dst.zoneId,
}
edge_data["reason"] = reason
edge_data["timestamp"] = time.time()
self._forbiddenEdges.append(edge)
self.edges_table.insert(edge_data)

def search(
self, worldGraph: WorldGraph, src: Vertex, dst: Union[Vertex, List[Vertex]], maxPathLength=None
) -> list["Edge"]:
if self.running:
raise Exception("Pathfinding already in progress")
self.initForbiddenSubareaList()
self.worldGraph = worldGraph
if not isinstance(dst, list):
dst = [dst]
Expand All @@ -61,9 +93,35 @@ def search(
heapq.heappush(self.openList, (0, id(node), node))
return self.compute()

def initForbiddenSubareaList(self) -> None:
# self._forbiddenSubareaIds = GameDataQuery.queryEquals(SubArea, "mountAutoTripAllowed", False)
self._forbiddenSubareaIds = []
def search_async(
self,
worldGraph: WorldGraph,
src: Vertex,
dst: Union[Vertex, List[Vertex]],
callback: Callable[[int, Optional[str], Optional[List[Edge]]], None],
maxPathLength=None,
) -> None:
"""
Asynchronous version of search that runs in a separate thread.
Callback receives (code, error, result):
code: 0 for success, 1 for error
error: error exception if code is 1, None otherwise
result: list of edges if code is 0, None otherwise
"""
if self.running:
callback(1, "Pathfinding already in progress", None)
return

def worker():
try:
result = self.search(worldGraph, src, dst, maxPathLength)
Kernel().defer(lambda: callback(0, None, result))
except Exception as exc:
Kernel().defer(lambda e=exc: callback(1, e, None))

thread = threading.Thread(target=worker, name=threading.current_thread().name, daemon=True)
thread.start()
return thread

def stopSearch(self) -> None:
if self.running != None:
Expand Down Expand Up @@ -95,11 +153,7 @@ def compute(self, e=None) -> None:
if Kernel().worker._terminating.is_set():
return

if (
edge not in self._forbiddenEdges
and self.hasValidTransition(edge)
and self.hasValidDestinationSubarea(edge)
):
if edge not in self._forbiddenEdges and self.hasValidTransition(edge):
existing = self.openDic.get(edge.dst)
if existing is None or current.moveCost + 1 < existing.moveCost:
node = Node(self, edge.dst, current)
Expand Down Expand Up @@ -141,21 +195,6 @@ def hasValidTransition(cls, edge: Edge) -> bool:
valid = True
return valid

def hasValidDestinationSubarea(self, edge: Edge) -> bool:
fromMapId = edge.src.mapId
fromMapPos = MapPosition.getMapPositionById(fromMapId)
fromSubareaId = fromMapPos.subAreaId
toMapId = edge.dst.mapId
toMapPos = MapPosition.getMapPositionById(toMapId)
if not toMapPos:
Logger().error(f"MapPosition {toMapId} not found")
return False
if fromSubareaId == toMapPos.subAreaId:
return True
if toMapPos.subAreaId in self._forbiddenSubareaIds:
return False
return True

def orderNodes(self, a: Node, b: Node) -> int:
return 0 if a.heuristic == b.heuristic else (1 if a.heuristic > b.heuristic else -1)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"forbidden_edges": {
"1": {
"src_mapId": 153879813.0,
"src_zoneId": 1,
"dst_mapId": 196085770.0,
"dst_zoneId": 1,
"reason": "Edge contains impossible zaap usage from/to ankarnam",
"timestamp": 1732953850.1883714
},
"2": {
"src_mapId": 153879813.0,
"src_zoneId": 1,
"dst_mapId": 203685888.0,
"dst_zoneId": 1,
"reason": "Edge contains impossible zaap usage from/to ankarnam",
"timestamp": 1732954170.8205929
},
"3": {
"src_mapId": 154010371.0,
"src_zoneId": 1,
"dst_mapId": 196085770.0,
"dst_zoneId": 1,
"reason": "Edge contains impossible zaap usage from/to ankarnam",
"timestamp": 1732954419.251738
},
"4": {
"src_mapId": 154010371.0,
"src_zoneId": 1,
"dst_mapId": 203685888.0,
"dst_zoneId": 1,
"reason": "Edge contains impossible zaap usage from/to ankarnam",
"timestamp": 1732954419.3378384
},
"5": {
"src_mapId": 153880064.0,
"src_zoneId": 1,
"dst_mapId": 196085770.0,
"dst_zoneId": 1,
"reason": "Edge contains impossible zaap usage from/to ankarnam",
"timestamp": 1732954424.2186527
},
"6": {
"src_mapId": 153880064.0,
"src_zoneId": 1,
"dst_mapId": 203685888.0,
"dst_zoneId": 1,
"reason": "Edge contains impossible zaap usage from/to ankarnam",
"timestamp": 1732954424.3485332
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,13 @@ def clone(self) -> "Edge":
edge = Edge(self.src, self.dst)
edge._transitions = [t.clone() for t in self.transitions]
return edge

def __eq__(self, other: object) -> bool:
"""Compare two edges for equality based on src and dst vertices"""
if not isinstance(other, Edge):
return False
return self.src.UID == other.src.UID and self.dst.UID == other.dst.UID

def __hash__(self) -> int:
"""Hash function for Edge objects based on src and dst vertices"""
return hash((self.src.UID, self.dst.UID))
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import json
import os
from time import perf_counter
from typing import List
from typing import List, Optional

from pydofus2.com.ankamagames.dofus.datacenter.world.Hint import Hint
from pydofus2.com.ankamagames.dofus.datacenter.world.MapPosition import MapPosition
from pydofus2.com.ankamagames.dofus.datacenter.world.SubArea import SubArea
from pydofus2.com.ankamagames.dofus.internalDatacenter.DataEnum import DataEnum
from pydofus2.com.ankamagames.dofus.logic.common.managers.PlayerManager import PlayerManager
from pydofus2.com.ankamagames.dofus.logic.game.common.managers.PlayedCharacterManager import PlayedCharacterManager
Expand Down Expand Up @@ -189,35 +190,50 @@ def hasZaapTransition(self, edge: Edge, tr_type: TransitionTypeEnum):
return False

def getEdgesToKnownZaapsFromVertex(self, src: Vertex, transition_type=TransitionTypeEnum.ZAAP) -> List[Edge]:
"""Get edges to all known zaap destinations from a source vertex."""
zaap_edges = []
for zaapMapId in PlayedCharacterManager()._knownZaapMapIds:
tp_cost = 10 * MapTools.distL2Maps(src.mapId, zaapMapId)
if int(tp_cost) > PlayedCharacterManager().characteristics.kamas:

def can_afford_teleport(src_map_id: int, dst_map_id: int) -> bool:
tp_cost = 10 * MapTools.distL2Maps(src_map_id, dst_map_id)
return int(tp_cost) <= PlayedCharacterManager().characteristics.kamas

def can_travel_between_areas(src_map_id: int, dst_map_id: int) -> bool:
src_sub_area = SubArea.getSubAreaByMapId(src_map_id)
dst_sub_area = SubArea.getSubAreaByMapId(dst_map_id)
return (src_sub_area.areaId == DataEnum.ANKARNAM_AREA_ID) == (
dst_sub_area.areaId == DataEnum.ANKARNAM_AREA_ID
)

def get_or_create_zaap_edge(src: Vertex, dst: Vertex) -> Optional[Edge]:
if src == dst:
return None

if not can_travel_between_areas(src.mapId, dst.mapId):
return None

edge = self.getEdge(src, dst) or self.addEdge(src, dst)
if not self.hasZaapTransition(edge, transition_type):
edge.addTransition(transition_type)
return edge

for zaap_map_id in PlayedCharacterManager()._knownZaapMapIds:
if not can_afford_teleport(src.mapId, zaap_map_id):
continue

zaap_vertex_info = self._map_memory.get_zaap_vertex(zaapMapId)
zaap_vertex_info = self._map_memory.get_zaap_vertex(zaap_map_id)

if zaap_vertex_info is None:
possible_vertices = self.getVertices(zaapMapId)
for vertex in possible_vertices.values():
if vertex == src:
continue
edge = self.getEdge(src, vertex)
if not edge:
edge = self.addEdge(src, vertex)
if not self.hasZaapTransition(edge, transition_type):
edge.addTransition(transition_type)
zaap_edges.append(edge)
# Try all possible vertices on the map
for dest_vertex in self.getVertices(zaap_map_id).values():
if edge := get_or_create_zaap_edge(src, dest_vertex):
zaap_edges.append(edge)
else:
# Use known zaap vertex
map_id, zone_id = zaap_vertex_info
dest_vertex = self.getVertex(map_id, zone_id)
if dest_vertex == src:
continue
edge = self.getEdge(src, dest_vertex)
if not edge:
edge = self.addEdge(src, dest_vertex)
if not self.hasZaapTransition(edge, transition_type):
edge.addTransition(transition_type)
zaap_edges.append(edge)
if edge := get_or_create_zaap_edge(src, dest_vertex):
zaap_edges.append(edge)

return zaap_edges

def getEdge(self, src: Vertex, dest: Vertex) -> Edge:
Expand Down
Loading

0 comments on commit 9fa5a91

Please sign in to comment.