Major refactor (+ new commands)

This commit is contained in:
ErrorNoInternet 2023-01-09 11:13:08 +08:00
parent 0f9efc1f9e
commit 5a4e3d7e2c
Signed by untrusted user who does not match committer: ErrorNoInternet
GPG Key ID: 2486BFB7B1E6A4A3
2 changed files with 503 additions and 233 deletions

View File

@ -1,54 +1,31 @@
use crate::State; use crate::{logging::log_error, State};
use azalea::prelude::*; use azalea::{pathfinder::BlockPosGoal, prelude::*, BlockPos};
use chrono::{Local, TimeZone};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(PartialEq, PartialOrd)] #[derive(Debug, Clone, PartialEq, PartialOrd, EnumIter)]
pub enum Command { pub enum Command {
Location, Help,
LastLocation,
LastOnline,
FollowPlayer,
StopFollowPlayer,
Goto, Goto,
Stop, StopGoto,
Say,
ToggleBotStatusMessages,
ToggleAlertMessages,
Unknown, Unknown,
} }
pub fn process_command(command: &String, _client: &Client, state: &mut State) -> String { pub async fn process_command(
let check_command = |command: &mut Command, segment: &String| { command: &String,
match command { executor: &String,
Command::Location => return format!("{} is somewhere", segment), client: &Client,
Command::Goto => { state: &mut State,
if state.final_target.lock().unwrap().is_some() ) -> String {
&& state.final_target.lock().unwrap().clone().unwrap().len() == 3 let mut segments: Vec<String> = command
{
*command = Command::Unknown;
let coordinates =
(*state.final_target.lock().unwrap().clone().unwrap()).to_vec();
return format!(
"I am now going to {} {} {}...",
coordinates[0], coordinates[1], coordinates[2]
);
}
if state.final_target.lock().unwrap().is_none() {
*state.final_target.lock().unwrap() = Some(Vec::new());
};
let mut new_coordinates = state.final_target.lock().unwrap().clone().unwrap();
new_coordinates.push(segment.parse().unwrap_or(0));
*state.final_target.lock().unwrap() = Some(new_coordinates);
return "".to_string();
}
Command::Stop => {
*state.final_target.lock().unwrap() = None;
*command = Command::Unknown;
return "I am no longer doing anything".to_string();
}
_ => {
*command = Command::Unknown;
return "".to_string();
}
};
};
let segments: Vec<String> = command
.split(" ") .split(" ")
.map(|segment| segment.to_string()) .map(|segment| segment.to_string())
.collect(); .collect();
@ -57,24 +34,213 @@ pub fn process_command(command: &String, _client: &Client, state: &mut State) ->
}; };
let mut command = Command::Unknown; let mut command = Command::Unknown;
for (_index, segment) in segments.iter().enumerate() { match segments[0].to_lowercase().as_str() {
match segment.to_lowercase().as_str() { "help" => command = Command::Help,
"location" => command = Command::Location, "last_location" => command = Command::LastLocation,
"goto" => command = Command::Goto, "last_online" => command = Command::LastOnline,
"stop" => command = Command::Stop, "follow_player" => command = Command::FollowPlayer,
_ => { "stop_follow_player" => command = Command::StopFollowPlayer,
let return_value = check_command(&mut command, &segment); "goto" => command = Command::Goto,
if !return_value.is_empty() { "stop_goto" => command = Command::StopGoto,
return return_value; "say" => command = Command::Say,
"toggle_alert_messages" => command = Command::ToggleAlertMessages,
"toggle_bot_status_messages" => command = Command::ToggleBotStatusMessages,
_ => (),
};
segments.remove(0);
let return_value = match command {
Command::Help => {
let mut commands = Vec::new();
for command in Command::iter() {
commands.push(format!("{:?}", command));
}
return "Commands: ".to_owned() + &commands.join(", ");
}
Command::LastLocation => {
if segments.len() < 1 {
return "Please tell me the name of the player!".to_string();
}
for (player, position_time_data) in state.player_locations.lock().unwrap().iter() {
if player.username == segments[0] || player.uuid.to_string() == segments[0] {
return format!(
"{} was last seen at {}, {}, {} ({})",
segments[0],
position_time_data.position[0],
position_time_data.position[1],
position_time_data.position[2],
Local
.timestamp_opt(position_time_data.time as i64, 0)
.unwrap()
.format("%Y/%m/%d %H:%M:%S")
);
} }
} }
}; format!("I haven't seen {} move anywhere near me...", segments[0])
}
if command != Command::Unknown {
let return_value = check_command(&mut command, &"".to_string());
if !return_value.is_empty() {
return return_value;
} }
Command::LastOnline => {
if segments.len() < 1 {
return "Please tell me the name of the player!".to_string();
}
for (player, player_time_data) in state.player_timestamps.lock().unwrap().iter() {
if player == &segments[0] {
return format!(
"{} - last join: {}, last chat message: {}, last leave: {}",
segments[0],
if player_time_data.join_time != 0 {
Local
.timestamp_opt(player_time_data.join_time as i64, 0)
.unwrap()
.format("%Y/%m/%d %H:%M:%S")
.to_string()
} else {
"never".to_string()
},
if player_time_data.chat_message_time != 0 {
Local
.timestamp_opt(player_time_data.chat_message_time as i64, 0)
.unwrap()
.format("%Y/%m/%d %H:%M:%S")
.to_string()
} else {
"never".to_string()
},
if player_time_data.leave_time != 0 {
Local
.timestamp_opt(player_time_data.leave_time as i64, 0)
.unwrap()
.format("%Y/%m/%d %H:%M:%S")
.to_string()
} else {
"never".to_string()
},
);
}
}
format!("I haven't seen {} online yet...", segments[0])
}
Command::FollowPlayer => {
if segments.len() < 1 {
return "Please tell me the name of the player!".to_string();
};
let mut found = true;
for (player, _position_time_data) in state.player_locations.lock().unwrap().iter() {
if player.username == segments[0] || player.uuid.to_string() == segments[0] {
found = true;
*state.followed_player.lock().unwrap() = Some(player.to_owned());
}
}
if found {
return format!("I am now following {}...", segments[0]);
} else {
return format!("I was unable to find {}...", segments[0]);
}
}
Command::StopFollowPlayer => {
*state.followed_player.lock().unwrap() = None;
let current_position = client.entity().pos().clone();
client.goto(BlockPosGoal {
pos: BlockPos {
x: current_position.x.round() as i32,
y: current_position.y.round() as i32,
z: current_position.z.round() as i32,
},
});
"I am no longer following anyone!".to_string()
}
Command::Goto => {
if segments.len() < 3 {
return "Please give me X, Y, and Z coordinates to go to!".to_string();
}
let mut coordinates: Vec<i32> = Vec::new();
for segment in segments {
coordinates.push(match segment.parse() {
Ok(number) => number,
Err(error) => return format!("Unable to parse coordinates: {}", error),
})
}
log_error(
client
.send_command_packet(&format!(
"msg {} I am now finding a path to {} {} {}...",
executor, coordinates[0], coordinates[1], coordinates[2]
))
.await,
);
client.goto(BlockPosGoal {
pos: BlockPos {
x: coordinates[0],
y: coordinates[1],
z: coordinates[2],
},
});
format!(
"I have found the path to {} {} {}!",
coordinates[0], coordinates[1], coordinates[2]
)
}
Command::StopGoto => {
let current_position = client.entity().pos().clone();
client.goto(BlockPosGoal {
pos: BlockPos {
x: current_position.x.round() as i32,
y: current_position.y.round() as i32,
z: current_position.z.round() as i32,
},
});
"I am no longer going anywhere!".to_string()
}
Command::Say => {
if segments.len() < 1 {
return "Please give me something to say!".to_string();
}
log_error(client.chat(segments.join(" ").as_str()).await);
"Successfully sent message!".to_string()
}
Command::ToggleAlertMessages => {
if state.alert_players.lock().unwrap().contains(executor) {
let mut players = state.alert_players.lock().unwrap().to_vec();
players.remove(
players
.iter()
.position(|item| *item == executor.to_owned())
.unwrap(),
);
*state.alert_players.lock().unwrap() = players;
"You will no longer be receiving alert messages!".to_string()
} else {
let mut players = state.alert_players.lock().unwrap().to_vec();
players.push(executor.to_owned());
*state.alert_players.lock().unwrap() = players;
"You will now be receiving alert messages!".to_string()
}
}
Command::ToggleBotStatusMessages => {
if state.bot_status_players.lock().unwrap().contains(executor) {
let mut players = state.bot_status_players.lock().unwrap().to_vec();
players.remove(
players
.iter()
.position(|item| *item == executor.to_owned())
.unwrap(),
);
*state.bot_status_players.lock().unwrap() = players;
"You will no longer be receiving bot status messages!".to_string()
} else {
let mut players = state.bot_status_players.lock().unwrap().to_vec();
players.push(executor.to_owned());
*state.bot_status_players.lock().unwrap() = players;
"You will now be receiving bot status messages!".to_string()
}
}
_ => "".to_string(),
};
if !return_value.is_empty() {
return return_value;
} }
"Sorry, I don't know what you mean...".to_string() "Sorry, I don't know what you mean...".to_string()

View File

@ -3,25 +3,17 @@ mod logging;
use azalea::pathfinder::BlockPosGoal; use azalea::pathfinder::BlockPosGoal;
use azalea::{prelude::*, BlockPos, ClientInformation}; use azalea::{prelude::*, BlockPos, ClientInformation};
use azalea_block::BlockState;
use azalea_protocol::packets::game::serverbound_client_command_packet::{ use azalea_protocol::packets::game::serverbound_client_command_packet::{
Action::PerformRespawn, ServerboundClientCommandPacket, Action::PerformRespawn, ServerboundClientCommandPacket,
}; };
use azalea_protocol::packets::game::ClientboundGamePacket;
use azalea_protocol::ServerAddress; use azalea_protocol::ServerAddress;
use logging::LogMessageType::*; use logging::LogMessageType::*;
use logging::{log_error, log_message}; use logging::{log_error, log_message};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
static NON_SOLID_BLOCKS: &[BlockState] = &[
BlockState::Air,
BlockState::Lava__0,
BlockState::Water__0,
BlockState::Cobweb,
BlockState::Grass,
BlockState::Fern,
BlockState::DeadBush,
];
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
struct BotConfiguration { struct BotConfiguration {
@ -32,6 +24,11 @@ struct BotConfiguration {
login_keyword: String, login_keyword: String,
login_command: String, login_command: String,
bot_owners: Vec<String>, bot_owners: Vec<String>,
whitelist: Vec<String>,
alert_players: Vec<String>,
alert_location: Vec<i32>,
alert_radius: i32,
alert_command: Vec<String>,
} }
impl Default for BotConfiguration { impl Default for BotConfiguration {
@ -44,6 +41,11 @@ impl Default for BotConfiguration {
login_keyword: "/login".to_string(), login_keyword: "/login".to_string(),
login_command: "login 1VerySafePassword!!!".to_string(), login_command: "login 1VerySafePassword!!!".to_string(),
bot_owners: vec![], bot_owners: vec![],
whitelist: vec![],
alert_players: vec![],
alert_location: vec![0, 0],
alert_radius: 100,
alert_command: Vec::new(),
} }
} }
} }
@ -73,56 +75,87 @@ async fn main() {
} }
}; };
match azalea::start(azalea::Options { loop {
account: Account::offline(&bot_configuration.username), match azalea::start(azalea::Options {
address: { account: Account::offline(&bot_configuration.username),
let segments: Vec<String> = bot_configuration address: {
.server_address let segments: Vec<String> = bot_configuration
.split(":") .server_address
.map(|item| item.to_string()) .split(":")
.collect(); .map(|item| item.to_string())
if segments.len() == 1 { .collect();
ServerAddress { if segments.len() == 1 {
host: segments[0].clone(), ServerAddress {
port: 25565, host: segments[0].clone(),
port: 25565,
}
} else if segments.len() == 2 {
ServerAddress {
host: segments[0].clone(),
port: segments[1].clone().parse().unwrap_or(25565),
}
} else {
log_message(
Error,
&"Unable to parse server address! Quitting...".to_string(),
);
return;
} }
} else if segments.len() == 2 { },
ServerAddress { state: State {
host: segments[0].clone(), bot_configuration: bot_configuration.clone(),
port: segments[1].clone().parse().unwrap_or(25565), logged_in: Arc::new(Mutex::new(false)),
} tick_counter: Arc::new(Mutex::new(0)),
} else { alert_second_counter: Arc::new(Mutex::new(0)),
log_message( followed_player: Arc::new(Mutex::new(None)),
Error, player_locations: Arc::new(Mutex::new(HashMap::new())),
&"Unable to parse server address! Quitting...".to_string(), player_timestamps: Arc::new(Mutex::new(HashMap::new())),
); alert_players: Arc::new(Mutex::new(bot_configuration.clone().alert_players)),
return; bot_status_players: Arc::new(Mutex::new(Vec::new())),
} },
}, plugins: plugins![],
state: State { handle,
bot_configuration, })
tick_counter: Arc::new(Mutex::new(0)), .await
pathfind_tick_counter: Arc::new(Mutex::new(0)), {
final_target: Arc::new(Mutex::new(None)), Ok(_) => (),
current_target: Arc::new(Mutex::new(None)), Err(error) => log_message(Error, &format!("An error occurred: {}", error)),
}, }
plugins: plugins![], std::thread::sleep(std::time::Duration::from_secs(5));
handle,
})
.await
{
Ok(_) => (),
Err(error) => log_message(Error, &format!("Unable to start ErrorNoWatcher: {}", error)),
} }
} }
#[derive(Default, Clone)] #[derive(Eq, Hash, PartialEq, PartialOrd, Debug, Clone)]
pub struct Player {
uuid: u128,
entity_id: u32,
username: String,
}
#[derive(Default, Debug, Clone)]
pub struct PositionTimeData {
position: Vec<i32>,
time: u64,
}
#[derive(Default, Debug, Clone)]
pub struct PlayerTimeData {
join_time: u64,
chat_message_time: u64,
leave_time: u64,
}
#[derive(Default, Debug, Clone)]
pub struct State { pub struct State {
bot_configuration: BotConfiguration, bot_configuration: BotConfiguration,
logged_in: Arc<Mutex<bool>>,
tick_counter: Arc<Mutex<u8>>, tick_counter: Arc<Mutex<u8>>,
pathfind_tick_counter: Arc<Mutex<u8>>, alert_second_counter: Arc<Mutex<u8>>,
final_target: Arc<Mutex<Option<Vec<i32>>>>, followed_player: Arc<Mutex<Option<Player>>>,
current_target: Arc<Mutex<Option<Vec<i32>>>>, player_locations: Arc<Mutex<HashMap<Player, PositionTimeData>>>,
player_timestamps: Arc<Mutex<HashMap<String, PlayerTimeData>>>,
alert_players: Arc<Mutex<Vec<String>>>,
bot_status_players: Arc<Mutex<Vec<String>>>,
} }
async fn handle(client: Client, event: Event, mut state: State) -> anyhow::Result<()> { async fn handle(client: Client, event: Event, mut state: State) -> anyhow::Result<()> {
@ -130,7 +163,7 @@ async fn handle(client: Client, event: Event, mut state: State) -> anyhow::Resul
Event::Login => { Event::Login => {
log_message( log_message(
Bot, Bot,
&"ErrorNoWatcher has successfully joined the server".to_string(), &"Successfully joined server, receiving initial data...".to_string(),
); );
log_error( log_error(
client client
@ -155,137 +188,188 @@ async fn handle(client: Client, event: Event, mut state: State) -> anyhow::Resul
) )
.await? .await?
} }
Event::AddPlayer(player) => {
let mut player_timestamps = state.player_timestamps.lock().unwrap().clone();
let mut current_player = player_timestamps
.get(&player.profile.name)
.unwrap_or(&PlayerTimeData {
join_time: 0,
chat_message_time: 0,
leave_time: 0,
})
.to_owned();
current_player.join_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
player_timestamps.insert(player.profile.name, current_player);
*state.player_timestamps.lock().unwrap() = player_timestamps;
}
Event::RemovePlayer(player) => {
let mut player_timestamps = state.player_timestamps.lock().unwrap().clone();
let mut current_player = player_timestamps
.get(&player.profile.name)
.unwrap_or(&PlayerTimeData {
join_time: 0,
chat_message_time: 0,
leave_time: 0,
})
.to_owned();
current_player.leave_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
player_timestamps.insert(player.profile.name, current_player);
*state.player_timestamps.lock().unwrap() = player_timestamps;
}
Event::Tick => { Event::Tick => {
*state.tick_counter.lock().unwrap() += 1; if !*state.logged_in.lock().unwrap() {
*state.pathfind_tick_counter.lock().unwrap() += 1; *state.logged_in.lock().unwrap() = true;
log_message(
Bot,
&"ErrorNoWatcher has finished initializing!".to_string(),
);
}
*state.tick_counter.lock().unwrap() += 1;
if *state.tick_counter.lock().unwrap() >= 20 { if *state.tick_counter.lock().unwrap() >= 20 {
*state.tick_counter.lock().unwrap() = 0; *state.tick_counter.lock().unwrap() = 0;
*state.alert_second_counter.lock().unwrap() += 1;
if state.current_target.lock().unwrap().is_some() { let followed_player = state.followed_player.lock().unwrap().to_owned();
let coordinates = if followed_player.is_some() {
(*state.current_target.lock().unwrap().clone().unwrap()).to_vec(); let player_locations = state.player_locations.lock().unwrap().clone();
println!("{:?}", coordinates); match player_locations.get(&followed_player.unwrap()) {
client.goto(BlockPosGoal { Some(position_time_data) => client.goto(BlockPosGoal {
pos: BlockPos { pos: BlockPos {
x: coordinates[0], x: position_time_data.position[0],
y: coordinates[1], y: position_time_data.position[1],
z: coordinates[2], z: position_time_data.position[2],
}, },
}); }),
None => *state.followed_player.lock().unwrap() = None,
}
} }
} }
if *state.pathfind_tick_counter.lock().unwrap() >= 10 { if *state.alert_second_counter.lock().unwrap() >= 5 {
*state.pathfind_tick_counter.lock().unwrap() = 0; *state.alert_second_counter.lock().unwrap() = 0;
if state.final_target.lock().unwrap().is_some() { let player_locations = state.player_locations.lock().unwrap().clone();
let current_position = client.entity().pos().clone(); for (player, position_time_data) in player_locations {
let target_position = if ((state.bot_configuration.alert_location[0]
state.final_target.lock().unwrap().clone().unwrap().to_vec(); - state.bot_configuration.alert_radius)
let mut new_position = Vec::new(); ..(state.bot_configuration.alert_location[0]
+ state.bot_configuration.alert_radius))
if (current_position.x as i32) < target_position[0] { .contains(&position_time_data.position[0])
new_position.push(current_position.x as i32 + 2); || ((state.bot_configuration.alert_location[1]
} else { - state.bot_configuration.alert_radius)
new_position.push(current_position.x as i32 - 2); ..(state.bot_configuration.alert_location[1]
} + state.bot_configuration.alert_radius))
new_position.push(current_position.y as i32 + 2); .contains(&position_time_data.position[2])
if (current_position.z as i32) < target_position[2] { {
new_position.push(current_position.z as i32 + 2); if !state.bot_configuration.whitelist.contains(&player.username) {
} else { let alert_players = state.alert_players.lock().unwrap().clone();
new_position.push(current_position.z as i32 - 2); for alert_player in alert_players {
} log_error(
client
while NON_SOLID_BLOCKS.to_vec().contains( .send_command_packet(&format!(
&client "msg {} {}",
.world alert_player,
.read() format!(
.get_block_state(&BlockPos { "{} is near our base at {} {} {}!",
x: new_position[0], player.username,
y: new_position[1] - 1, position_time_data.position[0],
z: new_position[2], position_time_data.position[1],
}) position_time_data.position[2],
.unwrap(), )
) { ))
new_position[1] -= 1; .await,
} );
}
while !NON_SOLID_BLOCKS.to_vec().contains( let mut alert_command = state.bot_configuration.alert_command.to_vec();
&client for argument in alert_command.iter_mut() {
.world *argument = argument.replace("{player_name}", &player.username);
.read() *argument = argument
.get_block_state(&BlockPos { .replace("{x}", &position_time_data.position[0].to_string());
x: new_position[0], *argument = argument
y: new_position[1], .replace("{y}", &position_time_data.position[1].to_string());
z: new_position[2], *argument = argument
}) .replace("{z}", &position_time_data.position[2].to_string());
.unwrap(), }
) { if alert_command.len() >= 1 {
if new_position[0] < target_position[0] { log_message(Bot, &"Executing alert shell command...".to_string());
new_position[0] += 1 let command_name = alert_command[0].clone();
} else { alert_command.remove(0);
new_position[0] -= 1 log_error(
std::process::Command::new(command_name)
.args(alert_command)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn(),
);
}
} }
} }
while !NON_SOLID_BLOCKS.to_vec().contains(
&client
.world
.read()
.get_block_state(&BlockPos {
x: new_position[0],
y: new_position[1],
z: new_position[2],
})
.unwrap(),
) {
if new_position[2] < target_position[2] {
new_position[2] += 1
} else {
new_position[2] -= 1
}
}
while NON_SOLID_BLOCKS.to_vec().contains(
&client
.world
.read()
.get_block_state(&BlockPos {
x: new_position[0],
y: new_position[1] - 1,
z: new_position[2],
})
.unwrap(),
) {
if new_position[0] < target_position[0] {
new_position[0] += 1
} else {
new_position[0] -= 1
}
}
while NON_SOLID_BLOCKS.to_vec().contains(
&client
.world
.read()
.get_block_state(&BlockPos {
x: new_position[0],
y: new_position[1] - 1,
z: new_position[2],
})
.unwrap(),
) {
if new_position[2] < target_position[2] {
new_position[2] += 1
} else {
new_position[2] -= 1
}
}
*state.current_target.lock().unwrap() = Some(new_position);
} }
} }
} }
Event::Packet(packet) => match packet.as_ref() {
ClientboundGamePacket::MoveEntityPos(packet) => {
let world = client.world.read();
let entity = world.entity(packet.entity_id).unwrap();
for (uuid, player) in client.players.read().iter() {
if uuid.as_u128() == entity.uuid.as_u128() {
let position = entity.pos();
let mut player_locations = state.player_locations.lock().unwrap().clone();
player_locations.insert(
Player {
uuid: uuid.as_u128(),
entity_id: entity.id,
username: player.profile.name.clone(),
},
PositionTimeData {
position: vec![
position.x as i32,
position.y as i32,
position.z as i32,
],
time: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
},
);
*state.player_locations.lock().unwrap() = player_locations;
}
}
}
ClientboundGamePacket::SetHealth(packet) => {
let bot_status_players: Vec<String> = state
.bot_status_players
.lock()
.unwrap()
.iter()
.map(|item| item.to_owned())
.collect();
for player in bot_status_players {
log_error(
client
.send_command_packet(&format!(
"msg {} {}",
player,
format!(
"Health: {}/20, Food: {}/20, Saturation: {}/20",
packet.health, packet.food, packet.saturation
)
))
.await,
);
}
}
_ => (),
},
Event::Chat(message) => { Event::Chat(message) => {
log_message(Chat, &message.message().to_ansi()); log_message(Chat, &message.message().to_ansi());
@ -343,11 +427,31 @@ async fn handle(client: Client, event: Event, mut state: State) -> anyhow::Resul
.send_command_packet(&format!( .send_command_packet(&format!(
"msg {} {}", "msg {} {}",
bot_owner, bot_owner,
&bot::process_command(&command, &client, &mut state), &bot::process_command(&command, &bot_owner, &client, &mut state)
.await,
)) ))
.await, .await,
); );
} }
let mut player_timestamps = state.player_timestamps.lock().unwrap().clone();
let mut current_player = player_timestamps
.get(&message.username().unwrap_or("Someone".to_string()))
.unwrap_or(&PlayerTimeData {
join_time: 0,
chat_message_time: 0,
leave_time: 0,
})
.to_owned();
current_player.chat_message_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
player_timestamps.insert(
message.username().unwrap_or("Someone".to_string()),
current_player,
);
*state.player_timestamps.lock().unwrap() = player_timestamps;
} }
} }
_ => {} _ => {}