use async_trait::async_trait;
use binrw::{BinRead, BinWrite};
use num_enum::{FromPrimitive, IntoPrimitive};
use rand::distr::Alphanumeric;
use rand::prelude::IndexedRandom;
use rand::{Rng, rng};
use regex::Regex;
use serde::Serialize;
use std::fmt::{Display, Formatter};
use std::sync::{Arc, Mutex};
use tokio::sync::RwLock;
use crate::client::prelude::*;
use crate::plugins::wow::wotlk::config::Config;
use crate::plugins::wow::wotlk::opcodes::Opcode;
use crate::plugins::wow::wotlk::realm::object::types::movement::Point3D;
#[derive(Packet, BinRead, Serialize, FieldsMetadata)]
#[br(little)]
struct Incoming {
characters_count: u8,
#[br(count = characters_count)]
characters: Vec<Character>,
}
#[derive(Packet, BinWrite, Serialize, FieldsMetadata)]
#[name(CMSG_CHAR_CREATE)]
#[opcode(U32(Opcode::CMSG_CHAR_CREATE))]
#[bw(little)]
struct CharCreate {
#[bw(write_with = crate::client::packet::helpers::null_terminated)]
name: NullTerminated<String>,
race: u8,
class: u8,
gender: u8,
skin: u8,
face: u8,
hair_style: u8,
hair_color: u8,
facial_hair: u8,
outfit_id: u8,
}
pub struct Handler {
pub guid: Arc<Mutex<u64>>,
}
#[async_trait]
impl PacketHandler for Handler {
async fn handle(
&mut self,
packet: &mut Packet,
_: Arc<RwLock<CtxMap>>,
) -> anyhow::Result<Vec<HandlerOutput>> {
let mut outputs = vec![];
let config: Config = ConfigParser::parse_from_file("wow/wotlk/connection.toml")?;
let autoselect = config
.autoselect
.ok_or_else(|| anyhow::anyhow!("Missing [autoselect]"))?;
let common = config
.common
.ok_or_else(|| anyhow::anyhow!("Missing [common]"))?;
let Incoming { characters, .. } = Incoming::unpack(packet)?;
if characters.is_empty() {
if common.create_character_if_empty {
outputs.push(HandlerOutput::Packets(vec![
CharCreate {
name: generate_random_name().into(),
race: generate_random_race(),
class: generate_random_class(),
gender: generate_random_gender(),
skin: 0,
face: 0,
hair_style: 0,
hair_color: 0,
facial_hair: 0,
outfit_id: 0,
}
.pack()?,
]));
} else {
outputs.push(HandlerOutput::Messages(vec![Message {
msg_type: MsgType::Error,
text: "Character list is empty, \
you should create new character to proceed."
.to_string(),
}]))
}
} else if autoselect.character_name.is_empty() {
let items = Arc::new(characters.iter().map(|c| c.to_string()).collect());
let arc_guid = self.guid.clone();
outputs.push(HandlerOutput::Requests(vec![Request::InitChoice(
ChoiceItems(items),
Some(Box::new(move |ids: Vec<usize>| {
let Character { name, guid, .. } = &characters[ids[0]];
*arc_guid.lock().unwrap() = *guid;
Ok(vec![HandlerOutput::Messages(vec![Message {
msg_type: MsgType::Info,
text: format!("Selected \"{name}\" character"),
}])])
})),
)]))
} else {
let re = Regex::new(&autoselect.character_name)?;
if let Some(character) = characters
.into_iter()
.find(|item| re.is_match(item.name.as_ref()))
{
let Character { name, guid, .. } = character;
*self.guid.lock().unwrap() = guid;
outputs.push(HandlerOutput::Messages(vec![Message {
msg_type: MsgType::Info,
text: format!("Selected \"{name}\" character"),
}]));
}
}
Ok(outputs)
}
}
fn generate_random_name() -> String {
let mut rng = rng();
let random_length = rng.random_range(9..=11);
let string: String = rng
.sample_iter(&Alphanumeric)
.filter(|c| c.is_ascii_alphabetic())
.take(random_length)
.map(|c| c as char)
.collect();
let first_letter = string.chars().next().unwrap();
format!("{}{}", first_letter.to_uppercase(), string.to_lowercase())
}
fn generate_random_race() -> u8 {
let races = &[
Race::Human,
Race::Orc,
Race::Dwarf,
Race::NightElf,
Race::Undead,
Race::Troll,
];
let mut rng = rng();
(*races.choose(&mut rng).unwrap()).into()
}
fn generate_random_class() -> u8 {
let races = &[Class::Warrior, Class::Rogue];
let mut rng = rng();
(*races.choose(&mut rng).unwrap()).into()
}
fn generate_random_gender() -> u8 {
let races = &[Gender::Male, Gender::Female];
let mut rng = rng();
(*races.choose(&mut rng).unwrap()).into()
}
#[derive(BinRead, FieldsMetadata, Serialize)]
struct Character {
guid: u64,
name: NullTerminated<String>,
race: u8,
class: u8,
gender: u8,
skin: u8,
face: u8,
hair_style: u8,
hair_color: u8,
facial_hair: u8,
level: u8,
zone_id: u32,
map_id: u32,
position: Point3D,
guild_id: u32,
flags: u32,
customize_flags: u32,
#[br(map = |x: u8| x != 0)]
first_login: bool,
pet_info: PetInfo,
equipment: [EquippedItem; 23],
}
impl Display for Character {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[{} lvl]: {}, {}, {}, (zone={}/map={})",
self.level, self.name, self.race, self.class, self.zone_id, self.map_id
)
}
}
#[derive(BinRead, FieldsMetadata, Serialize)]
struct PetInfo {
display_id: u32,
level: u32,
family: u32,
}
#[derive(BinRead, FieldsMetadata, Serialize)]
struct EquippedItem {
display_id: u32,
inventory_type: u8,
aura_id: u32,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, IntoPrimitive, FromPrimitive)]
#[repr(u8)]
pub enum Gender {
#[default]
Male = 0,
Female = 1,
None = 2,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, IntoPrimitive, FromPrimitive)]
#[repr(u8)]
pub enum Race {
#[default]
Human = 1,
Orc = 2,
Dwarf = 3,
NightElf = 4,
Undead = 5,
Tauren = 6,
Gnome = 7,
Troll = 8,
Goblin = 9,
BloodElf = 10,
Draenei = 11,
FelOrc = 12,
Naga = 13,
Broken = 14,
Skeleton = 15,
Vrykul = 16,
Tuskarr = 17,
ForestTroll = 18,
Taunka = 19,
NorthrendSkeleton = 20,
IceTroll = 21,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, IntoPrimitive, FromPrimitive)]
#[repr(u8)]
pub enum Class {
#[default]
Warrior = 1,
Paladin = 2,
Hunter = 3,
Rogue = 4,
Priest = 5,
DeathKnight = 6,
Shaman = 7,
Mage = 8,
Warlock = 9,
Druid = 11,
}