tentacli 15.3.2

Framework for building extensible network protocol clients via modular plugins.
use std::sync::Arc;

use async_trait::async_trait;
use binrw::{BinRead, BinWrite};
use serde::Serialize;
use tokio::sync::RwLock;

use crate::client::prelude::*;
use crate::plugins::wow::wotlk::opcodes::Opcode;
use crate::plugins::wow::wotlk::realm::object::names::ObjectNameRegistry;
use crate::plugins::wow::wotlk::realm::object::types::packed_guid::PackedGuid;

#[derive(Packet, BinWrite, Serialize, FieldsMetadata)]
#[name(CMSG_NAME_QUERY)]
#[opcode(U32(Opcode::CMSG_NAME_QUERY))]
#[bw(little)]
struct PlayerNameQuery {
    guid: u64,
}

#[derive(Packet, BinWrite, Serialize, FieldsMetadata)]
#[name(CMSG_PET_NAME_QUERY)]
#[opcode(U32(Opcode::CMSG_PET_NAME_QUERY))]
#[bw(little)]
struct PetNameQuery {
    pet_number: u32,
    guid: u64,
}

#[derive(Packet, BinWrite, Serialize, FieldsMetadata)]
#[name(CMSG_CREATURE_QUERY)]
#[opcode(U32(Opcode::CMSG_CREATURE_QUERY))]
#[bw(little)]
struct CreatureNameQuery {
    entry: u32,
    guid: u64,
}

#[derive(Packet, BinWrite, Serialize, FieldsMetadata)]
#[name(CMSG_GAMEOBJECT_QUERY)]
#[opcode(U32(Opcode::CMSG_GAMEOBJECT_QUERY))]
#[bw(little)]
struct GameObjectNameQuery {
    entry: u32,
    guid: u64,
}

#[derive(Packet, BinWrite, Serialize, FieldsMetadata)]
#[name(CMSG_ITEM_NAME_QUERY)]
#[opcode(U32(Opcode::CMSG_ITEM_NAME_QUERY))]
#[bw(little)]
struct ItemNameQuery {
    entry: u32,
    guid: u64,
}

#[derive(Packet, BinRead, Serialize, FieldsMetadata)]
#[br(little)]
struct PlayerResponse {
    guid: PackedGuid,
    name_unknown: u8,
    #[br(if(name_unknown == 0))]
    name: Option<NullTerminated<String>>,
    #[br(if(name_unknown == 0))]
    realm_name: Option<NullTerminated<String>>,
    #[br(if(name_unknown == 0))]
    race: Option<u8>,
    #[br(if(name_unknown == 0))]
    gender: Option<u8>,
    #[br(if(name_unknown == 0))]
    class: Option<u8>,
}

#[derive(Packet, BinRead, Serialize, FieldsMetadata)]
#[br(little)]
struct PetResponse {
    pet_number: u32,
    name: NullTerminated<String>,
    name_timestamp: u32,
}

#[derive(Packet, BinRead, Serialize, FieldsMetadata)]
#[br(little)]
struct CreatureResponse {
    entry: u32,
    #[br(if(entry & 0x8000_0000 == 0))]
    name: Option<NullTerminated<String>>,
}

#[derive(Packet, BinRead, Serialize, FieldsMetadata)]
#[br(little)]
struct GameObjectResponse {
    entry: u32,
    #[br(if(entry & 0x8000_0000 == 0))]
    game_object_type: Option<u32>,
    #[br(if(entry & 0x8000_0000 == 0))]
    display_id: Option<u32>,
    #[br(if(entry & 0x8000_0000 == 0))]
    name: Option<NullTerminated<String>>,
}

#[derive(Packet, BinRead, Serialize, FieldsMetadata)]
#[br(little)]
struct ItemResponse {
    entry: u32,
    name: NullTerminated<String>,
    inventory_type: u32,
}

pub struct Handler {
    pub kind: ResponseKind,
}

#[async_trait]
impl PacketHandler for Handler {
    async fn handle(
        &mut self,
        packet: &mut Packet,
        _: Arc<RwLock<CtxMap>>,
    ) -> anyhow::Result<Vec<HandlerOutput>> {
        let request = match self.kind {
            ResponseKind::Player => player_request(packet)?,
            ResponseKind::Pet => pet_request(packet)?,
            ResponseKind::Creature => creature_request(packet)?,
            ResponseKind::GameObject => game_object_request(packet)?,
            ResponseKind::Item => item_request(packet)?,
        };

        Ok(vec![HandlerOutput::Requests(vec![request])])
    }
}


#[derive(Debug, Clone, Copy)]
pub enum ResponseKind {
    Player,
    Pet,
    Creature,
    GameObject,
    Item,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NameQueryRequest {
    Player { guid: PackedGuid },
    Pet { pet_number: u32, guid: PackedGuid },
    Creature { entry: u32, guid: PackedGuid },
    GameObject { entry: u32, guid: PackedGuid },
    Item { entry: u32, guid: PackedGuid },
}

impl NameQueryRequest {
    pub(crate) fn pack(self) -> anyhow::Result<Packet> {
        match self {
            Self::Player { guid } => PlayerNameQuery { guid: guid.0 }.pack(),
            Self::Pet { pet_number, guid } => PetNameQuery {
                pet_number,
                guid: guid.0,
            }
            .pack(),
            Self::Creature { entry, guid } => CreatureNameQuery {
                entry,
                guid: guid.0,
            }
            .pack(),
            Self::GameObject { entry, guid } => GameObjectNameQuery {
                entry,
                guid: guid.0,
            }
            .pack(),
            Self::Item { entry, guid } => ItemNameQuery {
                entry,
                guid: guid.0,
            }
            .pack(),
        }
    }
}

fn player_request(packet: &mut Packet) -> anyhow::Result<Request> {
    let incoming = PlayerResponse::unpack(packet)?;

    Ok(Request::SetContext(Some(Box::new(move |ctx: &mut CtxMap| {
        let Some(registry) = ctx.get_mut::<ObjectNameRegistry>() else {
            return;
        };

        if incoming.name_unknown != 0 {
            registry.fail_player(incoming.guid);
            return;
        }

        registry.resolve_player(
            incoming.guid,
            incoming.name.map(|value| value.0).unwrap_or_default(),
            incoming.realm_name.map(|value| value.0).unwrap_or_default(),
        );
    }))))
}

fn pet_request(packet: &mut Packet) -> anyhow::Result<Request> {
    let incoming = PetResponse::unpack(packet)?;

    Ok(Request::SetContext(Some(Box::new(move |ctx: &mut CtxMap| {
        let Some(registry) = ctx.get_mut::<ObjectNameRegistry>() else {
            return;
        };

        if incoming.name.0.is_empty() {
            registry.fail_pet(incoming.pet_number);
        } else {
            registry.resolve_pet(incoming.pet_number, incoming.name.0, incoming.name_timestamp);
        }
    }))))
}

fn creature_request(packet: &mut Packet) -> anyhow::Result<Request> {
    let incoming = CreatureResponse::unpack(packet)?;
    let failed = incoming.entry & 0x8000_0000 != 0;
    let entry = incoming.entry & 0x7fff_ffff;

    Ok(Request::SetContext(Some(Box::new(move |ctx: &mut CtxMap| {
        let Some(registry) = ctx.get_mut::<ObjectNameRegistry>() else {
            return;
        };

        if failed {
            registry.fail_creature(entry);
        } else if let Some(name) = incoming.name {
            registry.resolve_creature(entry, name.0);
        }
    }))))
}

fn game_object_request(packet: &mut Packet) -> anyhow::Result<Request> {
    let incoming = GameObjectResponse::unpack(packet)?;
    let failed = incoming.entry & 0x8000_0000 != 0;
    let entry = incoming.entry & 0x7fff_ffff;

    Ok(Request::SetContext(Some(Box::new(move |ctx: &mut CtxMap| {
        let Some(registry) = ctx.get_mut::<ObjectNameRegistry>() else {
            return;
        };

        if failed {
            registry.fail_game_object(entry);
        } else if let (Some(name), Some(game_object_type), Some(display_id)) = (
            incoming.name,
            incoming.game_object_type,
            incoming.display_id,
        ) {
            registry.resolve_game_object(entry, name.0, game_object_type, display_id);
        }
    }))))
}

fn item_request(packet: &mut Packet) -> anyhow::Result<Request> {
    let incoming = ItemResponse::unpack(packet)?;

    Ok(Request::SetContext(Some(Box::new(move |ctx: &mut CtxMap| {
        let Some(registry) = ctx.get_mut::<ObjectNameRegistry>() else {
            return;
        };

        registry.resolve_item(incoming.entry, incoming.name.0, incoming.inventory_type);
    }))))
}