use serde_cbor::{from_slice, to_vec}; use uuid::Uuid; use postgres::transaction::Transaction; use failure::Error; // drops use rand::prelude::*; use rand::{thread_rng}; use rand::distributions::{LogNormal,WeightedIndex}; use account::Account; use rpc::{ItemUseParams}; use cryp::{Stat, cryp_get, cryp_write}; use game::{GameMode}; use spec::{Spec, SpecType}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum ItemAction { RerollPhysDamage, RerollSpellDamage, RerollSpeed, RerollStamina, RerollArmour, RerollSpellShield, RerollEvasion, SpecPhysDmg5, SpecSpellDmg5, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct Item { // mods: Vec, id: Uuid, account: Uuid, action: ItemAction, } impl Item { pub fn new(action: ItemAction, account_id: Uuid) -> Item { let id = Uuid::new_v4(); return Item { id, account: account_id, action, }; } fn apply(&mut self, tx: &mut Transaction, target: Uuid) -> Result<(), Error> { match self.action { ItemAction::RerollStamina => reroll(self, tx, target, Stat::Stamina), ItemAction::RerollPhysDamage => reroll(self, tx, target, Stat::PhysicalDamage), ItemAction::RerollSpellDamage => reroll(self, tx, target, Stat::SpellDamage), ItemAction::RerollSpeed => reroll(self, tx, target, Stat::Speed), ItemAction::RerollArmour => reroll(self, tx, target, Stat::Armour), ItemAction::RerollSpellShield => reroll(self, tx, target, Stat::SpellShield), ItemAction::RerollEvasion => reroll(self, tx, target, Stat::Evasion), ItemAction::SpecPhysDmg5 => spec_apply(self, tx, target, SpecType::PhysDamage5), ItemAction::SpecSpellDmg5 => spec_apply(self, tx, target, SpecType::SpellDamage5), } } } fn spec_apply(item: &mut Item, tx: &mut Transaction, target: Uuid, spec_type: SpecType) -> Result<(), Error> { let mut cryp = cryp_get(tx, target, item.account)?; let spec = Spec::new(spec_type); cryp.spec_apply(spec)?; cryp_write(cryp, tx)?; return Ok(()); } fn reroll(item: &mut Item, tx: &mut Transaction, target: Uuid, stat: Stat) -> Result<(), Error> { let mut cryp = cryp_get(tx, target, item.account)?; cryp.roll_stat(stat); cryp_write(cryp, tx)?; return Ok(()); } fn mode_drops(mode: GameMode) -> Vec<(ItemAction, usize)> { match mode { GameMode::Normal => vec![ (ItemAction::RerollStamina, 1), (ItemAction::RerollPhysDamage, 1), (ItemAction::RerollSpellDamage, 1), ], GameMode::Pvp => vec![ (ItemAction::RerollSpeed, 1), ], GameMode::Zone3v2Attack | GameMode::Zone2v2Caster | GameMode::Zone3v3MeleeMiniboss => vec![ (ItemAction::RerollEvasion, 1), (ItemAction::RerollArmour, 1), (ItemAction::RerollSpellShield, 1), ], GameMode::Zone3v3HealerBoss => vec![ (ItemAction::RerollSpeed, 1), ], // _ => vec![ // (ItemAction::RerollStamina, 1), // (ItemAction::RerollPhysDamage, 1), // (ItemAction::RerollSpellDamage, 1), // (ItemAction::RerollSpeed, 1), // (ItemAction::RerollArmour, 1), // (ItemAction::RerollSpellShield, 1), // (ItemAction::RerollEvasion, 1), // ], } } pub fn item_drop(tx: &mut Transaction, account_id: Uuid, mode: GameMode) -> Result<(), Error> { let mut rng = thread_rng(); let log_normal = LogNormal::new(1.0, 1.0); let num_drops = log_normal.sample(&mut rng).floor() as u16; let actions = mode_drops(mode); println!("{:?} drops", num_drops); for _i in 0..num_drops { let dist = WeightedIndex::new(actions.iter().map(|item| item.1)).unwrap(); let kind = actions[dist.sample(&mut rng)].0; let item = Item::new(kind, account_id); println!("{:?} dropped {:?}", account_id, item); item_create(item, tx, account_id)?; } Ok(()) } pub fn item_create(item: Item, tx: &mut Transaction, account_id: Uuid) -> Result { let item_bytes = to_vec(&item)?; let query = " INSERT INTO items (id, account, data) VALUES ($1, $2, $3) RETURNING id, account, data; "; let result = tx .query(query, &[&item.id, &account_id, &item_bytes])?; result.iter().next().expect("no row returned"); return Ok(item); } pub fn item_use(params: ItemUseParams, tx: &mut Transaction, account: &Account) -> Result<(), Error> { let query = " SELECT data FROM items WHERE id = $1 AND account = $2 FOR UPDATE; "; let result = tx .query(query, &[¶ms.item, &account.id])?; let returned = result.iter().next().expect("no row returned"); let item_bytes: Vec = returned.get(0); let mut item = from_slice::(&item_bytes)?; item.apply(tx, params.target)?; item_delete(tx, params.item)?; return Ok(()); } pub fn item_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { let query = " DELETE FROM items WHERE id = $1; "; let result = tx .execute(query, &[&id])?; if result != 1 { return Err(format_err!("unable to delete item {:?}", id)); } // println!("item deleted {:?}", id); return Ok(()); } pub fn items_list(tx: &mut Transaction, account: &Account) -> Result, Error> { let query = " SELECT data, id FROM items WHERE account = $1; "; let result = tx .query(query, &[&account.id])?; let mut items = vec![]; for row in result.into_iter() { let item_bytes: Vec = row.get(0); let id = row.get(1); match from_slice::(&item_bytes) { Ok(i) => items.push(i), Err(_e) => { item_delete(tx, id)?; } }; } return Ok(items); } // # max damage potion // name // "MapMonstersCurseEffectOnSelfFinal3": { // "adds_tags": [], // "domain": "area", // "generation_type": "prefix", // "generation_weights": [], // "grants_buff": {}, // "grants_effect": {}, // "group": "MapHexproof", // "is_essence_only": false, // "name": "Hexwarded", // "required_level": 1, // "spawn_weights": [ // { // "tag": "top_tier_map", // "weight": 0 // }, // { // "tag": "default", // "weight": 0 // } // ], // "stats": [ // { // "id": "map_item_drop_quantity_+%", // "max": 15, // "min": 15 // }, // { // "id": "map_item_drop_rarity_+%", // "max": 8, // "min": 8 // }, // { // "id": "map_monsters_curse_effect_on_self_+%_final", // "max": -60, // "min": -60 // } // ] // },