inital commit
This commit is contained in:
commit
ebcffcc9e3
9 changed files with 279 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.vscode
|
||||
|
||||
/target
|
||||
/data
|
||||
|
||||
Cargo.lock
|
||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "zz-map-stats-analyser"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.42"
|
||||
serde = {version = "1.0.228", features = ["derive"]}
|
||||
serde_json = "1.0"
|
||||
126
src/importer.rs
Normal file
126
src/importer.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
use std::{collections::HashMap, fs};
|
||||
|
||||
use chrono::{DateTime, Datelike};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::{MAPSTAT_AUTHOR_ID, SERVER, TARGET_YEAR};
|
||||
|
||||
//structure of the message with the data we want to extract
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ChatData {
|
||||
messages: Vec<ChatMessage>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ChatMessage {
|
||||
id: String,
|
||||
timestamp: String,
|
||||
author: Map<String, Value>, // there are 2 bots, one that announces a map and the other that announce the end result. For now only mapstats is important.
|
||||
// getting data from maunzbot can provide extra metrics like map time and player retention, getting those from only mapstats is too unreliable.
|
||||
embeds: Vec<MapStatsEmbed>, // all data is shown in embeds, embeds are represented as arrays but there will always only be 1 object
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct MapStatsEmbed {
|
||||
title: String, // Won or Lost
|
||||
fields: Vec<Value>, // Map, Server, Players, Wave
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct Stat {
|
||||
pub won: bool,
|
||||
pub timestamp: String, // the time the map was declared finished
|
||||
pub wave_ended: i8,
|
||||
pub player_count: i32,
|
||||
}
|
||||
|
||||
pub fn import() -> HashMap<String, HashMap<String, Stat>> {
|
||||
let chatdatafile = "./data/rawdata.json";
|
||||
let jsonstring = fs::read_to_string(chatdatafile).unwrap();
|
||||
let jsondata: ChatData = serde_json::from_str(&jsonstring).unwrap();
|
||||
|
||||
let mut map_stats: HashMap<String, HashMap<String, Stat>> = HashMap::new();
|
||||
|
||||
for message in &jsondata.messages {
|
||||
if message.author["id"] == MAPSTAT_AUTHOR_ID {
|
||||
let timestamp = DateTime::parse_from_rfc3339(&message.timestamp).unwrap();
|
||||
|
||||
|
||||
if timestamp.year().to_string().as_str() != TARGET_YEAR {
|
||||
// println!("year is {}, skipping", timestamp.year());
|
||||
continue;
|
||||
}
|
||||
let mut map_name = "".to_string();
|
||||
let mut stat = Stat {
|
||||
won: false,
|
||||
timestamp: timestamp.to_string(),
|
||||
wave_ended: 0,
|
||||
player_count: 0,
|
||||
};
|
||||
|
||||
let map_won = &message.embeds[0].title;
|
||||
if map_won == "Won" {
|
||||
stat.won = true
|
||||
} else if map_won == "Lost" {
|
||||
// do nothing :)
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
let chat_embed = &message.embeds[0].fields;
|
||||
for embed_value in chat_embed {
|
||||
let key = embed_value["name"].as_str().unwrap();
|
||||
|
||||
match &key {
|
||||
&"Server" => {
|
||||
// println!("{}", embed_value["value"]);
|
||||
if embed_value["value"].as_str().unwrap() != SERVER {
|
||||
println!("server is: {} - skipping", embed_value["value"]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
&"Map" => {
|
||||
let map_string = &embed_value["value"].to_string().replace('"', "");
|
||||
map_name = map_string.clone();
|
||||
}
|
||||
&"Players" => {
|
||||
let player_count = embed_value["value"].to_string().replace('"', "");
|
||||
stat.player_count = player_count.parse().unwrap();
|
||||
}
|
||||
&"Wave" => {
|
||||
let wave_count = &embed_value["value"].to_string().replace('"', "");
|
||||
stat.wave_ended = wave_count.parse().unwrap();
|
||||
}
|
||||
|
||||
_ => {} // do nothing :3
|
||||
}
|
||||
// if embed_value["name"] == "Map" {
|
||||
// println!("{}", embed_value["value"])
|
||||
// }
|
||||
}
|
||||
// println!(
|
||||
// "Map: {} - [Won?: {}, Wave ended: {}, Player count: {}] - {}",
|
||||
// map_name, stat.won, stat.wave_ended, stat.player_count, stat.timestamp
|
||||
// );
|
||||
|
||||
if map_name.as_str() == "" {
|
||||
// println!("no mapname set, skipping stat...");
|
||||
continue;
|
||||
}
|
||||
|
||||
if map_stats.contains_key(&map_name) {
|
||||
// println!("map exists, adding stats");
|
||||
let mut statvec = map_stats.get(&map_name).unwrap().clone();
|
||||
statvec.insert(message.id.clone(), stat);
|
||||
map_stats.insert(map_name, statvec);
|
||||
} else {
|
||||
let mut statvec = HashMap::new();
|
||||
statvec.insert(message.id.clone(), stat);
|
||||
map_stats.insert(map_name, statvec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map_stats;
|
||||
}
|
||||
120
src/main.rs
Normal file
120
src/main.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{File, create_dir_all},
|
||||
io::Write,
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
|
||||
mod importer;
|
||||
|
||||
// struct MapStat {
|
||||
// map: String,
|
||||
// wins: i64,
|
||||
// lost: i64,
|
||||
// players: i32,
|
||||
// }
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Stats {
|
||||
most_played: Vec<(String, usize)>,
|
||||
most_players: Vec<(String, String, i32)>,
|
||||
most_wins: Vec<(String, usize)>,
|
||||
most_lost: Vec<(String, usize)>,
|
||||
most_human_sided: Vec<(String, f32)>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct OverallStats {
|
||||
standard: Stats,
|
||||
obj: Stats,
|
||||
monthly: HashMap<String, Stats>,
|
||||
}
|
||||
|
||||
const MAPSTAT_AUTHOR_ID: &str = "879429830628753429";
|
||||
const SERVER: &str = "wzs";
|
||||
const TARGET_YEAR: &str = "2025";
|
||||
|
||||
const RESULT_PATH: &str = "./data/results/";
|
||||
|
||||
fn main() {
|
||||
//check if directory exists and creates it
|
||||
create_dir_all(RESULT_PATH).expect("Failed to create directory...");
|
||||
|
||||
let map_stats = importer::import();
|
||||
|
||||
let processed_data_path = RESULT_PATH.to_owned() + "alldata.json";
|
||||
|
||||
match serde_json::to_string(&map_stats) {
|
||||
Ok(json) => {
|
||||
let mut file = File::create(processed_data_path).expect("Unable to write file...");
|
||||
file.write_all(json.as_bytes())
|
||||
.expect("Failed to write data...");
|
||||
}
|
||||
Err(e) => println!("Serialization error: {}", e),
|
||||
}
|
||||
|
||||
// let most_played = sortplaycount(map_stats.clone());
|
||||
|
||||
let mut most_played = vec![];
|
||||
let mut highest_playercount = vec![];
|
||||
let mut most_wins = vec![];
|
||||
let mut most_lost = vec![];
|
||||
let mut most_human = vec![];
|
||||
|
||||
for (map, stats) in map_stats.iter() {
|
||||
most_played.push((map.clone(), stats.len()));
|
||||
let mut wins = vec![];
|
||||
let mut lost = vec![];
|
||||
for (id, stat) in stats.iter() {
|
||||
highest_playercount.push((map.clone(), stat.timestamp.clone(), stat.player_count));
|
||||
if stat.won {
|
||||
wins.push(id);
|
||||
} else {
|
||||
lost.push(id);
|
||||
}
|
||||
}
|
||||
most_wins.push((map.clone(), wins.len()));
|
||||
most_lost.push((map.clone(), lost.len()));
|
||||
let win_ratio: f32 = wins.len() as f32 / stats.len() as f32;
|
||||
most_human.push((map.clone(), win_ratio));
|
||||
}
|
||||
|
||||
most_played.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
highest_playercount.sort_by(|a, b| b.2.cmp(&a.2));
|
||||
most_wins.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
most_lost.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
most_human.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
|
||||
let report: Stats = Stats {
|
||||
most_played,
|
||||
most_players: highest_playercount,
|
||||
most_wins: most_wins,
|
||||
most_lost: most_lost,
|
||||
most_human_sided: most_human,
|
||||
};
|
||||
|
||||
match serde_json::to_string(&report) {
|
||||
Ok(json) => {
|
||||
let mut file = File::create(RESULT_PATH.to_owned() + "report.json").expect("Unable to write file...");
|
||||
file.write_all(json.as_bytes())
|
||||
.expect("Failed to write data...");
|
||||
}
|
||||
Err(e) => println!("Serialization error: {}", e),
|
||||
}
|
||||
|
||||
// for (map, playcount) in most_played.iter() {
|
||||
// println!("map: {} - played {} times", map, playcount)
|
||||
// }
|
||||
|
||||
// for (map, stats) in map_stats.iter() {
|
||||
// for (_id, stat) in stats.iter() {
|
||||
// highest_playercount.push((map, stat.timestamp.clone(), stat.player_count));
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (map, time, playercount) in highest_playercount {
|
||||
// println!("map: {} - player count: {} @ {}", map, playercount, time)
|
||||
// }
|
||||
}
|
||||
1
src/statgen.rs
Normal file
1
src/statgen.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod playcount;
|
||||
15
src/statgen/playcount.rs
Normal file
15
src/statgen/playcount.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::importer::Stat;
|
||||
|
||||
pub fn sortplaycount(map_stats: HashMap<String, HashMap<String, Stat>>) -> Vec<(String, usize)> {
|
||||
let mut most_played = vec![];
|
||||
|
||||
for (map, stats) in map_stats.iter() {
|
||||
let play_count = (map.clone(), stats.len());
|
||||
most_played.push(play_count);
|
||||
}
|
||||
|
||||
most_played.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
return most_played;
|
||||
}
|
||||
2
src/statgen/report.rs
Normal file
2
src/statgen/report.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
//given a timeframe and will compile all data into a report
|
||||
//add option for a month report or year
|
||||
0
src/statgen/wincount.rs
Normal file
0
src/statgen/wincount.rs
Normal file
0
src/statgen/winratio.rs
Normal file
0
src/statgen/winratio.rs
Normal file
Loading…
Reference in a new issue