Skip to content

How it works

Sergio Ivanuzzo edited this page Aug 7, 2024 · 27 revisions

How it works

Intro

TentaCLI receives responses from the WoW Server and can send requests. Each response can be processed by a group of handlers, where each group is defined by specific processor for a particular opcode. When opcode is missed in specific processor, it will be ignored. For handling the rest of application, each handler can return vector of HandlerOutput values:

#[derive(Debug, Clone)]
pub enum HandlerOutput {
    // data transfer
    ChatMessage(Message),
    Data((u32, Vec<u8>, String)),
    TransferCharactersList(Vec<Character>),
    TransferRealmsList(Vec<Realm>),
    UpdatePlayer(Player),

    // commands
    ConnectionRequest(String, u16),
    Freeze,
    Drop,
    SelectRealm(Realm),
    SelectCharacter(Character),

    // messages
    ResponseMessage(String, Option<String>),
    RequestMessage(String, Option<String>),
    DebugMessage(String, Option<String>),
    SuccessMessage(String, Option<String>),
    ErrorMessage(String, Option<String>),
}
  • HandlerOutput::ChatMessage notifies about messages received in chat
  • HandlerOutput::Data is used to send some packet to server, contains of opcode, packet body and extra string details.
  • TransferCharactersList or TransferRealmsList notifies about parsed Characters/Realms list (see ui feature, realm_modal and characters_modal)
  • UpdatePlayer(Player) is used to notify third-party apps about current player updates (triggered by SMSG_UPDATE_OBJECT/SMSG_COMPRESSED_UPDATE_OBJECT packets)
  • HandlerOutput::ConnectionRequest is used to set connection (each call will replace current connection)
  • HandlerOutput::Freeze is mostly used to stop packet handling (to wait for user actions etc)
  • HandlerOutput::Drop exits the handle_output task (since version v4.0.0 this behavior is wrong, should be fixed in future)
  • SelectRealm and SelectCharacter are used to notify core about selection (should be used in features)
  • messages are used to notify features about output (see features/ui and features/console how they processes the output)

Realm and Character selecting

You can configure to handle this automatically or manually. In case you want it to autoselect the character and(or) realm, you need to edit proper settings under autoselect section in config. For manual just keep realm_name and character_name as empty strings. Each host section in config supports multiple account sets, actually it's a dictionary. Example:

connection_data:
  127.0.0.1:
    account_name:
        password: "safe_password"
        autoselect:
            realm_name: ".*STRING OR REGEX PATTERN TO FIND REALM NAME.*"
            character_name: ".*STRING OR REGEX PATTERN TO FIND CHARACTER NAME.*"

    another_account_name:
        password: "safe_password"
        autoselect:
            realm_name: ".*STRING OR REGEX PATTERN TO FIND REALM NAME.*"
            character_name: ".*STRING OR REGEX PATTERN TO FIND CHARACTER NAME.*"

  another.server.com:
    account_name:
        password: "safe_password"
        autoselect:
            realm_name: ""
            character_name: ""

Extending

You can use own features list to extend existing tentacli functionality (and even run multiple tentaclis and connect them using common broadcast channel). Example:

use tokio::task::JoinHandle;

use tentacli::async_broadcast::{BroadcastSender, BroadcastReceiver};
use tentacli::{Client, RunOptions};
use tentacli_traits::Feature;
use tentacli_traits::types::HandlerOutput;

#[tokio::main]
async fn main() {
    pub struct MyFeature {
        _receiver: Option<BroadcastReceiver<HandlerOutput>>,
        _sender: Option<BroadcastSender<HandlerOutput>>,
    }

    impl Feature for MyFeature {
        fn new() -> Self where Self: Sized {
            Self {
                _receiver: None,
                _sender: None,
            }
        }

        fn set_broadcast_channel(
            &mut self,
            sender: BroadcastSender<HandlerOutput>,
            receiver: BroadcastReceiver<HandlerOutput>
        ) {
            self._sender = Some(sender);
            self._receiver = Some(receiver);
        }

        fn get_tasks(&mut self) -> Vec<JoinHandle<()>> {
            let mut receiver = self._receiver.as_mut().unwrap().clone();

            let handle_smth = || {
                tokio::spawn(async move {
                    loop {
                        if let Ok(output) = receiver.recv().await {
                            match output {
                                HandlerOutput::SuccessMessage(message, _) => {
                                    println!("{}", message);
                                }
                                _ => {}
                            }
                        }
                    }
                })
            };

            vec![handle_smth()]
        }
    }

    let options = RunOptions {
        external_features: vec![Box::new(MyFeature::new())],
        account: "account_name",
        config_path: "./dir/another_dir/ConfigFileName.yml",
        dotenv_path: "./path/to/.env"
    };

    // ... pass options to the client
    // Client::new().run(options).await.unwrap();
}
Clone this wiki locally