first commit
This commit is contained in:
commit
0cb536b42b
38 changed files with 9044 additions and 0 deletions
416
src/commands/eco/fish.rs
Normal file
416
src/commands/eco/fish.rs
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
use crate::User;
|
||||
use crate::client::Client;
|
||||
use crate::client::ClientEvent;
|
||||
use crate::client::Player;
|
||||
use crate::commands::Command;
|
||||
use crate::commands::calculate_fish_value;
|
||||
|
||||
use crate::commands::argument::{ArgumentSpec, ArgumentType, ParsedArgument, ParsedArguments};
|
||||
use chrono::Duration as ChronoDuration;
|
||||
use chrono::Utc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use rand::Rng;
|
||||
|
||||
use rand::prelude::IndexedRandom;
|
||||
|
||||
use sqlx::Pool;
|
||||
use sqlx::Postgres;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum FishRarity {
|
||||
Common,
|
||||
Uncommon,
|
||||
Rare,
|
||||
SuperRare,
|
||||
}
|
||||
|
||||
pub struct FishType {
|
||||
pub name: &'static str,
|
||||
pub color_hex: &'static str,
|
||||
pub price: u32,
|
||||
pub rarity: FishRarity,
|
||||
}
|
||||
|
||||
pub const FISH_TYPES: [FishType; 20] = [
|
||||
FishType {
|
||||
name: "Salmon",
|
||||
color_hex: "#FA8072",
|
||||
price: 50,
|
||||
rarity: FishRarity::Common,
|
||||
},
|
||||
FishType {
|
||||
name: "Trout",
|
||||
color_hex: "#A2B5CD",
|
||||
price: 40,
|
||||
rarity: FishRarity::Common,
|
||||
},
|
||||
FishType {
|
||||
name: "Bass",
|
||||
color_hex: "#6B8E23",
|
||||
price: 45,
|
||||
rarity: FishRarity::Common,
|
||||
},
|
||||
FishType {
|
||||
name: "Catfish",
|
||||
color_hex: "#B0C4DE",
|
||||
price: 60,
|
||||
rarity: FishRarity::Uncommon,
|
||||
},
|
||||
FishType {
|
||||
name: "Carp",
|
||||
color_hex: "#C2B280",
|
||||
price: 35,
|
||||
rarity: FishRarity::Common,
|
||||
},
|
||||
FishType {
|
||||
name: "Pike",
|
||||
color_hex: "#556B2F",
|
||||
price: 70,
|
||||
rarity: FishRarity::Uncommon,
|
||||
},
|
||||
FishType {
|
||||
name: "Perch",
|
||||
color_hex: "#FFD700",
|
||||
price: 30,
|
||||
rarity: FishRarity::Common,
|
||||
},
|
||||
FishType {
|
||||
name: "Sturgeon",
|
||||
color_hex: "#708090",
|
||||
price: 200,
|
||||
rarity: FishRarity::Rare,
|
||||
},
|
||||
FishType {
|
||||
name: "Bluegill",
|
||||
color_hex: "#4682B4",
|
||||
price: 25,
|
||||
rarity: FishRarity::Common,
|
||||
},
|
||||
FishType {
|
||||
name: "Crappie",
|
||||
color_hex: "#D3D3D3",
|
||||
price: 20,
|
||||
rarity: FishRarity::Common,
|
||||
},
|
||||
FishType {
|
||||
name: "Swordfish",
|
||||
color_hex: "#191970",
|
||||
price: 500,
|
||||
rarity: FishRarity::SuperRare,
|
||||
},
|
||||
FishType {
|
||||
name: "Marlin",
|
||||
color_hex: "#4169E1",
|
||||
price: 400,
|
||||
rarity: FishRarity::Rare,
|
||||
},
|
||||
FishType {
|
||||
name: "Tuna",
|
||||
color_hex: "#4682B4",
|
||||
price: 350,
|
||||
rarity: FishRarity::Rare,
|
||||
},
|
||||
FishType {
|
||||
name: "Mahi Mahi",
|
||||
color_hex: "#00CED1",
|
||||
price: 150,
|
||||
rarity: FishRarity::Uncommon,
|
||||
},
|
||||
FishType {
|
||||
name: "Halibut",
|
||||
color_hex: "#F0E68C",
|
||||
price: 120,
|
||||
rarity: FishRarity::Uncommon,
|
||||
},
|
||||
FishType {
|
||||
name: "Eel",
|
||||
color_hex: "#2F4F4F",
|
||||
price: 80,
|
||||
rarity: FishRarity::Uncommon,
|
||||
},
|
||||
FishType {
|
||||
name: "Opah",
|
||||
color_hex: "#FF4500",
|
||||
price: 600,
|
||||
rarity: FishRarity::SuperRare,
|
||||
},
|
||||
FishType {
|
||||
name: "Angelfish",
|
||||
color_hex: "#FFB6C1",
|
||||
price: 250,
|
||||
rarity: FishRarity::Rare,
|
||||
},
|
||||
FishType {
|
||||
name: "Clownfish",
|
||||
color_hex: "#FFA500",
|
||||
price: 180,
|
||||
rarity: FishRarity::Uncommon,
|
||||
},
|
||||
FishType {
|
||||
name: "Snapper",
|
||||
color_hex: "#FF6347",
|
||||
price: 90,
|
||||
rarity: FishRarity::Uncommon,
|
||||
},
|
||||
];
|
||||
|
||||
pub struct FishCommand {
|
||||
pool: Arc<Pool<Postgres>>,
|
||||
}
|
||||
|
||||
impl FishCommand {
|
||||
pub fn new(pool: Arc<Pool<Postgres>>) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
}
|
||||
#[async_trait]
|
||||
impl Command for FishCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"fish"
|
||||
}
|
||||
fn category(&self) -> &'static str {
|
||||
"eco"
|
||||
}
|
||||
fn description(&self) -> &'static str {
|
||||
"Do some fishing. Try to catch one of many fish types! Rarities and prices included."
|
||||
}
|
||||
fn aliases(&self) -> &[&'static str] {
|
||||
&[]
|
||||
}
|
||||
fn argument_spec(&self) -> &'static [ArgumentSpec] {
|
||||
&[ArgumentSpec {
|
||||
name: "action",
|
||||
arg_type: ArgumentType::String,
|
||||
required: false,
|
||||
default: None,
|
||||
}]
|
||||
}
|
||||
async fn constructed(&mut self, _: Client) {}
|
||||
async fn event(&mut self, _: Client, _: ClientEvent) {}
|
||||
async fn execute(&mut self, client: Client, player: Player, args: ParsedArguments) {
|
||||
let mut user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE _id = $1")
|
||||
.bind(&player._id)
|
||||
.fetch_optional(self.pool.as_ref())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let action = match args.get("action") {
|
||||
Some(ParsedArgument::String(s)) => s.as_str(),
|
||||
_ => "",
|
||||
};
|
||||
|
||||
if action == "sell" {
|
||||
let mut total_value: i32 = 0;
|
||||
let mut fish_count: i32 = 0;
|
||||
let mut sold_fish: Vec<String> = Vec::new();
|
||||
|
||||
let mut new_items: Vec<String> = Vec::new();
|
||||
for item in user.items.iter() {
|
||||
if item.starts_with("fish-") {
|
||||
let parts: Vec<&str> = item.split('-').collect();
|
||||
if parts.len() >= 3 {
|
||||
let fish_name = parts[1].replace('_', " ");
|
||||
let weight_str = parts[2];
|
||||
let weight: f64 = weight_str.parse().unwrap_or(0.0);
|
||||
|
||||
if let Some(fish_type) = FISH_TYPES
|
||||
.iter()
|
||||
.find(|f| f.name.to_lowercase() == fish_name.to_lowercase())
|
||||
{
|
||||
let value = calculate_fish_value(weight, fish_type.price);
|
||||
total_value += value;
|
||||
fish_count += 1;
|
||||
sold_fish.push(format!(
|
||||
"{} ({:.2} kg, {} coins)",
|
||||
fish_type.name, weight, value
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_items.push(item.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if fish_count == 0 {
|
||||
client
|
||||
.message("You have no fish to sell.".to_string())
|
||||
.await;
|
||||
} else {
|
||||
user.items = new_items.into();
|
||||
user.balance += total_value;
|
||||
let _ = sqlx::query("UPDATE users SET items = $1, balance = $2 WHERE _id = $3")
|
||||
.bind(&user.items)
|
||||
.bind(user.balance)
|
||||
.bind(&user._id)
|
||||
.execute(self.pool.as_ref())
|
||||
.await;
|
||||
|
||||
client
|
||||
.message(format!(
|
||||
"You sold {} fish for a total of {} coins! {}",
|
||||
fish_count,
|
||||
total_value,
|
||||
sold_fish.join(", ")
|
||||
))
|
||||
.await;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let cooldown_query_opt = sqlx::query_scalar::<_, Option<chrono::DateTime<Utc>>>(
|
||||
"SELECT untill FROM cooldowns WHERE _id = $1 AND type = $2",
|
||||
)
|
||||
.bind(&user._id)
|
||||
.bind("fish")
|
||||
.fetch_one(self.pool.as_ref())
|
||||
.await;
|
||||
let now = Utc::now();
|
||||
let cooldown_exists = cooldown_query_opt.is_ok();
|
||||
if cooldown_exists
|
||||
&& let Some(until) = cooldown_query_opt.unwrap()
|
||||
&& until > now
|
||||
{
|
||||
let secs_left = (until - now).num_seconds();
|
||||
client
|
||||
.message(format!(
|
||||
"⏳ You are tired from fishing! Please wait {} seconds before fishing again.",
|
||||
secs_left
|
||||
))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let mut rod_bonus = 0.0;
|
||||
let mut bait_bonus = 0.0;
|
||||
let mut bait_used = None;
|
||||
|
||||
fn count_item(items: &[String], id: &str) -> usize {
|
||||
items.iter().filter(|i| i.starts_with(id)).count()
|
||||
}
|
||||
|
||||
if user.items.iter().any(|i| i == "carbon_rod") {
|
||||
rod_bonus = 0.15;
|
||||
} else if user.items.iter().any(|i| i == "fiberglass_rod") {
|
||||
rod_bonus = 0.07;
|
||||
} else if user.items.iter().any(|i| i == "wooden_rod") {
|
||||
rod_bonus = 0.0;
|
||||
}
|
||||
|
||||
if count_item(&user.items, "large_bait") > 0 {
|
||||
bait_bonus = 0.10;
|
||||
bait_used = Some("large_bait");
|
||||
} else if count_item(&user.items, "small_bait") > 0 {
|
||||
bait_bonus = 0.04;
|
||||
bait_used = Some("small_bait");
|
||||
}
|
||||
|
||||
if let Some(bait_id) = bait_used
|
||||
&& let Some(pos) = user.items.iter().position(|i| i.starts_with(bait_id))
|
||||
{
|
||||
user.items.remove(pos);
|
||||
client
|
||||
.message(format!("You used one {}.", bait_id.replace('_', " ")))
|
||||
.await;
|
||||
}
|
||||
|
||||
let common_fish = FISH_TYPES
|
||||
.iter()
|
||||
.filter(|f| matches!(f.rarity, FishRarity::Common))
|
||||
.collect::<Vec<_>>();
|
||||
let uncommon_fish = FISH_TYPES
|
||||
.iter()
|
||||
.filter(|f| matches!(f.rarity, FishRarity::Uncommon))
|
||||
.collect::<Vec<_>>();
|
||||
let rare_fish = FISH_TYPES
|
||||
.iter()
|
||||
.filter(|f| matches!(f.rarity, FishRarity::Rare))
|
||||
.collect::<Vec<_>>();
|
||||
let super_rare_fish = FISH_TYPES
|
||||
.iter()
|
||||
.filter(|f| matches!(f.rarity, FishRarity::SuperRare))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut common_chance = 60.0;
|
||||
let mut uncommon_chance = 25.0;
|
||||
let mut rare_chance = 13.0;
|
||||
let mut super_rare_chance = 2.0;
|
||||
|
||||
let rare_bonus = rod_bonus + bait_bonus;
|
||||
rare_chance += rare_bonus * 100.0 * 0.5;
|
||||
super_rare_chance += rare_bonus * 100.0 * 0.5;
|
||||
let total = common_chance + uncommon_chance + rare_chance + super_rare_chance;
|
||||
|
||||
common_chance *= 100.0 / total;
|
||||
uncommon_chance *= 100.0 / total;
|
||||
rare_chance *= 100.0 / total;
|
||||
|
||||
let roll = rand::rng().random_range(0.0..100.0);
|
||||
let fish = if roll < common_chance {
|
||||
common_fish.choose(&mut rand::rng()).unwrap()
|
||||
} else if roll < common_chance + uncommon_chance {
|
||||
uncommon_fish.choose(&mut rand::rng()).unwrap()
|
||||
} else if roll < common_chance + uncommon_chance + rare_chance {
|
||||
rare_fish.choose(&mut rand::rng()).unwrap()
|
||||
} else {
|
||||
super_rare_fish.choose(&mut rand::rng()).unwrap()
|
||||
};
|
||||
|
||||
let timeout_secs = rand::rng().random_range(24..=48);
|
||||
|
||||
let cooldown_until = now + ChronoDuration::seconds(timeout_secs);
|
||||
|
||||
if cooldown_exists {
|
||||
let _ = sqlx::query("UPDATE cooldowns SET untill = $1 WHERE _id = $2 AND type = $3")
|
||||
.bind(cooldown_until)
|
||||
.bind(&user._id)
|
||||
.bind("fish")
|
||||
.execute(self.pool.as_ref())
|
||||
.await;
|
||||
} else {
|
||||
let _ = sqlx::query("INSERT INTO cooldowns (_id, type, untill) VALUES ($1, $2, $3)")
|
||||
.bind(&user._id)
|
||||
.bind("fish")
|
||||
.bind(cooldown_until)
|
||||
.execute(self.pool.as_ref())
|
||||
.await;
|
||||
}
|
||||
|
||||
let base_weight = rand::rng().random_range(0.1..=50.0);
|
||||
let weight = base_weight * (1.0 + rod_bonus + bait_bonus);
|
||||
|
||||
let fish_id = format!(
|
||||
"fish-{}-{:.2}",
|
||||
fish.name.to_lowercase().replace(' ', "_"),
|
||||
weight
|
||||
);
|
||||
|
||||
user.items.push(fish_id.clone());
|
||||
let _ = sqlx::query("UPDATE users SET items = $1 WHERE _id = $2")
|
||||
.bind(&user.items)
|
||||
.bind(&user._id)
|
||||
.execute(self.pool.as_ref())
|
||||
.await;
|
||||
|
||||
let value = calculate_fish_value(weight, fish.price);
|
||||
|
||||
client
|
||||
.message(format!(
|
||||
"🎣 You caught a {}! Color: {} Weight: {:.2} kg",
|
||||
fish.name, fish.color_hex, weight
|
||||
))
|
||||
.await;
|
||||
client
|
||||
.message(format!("Rarity: {:?} Value: {} coins", fish.rarity, value))
|
||||
.await;
|
||||
let client_clone = client.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(timeout_secs as u64)).await;
|
||||
client_clone
|
||||
.message(format!("🎣 @{} can fish again now!", player._id))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue