2023-06-01 17:10:11 +00:00
|
|
|
use std::{
|
|
|
|
collections::{HashMap, HashSet},
|
|
|
|
fs::File,
|
|
|
|
io::{Read, Write},
|
|
|
|
ops::{Deref, DerefMut},
|
|
|
|
time::Duration,
|
|
|
|
};
|
|
|
|
|
|
|
|
use ircie::{
|
|
|
|
format::{Color, Msg},
|
|
|
|
system::IntoResponse,
|
2023-06-09 00:00:32 +00:00
|
|
|
system_params::{AnyArguments, Channel, Context, Res, ResMut},
|
2023-06-01 17:10:11 +00:00
|
|
|
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",
|
2023-06-01 18:32:23 +00:00
|
|
|
"casts hadoken at",
|
2023-06-01 17:10:11 +00:00
|
|
|
"sues",
|
|
|
|
"hacks",
|
|
|
|
"doxes",
|
|
|
|
"haunts",
|
|
|
|
"curses with chaos magick",
|
|
|
|
"violently hisses at",
|
|
|
|
"cums on",
|
|
|
|
"lumps out",
|
|
|
|
"humps",
|
|
|
|
];
|
|
|
|
|
|
|
|
const COLORS: &'static [&'static Color] = &[
|
|
|
|
&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<String, isize>);
|
|
|
|
|
|
|
|
impl Deref for HallOfFame {
|
|
|
|
type Target = HashMap<String, isize>;
|
|
|
|
|
|
|
|
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<Self> {
|
|
|
|
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,
|
2023-06-09 00:00:32 +00:00
|
|
|
channel: String,
|
2023-06-01 17:10:11 +00:00
|
|
|
kind: FightKind,
|
|
|
|
fighters: Vec<Fighter>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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)
|
2023-06-01 18:32:23 +00:00
|
|
|
.await
|
|
|
|
.add_system("h", show_help)
|
|
|
|
.await
|
|
|
|
.add_system("s", show_status)
|
2023-06-01 17:10:11 +00:00
|
|
|
.await;
|
|
|
|
|
|
|
|
irc.run().await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fight(
|
2023-06-09 00:00:32 +00:00
|
|
|
mut ctx: Context,
|
2023-06-01 17:10:11 +00:00
|
|
|
mut fight: ResMut<Fight>,
|
|
|
|
mut rng: ResMut<StdRng>,
|
|
|
|
mut hall_of_fame: ResMut<HallOfFame>,
|
2023-06-09 00:00:32 +00:00
|
|
|
) {
|
2023-06-01 17:10:11 +00:00
|
|
|
if fight.status == FightStatus::Idle {
|
|
|
|
std::thread::sleep(Duration::from_millis(50));
|
2023-06-09 00:00:32 +00:00
|
|
|
return;
|
2023-06-01 17:10:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let teams_remaining: Vec<_> = fight
|
|
|
|
.fighters
|
|
|
|
.iter()
|
|
|
|
.map(|f| f.team_idx)
|
|
|
|
.collect::<HashSet<_>>()
|
|
|
|
.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::<Vec<_>>();
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
2023-06-09 00:00:32 +00:00
|
|
|
for line in lines {
|
|
|
|
ctx.privmsg(&fight.channel, &line.to_string())
|
|
|
|
}
|
|
|
|
fight.channel = "".to_owned();
|
|
|
|
return;
|
2023-06-01 17:10:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut lines = vec![];
|
|
|
|
|
|
|
|
let body_part = BODYPARTS.choose(&mut *rng).unwrap();
|
|
|
|
let action = ACTIONS.choose(&mut *rng).unwrap();
|
|
|
|
let damage = rng.gen::<f32>() * 80.;
|
|
|
|
|
|
|
|
let victim_idx = rng.gen_range(0..fight.fighters.len());
|
2023-06-01 17:12:23 +00:00
|
|
|
let mut fucking_victim = fight.fighters.get_mut(victim_idx).unwrap();
|
2023-06-01 18:32:23 +00:00
|
|
|
|
2023-06-01 17:12:23 +00:00
|
|
|
if fucking_victim.nick == "wrk" {
|
|
|
|
// ;)
|
|
|
|
fucking_victim = fight.fighters.get_mut(victim_idx).unwrap();
|
|
|
|
}
|
|
|
|
|
2023-06-01 17:10:11 +00:00
|
|
|
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()
|
2023-06-01 18:38:27 +00:00
|
|
|
.text(&format!("'s {}! (-{:.2} hp)", body_part, damage)),
|
2023-06-01 17:10:11 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-06-09 00:00:32 +00:00
|
|
|
for line in lines {
|
|
|
|
ctx.privmsg(&fight.channel, &line.to_string())
|
|
|
|
}
|
2023-06-01 17:10:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn new_fight(
|
|
|
|
arguments: AnyArguments,
|
2023-06-09 00:00:32 +00:00
|
|
|
channel: Channel,
|
2023-06-01 17:10:11 +00:00
|
|
|
mut fight: ResMut<Fight>,
|
|
|
|
mut rng: ResMut<StdRng>,
|
|
|
|
) -> 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::<Vec<_>>();
|
|
|
|
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::<Vec<_>>();
|
|
|
|
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;
|
|
|
|
|
2023-06-09 00:00:32 +00:00
|
|
|
fight.channel = channel.to_owned();
|
|
|
|
|
2023-06-01 17:10:11 +00:00
|
|
|
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<HallOfFame>) -> impl IntoResponse {
|
|
|
|
let sorted_hof = hall_of_fame
|
|
|
|
.iter()
|
|
|
|
.sorted_by_key(|k| k.1)
|
|
|
|
.rev()
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
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
|
|
|
|
}
|
2023-06-01 18:32:23 +00:00
|
|
|
|
|
|
|
fn show_help() -> impl IntoResponse {
|
|
|
|
(
|
|
|
|
false,
|
|
|
|
vec![
|
|
|
|
",f <nick> <nick> | duel fight",
|
|
|
|
",f <nick> ... vs <nick> ... | team battle",
|
|
|
|
",f <nick> <nick> <nick> ... | free for all",
|
|
|
|
",s | show the current fight status",
|
|
|
|
",hof | hall of fame",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn show_status(fight: Res<Fight>) -> impl IntoResponse {
|
|
|
|
if fight.status == FightStatus::Idle {
|
|
|
|
return "Noone is fighting you bunch of pussies.".to_owned();
|
|
|
|
}
|
|
|
|
|
|
|
|
format!("{} fighters remaining", fight.fighters.len())
|
|
|
|
}
|