From 4a5422b1a18e1485648438acc3fb8a24833d19f2 Mon Sep 17 00:00:00 2001 From: wrk Date: Thu, 1 Jun 2023 19:10:11 +0200 Subject: [PATCH] Init --- .gitignore | 4 + Cargo.toml | 15 ++ src/main.rs | 504 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 523 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03ba1f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock +irc_config.yaml +hall_of_fame.yaml \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b32203f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "hardfight" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ircie = { path = "../drugwars_v2/ircie" } +tokio = { version = "1.28.2", features = ["full"] } +env_logger = "0.10.0" +rand = "0.8.5" +itertools = "0.10.5" +serde = { version = "1.0.163", features = ["derive"] } +serde_yaml = "0.9.21" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cf9324e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,504 @@ +use std::{ + collections::{HashMap, HashSet}, + fs::File, + io::{Read, Write}, + ops::{Deref, DerefMut}, + time::Duration, +}; + +use ircie::{ + format::{Color, Msg}, + system::IntoResponse, + system_params::{AnyArguments, Res, ResMut}, + Irc, +}; +use itertools::Itertools; +use rand::{ + rngs::StdRng, + seq::{IteratorRandom, SliceRandom}, + Rng, SeedableRng, +}; +use serde::{Deserialize, Serialize}; + +const SAVE_LOC: &'static str = "hall_of_fame.yaml"; + +const PRESENTATIONS: &'static [&'static str] = &[ + "A faggot coming straight from the underground!", + "HARDCHATTER from the pong dynasty!", + "Tonight's most dangerous tranny!", + "Who the fuck invited this nigga? ! ? ! ? ! ? ! ?", +]; + +const BODYPARTS: &'static [&'static str] = &[ + "head", + "groin", + "left arm", + "right arm", + "left leg", + "right leg", + "soul", + "left eye", + "right eye", + "mouth", + "nose", + "chin", + "left ear", + "right ear", + "nick", + "torso", + "stomach", + "ribs", + "left foot", + "right foot", + "ass", + "small dick", + "deep pussy", + "hair", + "purse", + "sunglasses", + "teeth", + "anus", +]; + +const ACTIONS: &'static [&'static str] = &[ + "punches", + "kicks", + "bites", + "scratches", + "pinches", + "high kicks", + "low kicks", + "throws a left hook at", + "throws a right hook at", + "does a flying heel kick at", + "slaps", + "headbutts", + "hits", + "hammerfists", + "front kicks", + "stomps", + "packets", + "sues", + "hacks", + "doxes", + "haunts", + "curses with chaos magick", + "violently hisses at", + "cums on", + "lumps out", + "humps", +]; + +const COLORS: &'static [&'static Color] = &[ + &Color::Black, + &Color::Blue, + &Color::Brown, + &Color::Cyan, + &Color::Gray, + &Color::Green, + &Color::LightBlue, + &Color::LightGray, + &Color::LightGreen, + &Color::Magenta, + &Color::Orange, + &Color::Purple, + &Color::Red, + &Color::Teal, +]; + +struct Fighter { + nick: String, + health: f32, + color: Color, + team_idx: usize, +} + +#[derive(Default, Serialize, Deserialize)] +struct HallOfFame(HashMap); + +impl Deref for HallOfFame { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for HallOfFame { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl HallOfFame { + pub fn add_winner(&mut self, nick: &str) { + let winner = match self.entry(nick.to_owned()) { + std::collections::hash_map::Entry::Occupied(o) => o.into_mut(), + std::collections::hash_map::Entry::Vacant(v) => v.insert(0), + }; + *winner += 3; + } + pub fn add_fucking_looser(&mut self, nick: &str) { + let fucking_looser = match self.entry(nick.to_owned()) { + std::collections::hash_map::Entry::Occupied(o) => o.into_mut(), + std::collections::hash_map::Entry::Vacant(v) => v.insert(0), + }; + + *fucking_looser -= 1; + } + + pub fn load(path: &str) -> std::io::Result { + let Ok(mut file) = File::open(path) else { + return Ok(Self::default()); + }; + + let mut content = String::new(); + file.read_to_string(&mut content)?; + Ok(serde_yaml::from_str(&content).unwrap()) + } + + pub fn save(&self, path: &str) -> std::io::Result<()> { + let content_str = serde_yaml::to_string(self).unwrap(); + let mut content = content_str.as_bytes(); + + let mut file = File::create(path)?; + + file.write_all(&mut content)?; + Ok(()) + } +} + +impl Fighter { + pub fn new(nick: &str, color: Color, team_idx: usize) -> Self { + Self { + nick: nick.to_owned(), + health: 100., + color, + team_idx, + } + } +} + +impl Default for Fighter { + fn default() -> Self { + Self { + nick: Default::default(), + health: 100., + color: Color::Teal, + team_idx: 0, + } + } +} + +#[derive(Default, PartialEq, Eq)] +enum FightKind { + #[default] + Duel, + FreeForAll, + TeamBattle, +} + +#[derive(Default, PartialEq, Eq)] +enum FightStatus { + Happening, + #[default] + Idle, +} + +#[derive(Default)] +struct Fight { + status: FightStatus, + kind: FightKind, + fighters: Vec, +} + +#[tokio::main] +async fn main() -> std::io::Result<()> { + env_logger::init(); + + let mut irc = Irc::from_config("irc_config.yaml").await?; + + irc.add_resource(Fight::default()) + .await + .add_resource(StdRng::from_entropy()) + .await + .add_resource(HallOfFame::load(SAVE_LOC).unwrap()) + .await + .add_interval_task(Duration::from_secs(1), fight) + .await + .add_system("f", new_fight) + .await + .add_system("hof", show_hall_of_fame) + .await; + + irc.run().await?; + Ok(()) +} + +fn fight( + mut fight: ResMut, + mut rng: ResMut, + mut hall_of_fame: ResMut, +) -> impl IntoResponse { + if fight.status == FightStatus::Idle { + std::thread::sleep(Duration::from_millis(50)); + return None; + } + + let teams_remaining: Vec<_> = fight + .fighters + .iter() + .map(|f| f.team_idx) + .collect::>() + .into_iter() + .collect(); + + if teams_remaining.len() == 1 { + let team_idx = *teams_remaining.iter().next().unwrap(); + fight.status = FightStatus::Idle; + + let winners = fight + .fighters + .iter() + .filter(|f| f.team_idx == team_idx) + .collect::>(); + + let mut lines = vec![]; + + if fight.fighters.len() == 1 { + lines.push( + Msg::new() + .color(Color::Yellow) + .text("We have a winner! -> ") + .color(winners[0].color) + .text(&winners[0].nick) + .color(Color::Yellow) + .text(" <- !"), + ); + hall_of_fame.add_winner(&winners[0].nick); + } else { + lines.push(Msg::new().color(Color::Yellow).text("We have winners!")); + for w in &winners { + lines.push( + Msg::new() + .color(Color::Yellow) + .text("-> ") + .color(w.color) + .text(&w.nick) + .color(Color::Yellow) + .text(" <-"), + ); + + hall_of_fame.add_winner(&w.nick); + } + } + + for w in &winners { + lines.push(Msg::new().text("!beer ").text(&w.nick)); + } + + fight.fighters = vec![]; + hall_of_fame.save(SAVE_LOC).unwrap(); + + return Some((false, lines)); + } + + let mut lines = vec![]; + + let body_part = BODYPARTS.choose(&mut *rng).unwrap(); + let action = ACTIONS.choose(&mut *rng).unwrap(); + let damage = rng.gen::() * 80.; + + let victim_idx = rng.gen_range(0..fight.fighters.len()); + let fucking_victim = fight.fighters.get_mut(victim_idx).unwrap(); + fucking_victim.health -= damage; + + let fucking_victim = fight.fighters.get(victim_idx).unwrap(); + + let attacker = if fight + .fighters + .iter() + .filter(|f| f.team_idx == fucking_victim.team_idx && f.nick != fucking_victim.nick) + .count() + != 0 + && rng.gen_bool(1. / 4.) + { + let attacker = fight + .fighters + .iter() + .filter(|f| f.team_idx == fucking_victim.team_idx && f.nick != fucking_victim.nick) + .choose(&mut *rng) + .unwrap(); + + lines.push( + Msg::new() + .color(Color::Yellow) + .text("Oh no! ") + .color(attacker.color) + .text(&attacker.nick) + .color(Color::Yellow) + .text(&format!(" is fucking retarded and is attacking his mate!")), + ); + attacker + } else { + fight + .fighters + .iter() + .filter(|f| f.team_idx != fucking_victim.team_idx) + .choose(&mut *rng) + .unwrap() + }; + + lines.push( + Msg::new() + .color(attacker.color) + .text(&attacker.nick) + .reset() + .text(&format!(" {} ", action)) + .color(fucking_victim.color) + .text(&fucking_victim.nick) + .reset() + .text(&format!("'s {}!", body_part)), + ); + + if fucking_victim.health <= 0. { + lines.push( + Msg::new() + .color(fucking_victim.color) + .text(&fucking_victim.nick) + .color(Color::Yellow) + .text(" is lying dead!"), + ); + hall_of_fame.add_fucking_looser(&fucking_victim.nick); + fight.fighters.remove(victim_idx); + } + + Some((false, lines)) +} + +fn new_fight( + arguments: AnyArguments, + mut fight: ResMut, + mut rng: ResMut, +) -> impl IntoResponse { + if fight.status == FightStatus::Happening { + return Err("Shut up and watch the show".to_owned()); + } + + if arguments.len() < 2 { + return Err("nigga's supposed to fight alone?".to_owned()); + } + + fight.kind = if arguments.len() == 2 { + FightKind::Duel + } else if arguments.contains(&"vs") { + FightKind::TeamBattle + } else { + FightKind::FreeForAll + }; + + let mut colors = COLORS.iter().map(|c| *c.clone()).collect::>(); + colors.shuffle(&mut *rng); + + match fight.kind { + FightKind::Duel => fight.fighters.append(&mut vec![ + Fighter::new(arguments[0], colors.pop().unwrap(), 0), + Fighter::new(arguments[1], colors.pop().unwrap(), 1), + ]), + FightKind::FreeForAll => { + for (idx, f) in arguments.iter().enumerate() { + let color = colors.pop().unwrap(); + fight.fighters.push(Fighter::new(f, color, idx)); + + if colors.len() == 0 { + colors = COLORS.iter().map(|c| *c.clone()).collect::>(); + colors.shuffle(&mut *rng); + } + } + } + FightKind::TeamBattle => { + let mut team_idx = 0; + let mut team_color = colors.pop().unwrap(); + for f in arguments.iter() { + if f == &"vs" { + team_idx += 1; + team_color = colors.pop().unwrap(); + continue; + } + fight.fighters.push(Fighter::new(f, team_color, team_idx)); + } + } + } + + fight.status = FightStatus::Happening; + + let mut init_msg = vec![Msg::new() + .color(Color::Yellow) + .text("THE FIGHT IS ABOUT TO BEGIN! TAKE YOUR BETS!")]; + + match fight.kind { + FightKind::Duel => init_msg.push( + Msg::new() + .color(Color::Yellow) + .text("Tonight's fight is a duel!"), + ), + FightKind::FreeForAll => init_msg.push( + Msg::new() + .color(Color::Yellow) + .text("Tonight's fight is a free for all!"), + ), + FightKind::TeamBattle => init_msg.push( + Msg::new() + .color(Color::Yellow) + .text("Tonight's fight is a team battle!"), + ), + } + + init_msg.push( + Msg::new() + .color(Color::Yellow) + .text("Everyone please welcome our contenders:"), + ); + + for f in &fight.fighters { + init_msg.push( + Msg::new() + .color(f.color) + .text(&f.nick) + .color(Color::Yellow) + .text("! ") + .text(PRESENTATIONS.choose(&mut *rng).unwrap()), + ) + } + + init_msg.push(Msg::new().color(Color::Yellow).text("TO THE DEATH!")); + + Ok((false, init_msg)) +} + +fn show_hall_of_fame(hall_of_fame: Res) -> impl IntoResponse { + let sorted_hof = hall_of_fame + .iter() + .sorted_by_key(|k| k.1) + .rev() + .collect::>(); + let mut lines = vec![Msg::new() + .color(Color::Yellow) + .text("Let thee be champions")]; + + for fighter in &sorted_hof[0..sorted_hof.len().min(10)] { + lines.push( + Msg::new() + .color(Color::Green) + .text("-> ") + .color(Color::Cyan) + .text(fighter.0) + .color(Color::Green) + .text(" <- ") + .color(Color::Yellow) + .text(format!("({} pts)", fighter.1)), + ) + } + + lines +}