EntityLook et SubEntity

Inscrit
30 Septembre 2022
Messages
1
Reactions
0
#1
Salut les gars,
Afin de parfaire mon niveau en Python, je me suis mis en tête de traduire les packets que Dofus m'envoie.
Je suis donc au stade de la class MapComplementaryInformationsDataMessage, au niveau actors.
Mais dans cette variable, je dois désérialiser une class EntityLook. A la fin de cette class, je dois désérialiser une class SubEntity.
Mais la class SubEntity désérialise elle-même EntityLook.

EntityLook :
Code:
var _subentitiesLen:uint = uint(input.readUnsignedShort());
for(var _i5:uint = 0; _i5 < _subentitiesLen; _i5++)
{
   _item5 = new com.ankamagames.dofus.network.types.game.look.SubEntity();
   _item5.deserialize(input);
   this.subentities.push(_item5);
}
SubEntity
Code:
public function deserializeAs_SubEntity(input:ICustomDataInput) : void
{
   this._bindingPointCategoryFunc(input);
   this._bindingPointIndexFunc(input);
   this.subEntityLook = new com.ankamagames.dofus.network.types.game.look.EntityLook();
   this.subEntityLook.deserialize(input);
}
J'ai dû loupé quelque chose mais ça fait quelques jours que je réfléchis et je ne vois pas quoi faire.

Merci pour l'aide.
Tessrac
 
Inscrit
16 Mars 2014
Messages
214
Reactions
30
#2
L'EntityLook représente le "look" d'un joueur, PNJ ou monstre, et peut contenir une ou plusieurs SubEntities qui ont également leur propre "look", donc c'est normal, itére dans les SubEntities et désérialiser le tout comme un EntityLook
 

BlueDream

Administrateur
Membre du personnel
Inscrit
8 Decembre 2012
Messages
2 010
Reactions
149
#3
Ça devrait boucler sans segfault, passé un certain nombre de subEntity, ca passera en null.
 
Inscrit
10 Février 2017
Messages
26
Reactions
43
#4
Class to deserialize any class:
class NetworkMessageClassDefinition:
    TRACE = False
   
    def __init__(self, className: str, raw: ByteArray) -> None:
        classSpec = ProtocolSpec.getClassSpecByName(className)
        self.parent = classSpec.parent
        self.fields = classSpec.fields
        self.boolfields = classSpec.boolfields
        self.cls = classSpec.cls
        self.raw = raw

    def deserialize(self, childInstance: object = None) -> object:
        if childInstance is None:
            inst = self.cls()
        else:
            inst = childInstance

        if self.TRACE:
            TraceLogger().debug("------------------ Deserializing {} STARTED-----------------".format(self.cls.__name__))

        if self.parent is not None:
            if self.TRACE:
                TraceLogger().debug(f"Class has parent {self.parent}")
            inst = NetworkMessageClassDefinition(self.parent, self.raw).deserialize(inst)
            if self.TRACE:
                TraceLogger().debug("End of parent deserialization")
                TraceLogger().debug(f"BytesArray positon: {self.raw.position}")

        try:
            for field, value in self.readBooleans(self.boolfields, self.raw).items():
                if self.TRACE:
                    TraceLogger().debug(f"{field} = {value}")
                setattr(inst, field, value)
        except Exception as e:
            TraceLogger().debug(f"Remaining bytes in raw: {self.raw.remaining()}")
            TraceLogger().error(f"Error while reading boolean fields!")
            raise e

        for field in self.fields:
            attrib = field.name
            if self.TRACE:
                TraceLogger().debug(f"Deserializing field '{attrib}', remaining bytes '{self.raw.remaining()}'.")
            if field.optional:
                isProvided = self.raw.readByte()
                if not isProvided:
                    if self.TRACE:
                        TraceLogger().debug(f"Field '{attrib}' is optional and was not provided.")
                    continue
            try:
                value = nmdf.NetMsgDataField(field, self.raw).deserialize()
            except Exception as e:
                TraceLogger().debug(inst.__class__.__name__)
                TraceLogger().debug(self.fields)
                TraceLogger().error(str(e), exc_info=True)
                raise KeyboardInterrupt
            setattr(inst, attrib, value)
        if self.TRACE:
            TraceLogger().debug("------------------ Deserializing {} ENDED---------------------".format(self.cls.__name__))

        if inst.__class__.__base__ == bnm.NetworkMessage:
            bnm.NetworkMessage.__init__(inst)

        return inst
   
    @classmethod
    def readBooleans(cls, boolfields: list[FieldSpec], raw: ByteArray):
        ans = {}
        n = len(boolfields)
        if n > 0:
            if cls.TRACE:
                TraceLogger().debug("Reading {} booleans".format(n))
                TraceLogger().debug(f"I need {n // 8} bytes")
                TraceLogger().debug(f"Remaining bytes in raw: {raw.remaining()}")
            if raw.remaining() < n // 8:
                raise Exception("Not enough bytes to read booleans")
            for i, var in enumerate(boolfields):
                if i % 8 == 0:
                    _box: int = raw.readByte()
                value = boolByteWrapper.getFlag(_box, i % 8)
                if cls.TRACE:
                    TraceLogger().debug(f"{var.name} = {value}")
                ans[var.name] = value
        return ans
Class to deserialize any field:
class NetMsgDataField:
    TRACE = False

    dataReader = {
        TypeEnum.INT: "readInt",
        TypeEnum.UNSIGNEDINT: "readUnsignedInt",
        TypeEnum.SHORT: "readShort",
        TypeEnum.UNSIGNEDSHORT: "readUnsignedShort",
        TypeEnum.BYTE: "readByte",
        TypeEnum.UNSIGNEDBYTE: "readUnsignedByte",
        TypeEnum.FLOAT: "readFloat",
        TypeEnum.DOUBLE: "readDouble",
        TypeEnum.BOOLEAN: "readBoolean",
        TypeEnum.VARINT: "readVarInt",
        TypeEnum.VARLONG: "readVarLong",
        TypeEnum.UTF: "readUTF",
        TypeEnum.VARUHSHORT: "readVarUhShort",
        TypeEnum.VARUHINT: "readVarUhInt",
        TypeEnum.VARSHORT: "readVarShort",
        TypeEnum.VARUHLONG: "readVarUhLong",
    }

    def __init__(self, spec: FieldSpec, raw: ByteArray):
        self._spec = spec
        self._raw = raw
        self._type = None
        self._length = None

    @property
    def name(self) -> str:
        return self._spec.name

    @property
    def type(self) -> str:
        if not self._type:
            self._type = self._spec. type
        return self._type

    @type.setter
    def type(self, newValue):
        self._type = newValue

    @property
    def length(self) -> int:
        if self._length is None:
            self._length = self._spec.length
        return self._length
   
    @length.setter
    def length(self, val):
        if val < 0:
            raise ValueError(f"Vector length can't be assigned a negative value '{val}'")
        self._length = val
   
    @property
    def lengthTypeId(self) -> int:
        return self._spec.lengthTypeId

    @property
    def typename(self) -> str:
        return self._spec.typename

    def deserialize(self):
        if self._spec.isVector():
            return self.readVector()
        if self._spec.isPrimitive():
            val = self.readPrimitive()
            if self.TRACE:
                TraceLogger().debug(f"Field {self.name} = {val}")
            return val
        else:
            return self.readObject()

    def readPrimitive(self, typeId=None):
        if typeId is None:
            typeId = self._spec.typeId
        dataReader = NetMsgDataField.dataReader.get(typeId)
        if dataReader is None:
            raise Exception(f"TypeId '{typeId}' not found in known types ids")
        return getattr(self._raw, dataReader)()

    def readObject(self):
        if self._spec.dynamicType:
            typeId = self._raw.readUnsignedShort()
            self.type = ProtocolSpec.getTypeSpecById(typeId).name
            if self.type is None:
                raise Exception(f"Unable to parse dynamic type name of typeid '{typeId}'.")
        obj = nmcd.NetworkMessageClassDefinition(self.type, self._raw).deserialize()
        return obj

    def readVector(self):
        if self.length is None:
            self.length = self.readPrimitive(TypeEnum(self.lengthTypeId))
            if self.TRACE:
                TraceLogger().debug(f"Read Vector length = {self.length}")
        if self.TRACE:
            TraceLogger().debug(f"==> Deserialising Vector<{self.typename}> of length {self.length}, remaining bytes {self._raw.remaining()}")
        ret = []
        for i in range(self.length):
            if self._spec.isPrimitive():
                val = self.readPrimitive()
                if self.TRACE:
                    TraceLogger().debug(f"Vector value {i} = {val}")
                ret.append(val)
            else:
                if self.TRACE:
                    TraceLogger().debug(f"Reading Vector Object {i}.")
                ret.append(self.readObject())
        return ret
 
Inscrit
10 Février 2017
Messages
26
Reactions
43
#5
Pour le contenu des objets spec que j'utilise pour avoir les infos sur les classes et les fields tu peux regarder le repo opensource de louis abraham c'est un bon point de départ.

Ce code produit à la fin des classes python qui représentent les messages comme ce qui est fait dans le code original AS.
Les classes python de tous les messages peuvent êtres généré automatiquement depuis les specs du protocole produit par un script tel que celui de louis Abraham.
voici à quoi elles ressemble :

Python:
from pydofus2.com.ankamagames.jerakine.network.NetworkMessage import NetworkMessage
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from pydofus2.com.ankamagames.dofus.network.types.game.house.HouseInformations import HouseInformations
    from pydofus2.com.ankamagames.dofus.network.types.game.context.roleplay.GameRolePlayActorInformations import GameRolePlayActorInformations
    from pydofus2.com.ankamagames.dofus.network.types.game.interactive.InteractiveElement import InteractiveElement
    from pydofus2.com.ankamagames.dofus.network.types.game.interactive.StatedElement import StatedElement
    from pydofus2.com.ankamagames.dofus.network.types.game.interactive.MapObstacle import MapObstacle
    from pydofus2.com.ankamagames.dofus.network.types.game.context.fight.FightCommonInformations import FightCommonInformations
    from pydofus2.com.ankamagames.dofus.network.types.game.context.fight.FightStartingPositions import FightStartingPositions
  

class MapComplementaryInformationsDataMessage(NetworkMessage):
    subAreaId: int
    mapId: int
    houses: list['HouseInformations']
    actors: list['GameRolePlayActorInformations']
    interactiveElements: list['InteractiveElement']
    statedElements: list['StatedElement']
    obstacles: list['MapObstacle']
    fights: list['FightCommonInformations']
    hasAggressiveMonsters: bool
    fightStartPositions: 'FightStartingPositions'
    def init(self, subAreaId_: int, mapId_: int, houses_: list['HouseInformations'], actors_: list['GameRolePlayActorInformations'], interactiveElements_: list['InteractiveElement'], statedElements_: list['StatedElement'], obstacles_: list['MapObstacle'], fights_: list['FightCommonInformations'], hasAggressiveMonsters_: bool, fightStartPositions_: 'FightStartingPositions'):
        self.subAreaId = subAreaId_
        self.mapId = mapId_
        self.houses = houses_
        self.actors = actors_
        self.interactiveElements = interactiveElements_
        self.statedElements = statedElements_
        self.obstacles = obstacles_
        self.fights = fights_
        self.hasAggressiveMonsters = hasAggressiveMonsters_
        self.fightStartPositions = fightStartPositions_
      
        super().__init__()
 
Dernière édition:
Inscrit
10 Février 2017
Messages
26
Reactions
43
#6
Tu peux suivre la même démarche à ta façon et t'auras à la fin un deserialiser qui produit des classes python ca t'aidera aussi pour l'autocompletion, ca change enormement la vie par rapport à l'usage des messages en json.
Bon courage
 
Haut Bas