Merge branch 'aweinstock/mining-skill-tree' into 'master'

Mining skill tree.

See merge request veloren/veloren!2406
This commit is contained in:
Marcel 2021-06-14 14:19:06 +00:00
commit 4cafdb3bfd
36 changed files with 357 additions and 51 deletions

View File

@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Added a skill tree for mining, which gains xp from mining ores and gems.
### Changed ### Changed

View File

@ -76,4 +76,7 @@
Climb(Cost): Some(2), Climb(Cost): Some(2),
Climb(Speed): Some(2), Climb(Speed): Some(2),
Swim(Speed): Some(2), Swim(Speed): Some(2),
Pick(Speed): Some(3),
Pick(OreGain): Some(3),
Pick(GemGain): Some(3),
}) })

View File

@ -109,4 +109,9 @@
Sceptre(ARange), Sceptre(ARange),
Sceptre(ACost), Sceptre(ACost),
], ],
Weapon(Pick): [
Pick(Speed),
Pick(OreGain),
Pick(GemGain),
],
}) })

View File

@ -127,6 +127,10 @@
(Sceptre(ADuration), 2), (Sceptre(ADuration), 2),
(Sceptre(ARange), 2), (Sceptre(ARange), 2),
(Sceptre(ACost), 2), (Sceptre(ACost), 2),
// Mining
(Pick(Speed), 3),
(Pick(OreGain), 3),
(Pick(GemGain), 3),
], ],
// Just copypasta from max with random reductions // Just copypasta from max with random reductions
"middle": [ "middle": [

View File

@ -0,0 +1,19 @@
ResourceExperienceManifest({
"common.items.mineral.gem.amethyst": 4,
"common.items.mineral.gem.sapphire": 8,
"common.items.mineral.gem.topaz": 4,
"common.items.mineral.gem.diamond": 25,
"common.items.mineral.gem.emerald": 10,
"common.items.mineral.gem.ruby": 12,
"common.items.mineral.ore.coal": 6,
"common.items.mineral.ore.gold": 25,
"common.items.mineral.ore.iron": 4,
"common.items.mineral.ore.silver": 22,
"common.items.mineral.ore.velorite": 8,
"common.items.mineral.ore.veloritefrag": 4,
"common.items.mineral.ore.bloodstone": 20,
"common.items.mineral.ore.cobalt": 15,
"common.items.mineral.ore.copper": 3,
"common.items.mineral.ore.tin": 3,
})

BIN
assets/voxygen/element/skills/pickaxe.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/skills/pickaxe_gemgain.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/skills/pickaxe_oregain.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/skills/pickaxe_speed.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/weapons/axe.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/weapons/bow.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/weapons/daggers.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/weapons/hammer.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/weapons/mining.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/weapons/pickaxe.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/weapons/staff.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/weapons/sword.png (Stored with Git LFS)

Binary file not shown.

View File

@ -78,6 +78,7 @@ Is the client up to date?"#,
"common.tool.debug": "Debug", "common.tool.debug": "Debug",
"common.tool.faming": "Farming Tool", "common.tool.faming": "Farming Tool",
"common.tool.pick": "Pickaxe", "common.tool.pick": "Pickaxe",
"common.tool.mining": "Mining",
"common.kind.modular_component": "Modular Component", "common.kind.modular_component": "Modular Component",
"common.kind.glider": "Glider", "common.kind.glider": "Glider",
"common.kind.consumable": "Consumable", "common.kind.consumable": "Consumable",

View File

@ -238,6 +238,16 @@
"hud.skill.axe_leap_cost": "Decreases cost of leap by 25%{SP}", "hud.skill.axe_leap_cost": "Decreases cost of leap by 25%{SP}",
"hud.skill.axe_leap_distance_title": "Leap Distance", "hud.skill.axe_leap_distance_title": "Leap Distance",
"hud.skill.axe_leap_distance": "Increases distance of leap by 20%{SP}", "hud.skill.axe_leap_distance": "Increases distance of leap by 20%{SP}",
// Mining
"hud.skill.mining_title": "Mining",
"hud.skill.pick_strike_title": "Pickaxe Strike",
"hud.skill.pick_strike": "Hit rocks with the pickaxe to gain ore and gems and experience",
"hud.skill.pick_strike_speed_title": "Pickaxe Strike Speed",
"hud.skill.pick_strike_speed": "Mine rocks faster{SP}",
"hud.skill.pick_strike_oregain_title": "Pickaxe Strike Ore Yield",
"hud.skill.pick_strike_oregain": "Chance to gain extra ore (5% per level){SP}",
"hud.skill.pick_strike_gemgain_title": "Pickaxe Strike Gem Yield",
"hud.skill.pick_strike_gemgain": "Chance to gain extra gems (5% per level){SP}",
}, },

View File

@ -65,6 +65,10 @@
"voxel.weapon.sceptre.wood-nature", "voxel.weapon.sceptre.wood-nature",
(-1.0, 0.0, 0.0), (-90.0, 55.0, 0.0), 1.0, (-1.0, 0.0, 0.0), (-90.0, 55.0, 0.0), 1.0,
), ),
Tool("example_pick"): VoxTrans(
"voxel.weapon.tool.pickaxe_green-0",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.0,
),
Tool("example_dagger"): VoxTrans( Tool("example_dagger"): VoxTrans(
"voxel.weapon.dagger.dagger_basic-0", "voxel.weapon.dagger.dagger_basic-0",
(0.0, 0.0, 0.0), (90.0, 90.0, 0.0), 1.0, (0.0, 0.0, 0.0), (90.0, 90.0, 0.0), 1.0,

View File

@ -127,7 +127,7 @@ lazy_static! {
.iter() .iter()
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect(); .collect();
static ref SKILL_TREES: Vec<String> = vec!["general", "sword", "axe", "hammer", "bow", "staff", "sceptre"] static ref SKILL_TREES: Vec<String> = vec!["general", "sword", "axe", "hammer", "bow", "staff", "sceptre", "pick"]
.iter() .iter()
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect(); .collect();

View File

@ -1174,6 +1174,23 @@ impl CharacterAbility {
_ => {}, _ => {},
} }
}, },
Some(ToolKind::Pick) => {
use skills::MiningSkill::*;
if let BasicMelee {
ref mut buildup_duration,
ref mut swing_duration,
ref mut recover_duration,
..
} = self
{
if let Ok(Some(level)) = skillset.skill_level(Pick(Speed)) {
let speed = 1.1_f32.powi(level.into());
*buildup_duration /= speed;
*swing_duration /= speed;
*recover_duration /= speed;
}
}
},
None => { None => {
if let CharacterAbility::Roll { if let CharacterAbility::Roll {
ref mut energy_cost, ref mut energy_cost,

View File

@ -50,6 +50,21 @@ impl ToolKind {
ToolKind::Empty => "empty", ToolKind::Empty => "empty",
} }
} }
pub fn gains_combat_xp(&self) -> bool {
matches!(
self,
ToolKind::Sword
| ToolKind::Axe
| ToolKind::Hammer
| ToolKind::Bow
| ToolKind::Dagger
| ToolKind::Staff
| ToolKind::Spear
| ToolKind::Sceptre
| ToolKind::Shield
)
}
} }
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]

View File

@ -107,6 +107,7 @@ pub enum Skill {
Roll(RollSkill), Roll(RollSkill),
Climb(ClimbSkill), Climb(ClimbSkill),
Swim(SwimSkill), Swim(SwimSkill),
Pick(MiningSkill),
} }
pub enum SkillError { pub enum SkillError {
@ -263,6 +264,13 @@ pub enum SwimSkill {
Speed, Speed,
} }
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum MiningSkill {
Speed,
OreGain,
GemGain,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum SkillGroupKind { pub enum SkillGroupKind {
General, General,
@ -344,7 +352,10 @@ impl Default for SkillSet {
/// player /// player
fn default() -> Self { fn default() -> Self {
Self { Self {
skill_groups: vec![SkillGroup::new(SkillGroupKind::General)], skill_groups: vec![
SkillGroup::new(SkillGroupKind::General),
SkillGroup::new(SkillGroupKind::Weapon(ToolKind::Pick)),
],
skills: HashMap::new(), skills: HashMap::new(),
modify_health: false, modify_health: false,
modify_energy: false, modify_energy: false,
@ -365,7 +376,7 @@ impl SkillSet {
/// let mut skillset = SkillSet::default(); /// let mut skillset = SkillSet::default();
/// skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Sword)); /// skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Sword));
/// ///
/// assert_eq!(skillset.skill_groups.len(), 2); /// assert_eq!(skillset.skill_groups.len(), 3);
/// ``` /// ```
pub fn unlock_skill_group(&mut self, skill_group_kind: SkillGroupKind) { pub fn unlock_skill_group(&mut self, skill_group_kind: SkillGroupKind) {
if !self.contains_skill_group(skill_group_kind) { if !self.contains_skill_group(skill_group_kind) {
@ -668,13 +679,13 @@ mod tests {
skillset.add_skill_points(SkillGroupKind::Weapon(ToolKind::Axe), 1); skillset.add_skill_points(SkillGroupKind::Weapon(ToolKind::Axe), 1);
skillset.unlock_skill(Skill::Axe(AxeSkill::UnlockLeap)); skillset.unlock_skill(Skill::Axe(AxeSkill::UnlockLeap));
assert_eq!(skillset.skill_groups[1].available_sp, 0); assert_eq!(skillset.skill_groups[2].available_sp, 0);
assert_eq!(skillset.skills.len(), 1); assert_eq!(skillset.skills.len(), 1);
assert!(skillset.has_skill(Skill::Axe(AxeSkill::UnlockLeap))); assert!(skillset.has_skill(Skill::Axe(AxeSkill::UnlockLeap)));
skillset.refund_skill(Skill::Axe(AxeSkill::UnlockLeap)); skillset.refund_skill(Skill::Axe(AxeSkill::UnlockLeap));
assert_eq!(skillset.skill_groups[1].available_sp, 1); assert_eq!(skillset.skill_groups[2].available_sp, 1);
assert_eq!(skillset.skills.get(&Skill::Axe(AxeSkill::UnlockLeap)), None); assert_eq!(skillset.skills.get(&Skill::Axe(AxeSkill::UnlockLeap)), None);
} }
@ -683,9 +694,9 @@ mod tests {
let mut skillset = SkillSet::default(); let mut skillset = SkillSet::default();
skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)); skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe));
assert_eq!(skillset.skill_groups.len(), 2); assert_eq!(skillset.skill_groups.len(), 3);
assert_eq!( assert_eq!(
skillset.skill_groups[1], skillset.skill_groups[2],
SkillGroup::new(SkillGroupKind::Weapon(ToolKind::Axe)) SkillGroup::new(SkillGroupKind::Weapon(ToolKind::Axe))
); );
} }
@ -697,13 +708,13 @@ mod tests {
skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)); skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe));
skillset.add_skill_points(SkillGroupKind::Weapon(ToolKind::Axe), 1); skillset.add_skill_points(SkillGroupKind::Weapon(ToolKind::Axe), 1);
assert_eq!(skillset.skill_groups[1].available_sp, 1); assert_eq!(skillset.skill_groups[2].available_sp, 1);
assert_eq!(skillset.skills.len(), 0); assert_eq!(skillset.skills.len(), 0);
// Try unlocking a skill with enough skill points // Try unlocking a skill with enough skill points
skillset.unlock_skill(Skill::Axe(AxeSkill::UnlockLeap)); skillset.unlock_skill(Skill::Axe(AxeSkill::UnlockLeap));
assert_eq!(skillset.skill_groups[1].available_sp, 0); assert_eq!(skillset.skill_groups[2].available_sp, 0);
assert_eq!(skillset.skills.len(), 1); assert_eq!(skillset.skills.len(), 1);
assert!(skillset.has_skill(Skill::Axe(AxeSkill::UnlockLeap))); assert!(skillset.has_skill(Skill::Axe(AxeSkill::UnlockLeap)));
@ -720,6 +731,6 @@ mod tests {
skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)); skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe));
skillset.add_skill_points(SkillGroupKind::Weapon(ToolKind::Axe), 1); skillset.add_skill_points(SkillGroupKind::Weapon(ToolKind::Axe), 1);
assert_eq!(skillset.skill_groups[1].available_sp, 1); assert_eq!(skillset.skill_groups[2].available_sp, 1);
} }
} }

View File

@ -164,6 +164,7 @@ pub enum ServerEvent {
}, },
// Attempt to mine a block, turning it into an item // Attempt to mine a block, turning it into an item
MineBlock { MineBlock {
entity: EcsEntity,
pos: Vec3<i32>, pos: Vec3<i32>,
tool: Option<comp::tool::ToolKind>, tool: Option<comp::tool::ToolKind>,
}, },

View File

@ -1,5 +1,6 @@
use crate::{comp, uid::Uid}; use crate::{comp, uid::Uid};
use comp::{beam, item::Reagent, poise::PoiseState}; use comp::{beam, item::Reagent, poise::PoiseState, skills::SkillGroupKind};
use hashbrown::HashSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vek::*; use vek::*;
@ -36,6 +37,7 @@ pub enum Outcome {
ExpChange { ExpChange {
uid: Uid, uid: Uid,
exp: i32, exp: i32,
xp_pools: HashSet<SkillGroupKind>,
}, },
SkillPointGain { SkillPointGain {
uid: Uid, uid: Uid,

View File

@ -86,6 +86,7 @@ impl<'a> System<'a> for Sys {
< (rad + scale * melee_attack.range).powi(2) < (rad + scale * melee_attack.range).powi(2)
{ {
server_emitter.emit(ServerEvent::MineBlock { server_emitter.emit(ServerEvent::MineBlock {
entity: attacker,
pos: block_pos, pos: block_pos,
tool, tool,
}); });

View File

@ -2610,6 +2610,7 @@ fn parse_skill_tree(skill_tree: &str) -> CmdResult<comp::skills::SkillGroupKind>
"bow" => Ok(SkillGroupKind::Weapon(ToolKind::Bow)), "bow" => Ok(SkillGroupKind::Weapon(ToolKind::Bow)),
"staff" => Ok(SkillGroupKind::Weapon(ToolKind::Staff)), "staff" => Ok(SkillGroupKind::Weapon(ToolKind::Staff)),
"sceptre" => Ok(SkillGroupKind::Weapon(ToolKind::Sceptre)), "sceptre" => Ok(SkillGroupKind::Weapon(ToolKind::Sceptre)),
"mining" => Ok(SkillGroupKind::Weapon(ToolKind::Pick)),
_ => Err(format!("{} is not a skill group!", skill_tree)), _ => Err(format!("{} is not a skill group!", skill_tree)),
} }
} }

View File

@ -992,12 +992,11 @@ fn handle_exp_gain(
// Closure to add xp pool corresponding to weapon type equipped in a particular // Closure to add xp pool corresponding to weapon type equipped in a particular
// EquipSlot // EquipSlot
let mut add_tool_from_slot = |equip_slot| { let mut add_tool_from_slot = |equip_slot| {
let tool_kind = inventory.equipped(equip_slot).and_then(|i| { let tool_kind = inventory
if let ItemKind::Tool(tool) = &i.kind() { .equipped(equip_slot)
Some(tool.kind) .and_then(|i| match &i.kind() {
} else { ItemKind::Tool(tool) if tool.kind.gains_combat_xp() => Some(tool.kind),
None _ => None,
}
}); });
if let Some(weapon) = tool_kind { if let Some(weapon) = tool_kind {
// Only adds to xp pools if entity has that skill group available // Only adds to xp pools if entity has that skill group available
@ -1012,12 +1011,13 @@ fn handle_exp_gain(
add_tool_from_slot(EquipSlot::InactiveMainhand); add_tool_from_slot(EquipSlot::InactiveMainhand);
add_tool_from_slot(EquipSlot::InactiveOffhand); add_tool_from_slot(EquipSlot::InactiveOffhand);
let num_pools = xp_pools.len() as f32; let num_pools = xp_pools.len() as f32;
for pool in xp_pools { for pool in xp_pools.iter() {
skill_set.change_experience(pool, (exp_reward / num_pools).ceil() as i32); skill_set.change_experience(*pool, (exp_reward / num_pools).ceil() as i32);
} }
outcomes.push(Outcome::ExpChange { outcomes.push(Outcome::ExpChange {
uid: *uid, uid: *uid,
exp: exp_reward as i32, exp: exp_reward as i32,
xp_pools,
}); });
} }

View File

@ -3,6 +3,7 @@ use tracing::error;
use vek::*; use vek::*;
use common::{ use common::{
assets,
comp::{ comp::{
self, self,
agent::{AgentEvent, Sound, MAX_LISTEN_DIST}, agent::{AgentEvent, Sound, MAX_LISTEN_DIST},
@ -11,7 +12,7 @@ use common::{
item, item,
slot::Slot, slot::Slot,
tool::ToolKind, tool::ToolKind,
Inventory, Pos, Inventory, Pos, SkillGroupKind,
}, },
consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME}, consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME},
outcome::Outcome, outcome::Outcome,
@ -27,6 +28,11 @@ use crate::{
Server, Server,
}; };
use hashbrown::{HashMap, HashSet};
use lazy_static::lazy_static;
use serde::Deserialize;
use std::iter::FromIterator;
pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) { pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) {
let ecs = server.state_mut().ecs(); let ecs = server.state_mut().ecs();
@ -281,13 +287,81 @@ fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<&
} }
} }
pub fn handle_mine_block(server: &mut Server, pos: Vec3<i32>, tool: Option<ToolKind>) { #[derive(Deserialize)]
struct ResourceExperienceManifest(HashMap<String, i32>);
impl assets::Asset for ResourceExperienceManifest {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
lazy_static! {
static ref RESOURCE_EXPERIENCE_MANIFEST: assets::AssetHandle<ResourceExperienceManifest> =
assets::AssetExt::load_expect("server.manifests.resource_experience_manifest");
}
pub fn handle_mine_block(
server: &mut Server,
entity: EcsEntity,
pos: Vec3<i32>,
tool: Option<ToolKind>,
) {
let state = server.state_mut(); let state = server.state_mut();
if state.can_set_block(pos) { if state.can_set_block(pos) {
let block = state.terrain().get(pos).ok().copied(); let block = state.terrain().get(pos).ok().copied();
if let Some(block) = block.filter(|b| b.mine_tool().map_or(false, |t| Some(t) == tool)) { if let Some(block) = block.filter(|b| b.mine_tool().map_or(false, |t| Some(t) == tool)) {
// Drop item if one is recoverable from the block // Drop item if one is recoverable from the block
if let Some(item) = comp::Item::try_reclaim_from_block(block) { if let Some(mut item) = comp::Item::try_reclaim_from_block(block) {
if let Some(mut skillset) = state
.ecs()
.write_storage::<comp::SkillSet>()
.get_mut(entity)
{
if let (Some(tool), Some(uid), Some(exp_reward)) = (
tool,
state.ecs().uid_from_entity(entity),
RESOURCE_EXPERIENCE_MANIFEST
.read()
.0
.get(item.item_definition_id()),
) {
skillset.change_experience(SkillGroupKind::Weapon(tool), *exp_reward);
state
.ecs()
.write_resource::<Vec<Outcome>>()
.push(Outcome::ExpChange {
uid,
exp: *exp_reward,
xp_pools: HashSet::from_iter(vec![SkillGroupKind::Weapon(tool)]),
});
}
use common::comp::skills::{MiningSkill, Skill};
use rand::Rng;
let mut rng = rand::thread_rng();
if item.item_definition_id().contains("mineral.ore.")
&& rng.gen_bool(
0.05 * skillset
.skill_level(Skill::Pick(MiningSkill::OreGain))
.ok()
.flatten()
.unwrap_or(0) as f64,
)
{
let _ = item.increase_amount(1);
}
if item.item_definition_id().contains("mineral.gem.")
&& rng.gen_bool(
0.05 * skillset
.skill_level(Skill::Pick(MiningSkill::GemGain))
.ok()
.flatten()
.unwrap_or(0) as f64,
)
{
let _ = item.increase_amount(1);
}
}
state state
.create_object(Default::default(), comp::object::Body::Pouch) .create_object(Default::default(), comp::object::Body::Pouch)
.with(comp::Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0))) .with(comp::Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)))

View File

@ -207,7 +207,9 @@ impl Server {
handle_combo_change(&self, entity, change) handle_combo_change(&self, entity, change)
}, },
ServerEvent::RequestSiteInfo { entity, id } => handle_site_info(&self, entity, id), ServerEvent::RequestSiteInfo { entity, id } => handle_site_info(&self, entity, id),
ServerEvent::MineBlock { pos, tool } => handle_mine_block(self, pos, tool), ServerEvent::MineBlock { entity, pos, tool } => {
handle_mine_block(self, entity, pos, tool)
},
ServerEvent::TeleportTo { ServerEvent::TeleportTo {
entity, entity,
target, target,

View File

@ -0,0 +1,7 @@
-- Every character should have the pick skilltree unlocked by default.
-- This is handled by `SkillSet::default()` for new characters (and their skill
-- sets serialize properly during character creation), but since the database
-- deserialization builds the SkillSet fields from empty Vecs/HashMaps, the skill
-- tree needs to manually be added to each character.
INSERT INTO skill_group (entity_id, skill_group_kind, exp, available_sp, earned_sp)
SELECT character_id, 'Weapon Pick', 0, 0, 0 FROM character;

View File

@ -40,8 +40,8 @@ pub fn skill_to_db_string(skill: comp::skills::Skill) -> String {
use comp::{ use comp::{
item::tool::ToolKind, item::tool::ToolKind,
skills::{ skills::{
AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, RollSkill, SceptreSkill, AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, MiningSkill, RollSkill,
Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, SceptreSkill, Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill,
}, },
}; };
let skill_string = match skill { let skill_string = match skill {
@ -135,6 +135,9 @@ pub fn skill_to_db_string(skill: comp::skills::Skill) -> String {
Climb(ClimbSkill::Cost) => "Climb Cost", Climb(ClimbSkill::Cost) => "Climb Cost",
Climb(ClimbSkill::Speed) => "Climb Speed", Climb(ClimbSkill::Speed) => "Climb Speed",
Swim(SwimSkill::Speed) => "Swim Speed", Swim(SwimSkill::Speed) => "Swim Speed",
Pick(MiningSkill::Speed) => "Pick Speed",
Pick(MiningSkill::OreGain) => "Pick OreGain",
Pick(MiningSkill::GemGain) => "Pick GemGain",
UnlockGroup(SkillGroupKind::Weapon(ToolKind::Sword)) => "Unlock Weapon Sword", UnlockGroup(SkillGroupKind::Weapon(ToolKind::Sword)) => "Unlock Weapon Sword",
UnlockGroup(SkillGroupKind::Weapon(ToolKind::Axe)) => "Unlock Weapon Axe", UnlockGroup(SkillGroupKind::Weapon(ToolKind::Axe)) => "Unlock Weapon Axe",
UnlockGroup(SkillGroupKind::Weapon(ToolKind::Hammer)) => "Unlock Weapon Hammer", UnlockGroup(SkillGroupKind::Weapon(ToolKind::Hammer)) => "Unlock Weapon Hammer",
@ -160,8 +163,8 @@ pub fn db_string_to_skill(skill_string: &str) -> comp::skills::Skill {
use comp::{ use comp::{
item::tool::ToolKind, item::tool::ToolKind,
skills::{ skills::{
AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, RollSkill, SceptreSkill, AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, MiningSkill, RollSkill,
Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, SceptreSkill, Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill,
}, },
}; };
match skill_string { match skill_string {
@ -255,6 +258,9 @@ pub fn db_string_to_skill(skill_string: &str) -> comp::skills::Skill {
"Climb Cost" => Climb(ClimbSkill::Cost), "Climb Cost" => Climb(ClimbSkill::Cost),
"Climb Speed" => Climb(ClimbSkill::Speed), "Climb Speed" => Climb(ClimbSkill::Speed),
"Swim Speed" => Swim(SwimSkill::Speed), "Swim Speed" => Swim(SwimSkill::Speed),
"Pick Speed" => Pick(MiningSkill::Speed),
"Pick GemGain" => Pick(MiningSkill::GemGain),
"Pick OreGain" => Pick(MiningSkill::OreGain),
"Unlock Weapon Sword" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Sword)), "Unlock Weapon Sword" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Sword)),
"Unlock Weapon Axe" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Axe)), "Unlock Weapon Axe" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Axe)),
"Unlock Weapon Hammer" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Hammer)), "Unlock Weapon Hammer" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Hammer)),
@ -280,12 +286,12 @@ pub fn skill_group_to_db_string(skill_group: comp::skills::SkillGroupKind) -> St
Weapon(ToolKind::Bow) => "Weapon Bow", Weapon(ToolKind::Bow) => "Weapon Bow",
Weapon(ToolKind::Staff) => "Weapon Staff", Weapon(ToolKind::Staff) => "Weapon Staff",
Weapon(ToolKind::Sceptre) => "Weapon Sceptre", Weapon(ToolKind::Sceptre) => "Weapon Sceptre",
Weapon(ToolKind::Pick) => "Weapon Pick",
Weapon(ToolKind::Dagger) Weapon(ToolKind::Dagger)
| Weapon(ToolKind::Shield) | Weapon(ToolKind::Shield)
| Weapon(ToolKind::Spear) | Weapon(ToolKind::Spear)
| Weapon(ToolKind::Debug) | Weapon(ToolKind::Debug)
| Weapon(ToolKind::Farming) | Weapon(ToolKind::Farming)
| Weapon(ToolKind::Pick)
| Weapon(ToolKind::Empty) | Weapon(ToolKind::Empty)
| Weapon(ToolKind::Natural) => panic!( | Weapon(ToolKind::Natural) => panic!(
"Tried to add unsupported skill group to database: {:?}", "Tried to add unsupported skill group to database: {:?}",
@ -305,6 +311,7 @@ pub fn db_string_to_skill_group(skill_group_string: &str) -> comp::skills::Skill
"Weapon Bow" => Weapon(ToolKind::Bow), "Weapon Bow" => Weapon(ToolKind::Bow),
"Weapon Staff" => Weapon(ToolKind::Staff), "Weapon Staff" => Weapon(ToolKind::Staff),
"Weapon Sceptre" => Weapon(ToolKind::Sceptre), "Weapon Sceptre" => Weapon(ToolKind::Sceptre),
"Weapon Pick" => Weapon(ToolKind::Pick),
_ => panic!( _ => panic!(
"Tried to convert an unsupported string from the database: {}", "Tried to convert an unsupported string from the database: {}",
skill_group_string skill_group_string

View File

@ -149,6 +149,11 @@ widget_ids! {
skill_sceptre_aura_2, skill_sceptre_aura_2,
skill_sceptre_aura_3, skill_sceptre_aura_3,
skill_sceptre_aura_4, skill_sceptre_aura_4,
pick_render,
skill_pick_m1,
skill_pick_m1_0,
skill_pick_m1_1,
skill_pick_m1_2,
general_combat_render_0, general_combat_render_0,
general_combat_render_1, general_combat_render_1,
skill_general_stat_0, skill_general_stat_0,
@ -227,7 +232,7 @@ impl<'a> Diary<'a> {
pub type SelectedSkillTree = skills::SkillGroupKind; pub type SelectedSkillTree = skills::SkillGroupKind;
const TREES: [&str; 7] = [ const TREES: [&str; 8] = [
"General Combat", "General Combat",
"Sword", "Sword",
"Hammer", "Hammer",
@ -235,6 +240,7 @@ const TREES: [&str; 7] = [
"Sceptre", "Sceptre",
"Bow", "Bow",
"Fire Staff", "Fire Staff",
"Mining",
]; ];
pub enum Event { pub enum Event {
@ -353,6 +359,7 @@ impl<'a> Widget for Diary<'a> {
"Sceptre" => self.imgs.sceptre, "Sceptre" => self.imgs.sceptre,
"Bow" => self.imgs.bow, "Bow" => self.imgs.bow,
"Fire Staff" => self.imgs.staff, "Fire Staff" => self.imgs.staff,
"Mining" => self.imgs.mining,
_ => self.imgs.nothing, _ => self.imgs.nothing,
}); });
@ -500,6 +507,9 @@ impl<'a> Widget for Diary<'a> {
SelectedSkillTree::Weapon(ToolKind::Staff) => { SelectedSkillTree::Weapon(ToolKind::Staff) => {
self.localized_strings.get("common.weapons.staff") self.localized_strings.get("common.weapons.staff")
}, },
SelectedSkillTree::Weapon(ToolKind::Pick) => {
self.localized_strings.get("common.tool.mining")
},
_ => "Unknown", _ => "Unknown",
}; };
self.create_new_text(&tree_title, state.content_align, 2.0, 34, TEXT_COLOR) self.create_new_text(&tree_title, state.content_align, 2.0, 34, TEXT_COLOR)
@ -531,6 +541,7 @@ impl<'a> Widget for Diary<'a> {
SelectedSkillTree::Weapon(ToolKind::Bow) => 6, SelectedSkillTree::Weapon(ToolKind::Bow) => 6,
SelectedSkillTree::Weapon(ToolKind::Staff) => 4, SelectedSkillTree::Weapon(ToolKind::Staff) => 4,
SelectedSkillTree::Weapon(ToolKind::Sceptre) => 5, SelectedSkillTree::Weapon(ToolKind::Sceptre) => 5,
SelectedSkillTree::Weapon(ToolKind::Pick) => 4,
_ => 0, _ => 0,
}; };
let skills_top_r = match sel_tab { let skills_top_r = match sel_tab {
@ -1976,6 +1987,65 @@ impl<'a> Widget for Diary<'a> {
&diary_tooltip, &diary_tooltip,
); );
}, },
SelectedSkillTree::Weapon(ToolKind::Pick) => {
use skills::MiningSkill::*;
// Mining
Image::new(animate_by_pulse(
&self
.item_imgs
.img_ids_or_not_found_img(Tool("example_pick".to_string())),
self.pulse,
))
.wh(art_size)
.middle_of(state.content_align)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
.set(state.pick_render, ui);
// Top Left skills
// 5 1 6
// 3 0 4
// 8 2 7
Button::image(self.imgs.pickaxe)
.w_h(74.0, 74.0)
.mid_top_with_margin_on(state.skills_top_l[0], 3.0)
.with_tooltip(
self.tooltip_manager,
&self.localized_strings.get("hud.skill.pick_strike_title"),
&self.localized_strings.get("hud.skill.pick_strike"),
&diary_tooltip,
TEXT_COLOR,
)
.set(state.skill_pick_m1, ui);
self.create_unlock_skill_button(
Skill::Pick(Speed),
self.imgs.pickaxe_speed_skill,
state.skills_top_l[1],
"pick_strike_speed",
state.skill_pick_m1_0,
ui,
&mut events,
&diary_tooltip,
);
self.create_unlock_skill_button(
Skill::Pick(OreGain),
self.imgs.pickaxe_oregain_skill,
state.skills_top_l[2],
"pick_strike_oregain",
state.skill_pick_m1_1,
ui,
&mut events,
&diary_tooltip,
);
self.create_unlock_skill_button(
Skill::Pick(GemGain),
self.imgs.pickaxe_gemgain_skill,
state.skills_top_l[3],
"pick_strike_gemgain",
state.skill_pick_m1_2,
ui,
&mut events,
&diary_tooltip,
);
},
_ => {}, _ => {},
} }
@ -2034,6 +2104,7 @@ fn skill_tree_from_str(string: &str) -> Option<SelectedSkillTree> {
"Sceptre" => Some(SelectedSkillTree::Weapon(ToolKind::Sceptre)), "Sceptre" => Some(SelectedSkillTree::Weapon(ToolKind::Sceptre)),
"Bow" => Some(SelectedSkillTree::Weapon(ToolKind::Bow)), "Bow" => Some(SelectedSkillTree::Weapon(ToolKind::Bow)),
"Fire Staff" => Some(SelectedSkillTree::Weapon(ToolKind::Staff)), "Fire Staff" => Some(SelectedSkillTree::Weapon(ToolKind::Staff)),
"Mining" => Some(SelectedSkillTree::Weapon(ToolKind::Pick)),
_ => None, _ => None,
} }
} }

View File

@ -78,6 +78,9 @@ image_ids! {
hammer: "voxygen.element.weapons.hammer", hammer: "voxygen.element.weapons.hammer",
bow: "voxygen.element.weapons.bow", bow: "voxygen.element.weapons.bow",
staff: "voxygen.element.weapons.staff", staff: "voxygen.element.weapons.staff",
mining: "voxygen.element.weapons.mining",
pickaxe: "voxygen.element.skills.pickaxe",
pickaxe_ico: "voxygen.element.weapons.pickaxe",
lock: "voxygen.element.ui.diary.buttons.lock", lock: "voxygen.element.ui.diary.buttons.lock",
wpn_icon_border_skills: "voxygen.element.ui.diary.buttons.border_skills", wpn_icon_border_skills: "voxygen.element.ui.diary.buttons.border_skills",
wpn_icon_border: "voxygen.element.ui.generic.buttons.border", wpn_icon_border: "voxygen.element.ui.generic.buttons.border",
@ -303,6 +306,10 @@ image_ids! {
utility_speed_skill: "voxygen.element.skills.skilltree.utility_speed", utility_speed_skill: "voxygen.element.skills.skilltree.utility_speed",
utility_duration_skill: "voxygen.element.skills.skilltree.utility_duration", utility_duration_skill: "voxygen.element.skills.skilltree.utility_duration",
pickaxe_speed_skill: "voxygen.element.skills.pickaxe_speed",
pickaxe_oregain_skill: "voxygen.element.skills.pickaxe_oregain",
pickaxe_gemgain_skill: "voxygen.element.skills.pickaxe_gemgain",
// Skillbar // Skillbar
level_up: "voxygen.element.ui.skillbar.level_up", level_up: "voxygen.element.ui.skillbar.level_up",
bar_content: "voxygen.element.ui.skillbar.bar_content", bar_content: "voxygen.element.ui.skillbar.bar_content",

View File

@ -98,7 +98,7 @@ use conrod_core::{
widget::{self, Button, Image, Text}, widget::{self, Button, Image, Text},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
}; };
use hashbrown::HashMap; use hashbrown::{HashMap, HashSet};
use rand::Rng; use rand::Rng;
use specs::{Entity as EcsEntity, Join, WorldExt}; use specs::{Entity as EcsEntity, Join, WorldExt};
use std::{ use std::{
@ -211,6 +211,7 @@ widget_ids! {
player_rank_up_icon, player_rank_up_icon,
sct_exp_bgs[], sct_exp_bgs[],
sct_exps[], sct_exps[],
sct_exp_icons[],
sct_lvl_bg, sct_lvl_bg,
sct_lvl, sct_lvl,
hurt_bg, hurt_bg,
@ -324,6 +325,7 @@ pub struct ExpFloater {
pub exp_change: i32, pub exp_change: i32,
pub timer: f32, pub timer: f32,
pub rand_offset: (f32, f32), pub rand_offset: (f32, f32),
pub xp_pools: HashSet<SkillGroupKind>,
} }
pub struct SkillPointGain { pub struct SkillPointGain {
@ -1252,7 +1254,11 @@ impl Hud {
&mut self.ids.player_scts, &mut self.ids.player_scts,
&mut ui_widgets.widget_id_generator(), &mut ui_widgets.widget_id_generator(),
); );
// Increase font size based on fraction of maximum health let player_sct_icon_id = player_sct_id_walker.next(
&mut self.ids.player_scts,
&mut ui_widgets.widget_id_generator(),
);
// Increase font size based on fraction of maximum Experience
// "flashes" by having a larger size in the first 100ms // "flashes" by having a larger size in the first 100ms
let font_size_xp = let font_size_xp =
30 + ((floater.exp_change as f32 / 300.0).min(1.0) * 50.0) as u32; 30 + ((floater.exp_change as f32 / 300.0).min(1.0) * 50.0) as u32;
@ -1266,6 +1272,7 @@ impl Hud {
}; };
if floater.exp_change > 0 { if floater.exp_change > 0 {
let xp_pool = &floater.xp_pools;
// Don't show 0 Exp // Don't show 0 Exp
Text::new(&format!("{} Exp", floater.exp_change.max(1))) Text::new(&format!("{} Exp", floater.exp_change.max(1)))
.font_size(font_size_xp) .font_size(font_size_xp)
@ -1280,12 +1287,25 @@ impl Hud {
Text::new(&format!("{} Exp", floater.exp_change.max(1))) Text::new(&format!("{} Exp", floater.exp_change.max(1)))
.font_size(font_size_xp) .font_size(font_size_xp)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.59, 0.41, 0.67, fade)) .color(
if xp_pool.contains(&SkillGroupKind::Weapon(ToolKind::Pick)) {
Color::Rgba(0.18, 0.32, 0.9, fade)
} else {
Color::Rgba(0.59, 0.41, 0.67, fade)
},
)
.x_y( .x_y(
ui_widgets.win_w * (0.5 * floater.rand_offset.0 as f64 - 0.25), ui_widgets.win_w * (0.5 * floater.rand_offset.0 as f64 - 0.25),
ui_widgets.win_h * (0.15 * floater.rand_offset.1 as f64) + y, ui_widgets.win_h * (0.15 * floater.rand_offset.1 as f64) + y,
) )
.set(player_sct_id, ui_widgets); .set(player_sct_id, ui_widgets);
// Exp Source Image
if xp_pool.contains(&SkillGroupKind::Weapon(ToolKind::Pick)) {
Image::new(self.imgs.pickaxe_ico)
.w_h(font_size_xp as f64, font_size_xp as f64)
.left_from(player_sct_id, 5.0)
.set(player_sct_icon_id, ui_widgets);
}
} }
} }
} }
@ -1353,6 +1373,7 @@ impl Hud {
Weapon(ToolKind::Sceptre) => &i18n.get("common.weapons.sceptre"), Weapon(ToolKind::Sceptre) => &i18n.get("common.weapons.sceptre"),
Weapon(ToolKind::Bow) => &i18n.get("common.weapons.bow"), Weapon(ToolKind::Bow) => &i18n.get("common.weapons.bow"),
Weapon(ToolKind::Staff) => &i18n.get("common.weapons.staff"), Weapon(ToolKind::Staff) => &i18n.get("common.weapons.staff"),
Weapon(ToolKind::Pick) => &i18n.get("common.tool.mining"),
_ => "Unknown", _ => "Unknown",
}; };
Text::new(skill) Text::new(skill)
@ -1377,6 +1398,7 @@ impl Hud {
Weapon(ToolKind::Sceptre) => self.imgs.sceptre, Weapon(ToolKind::Sceptre) => self.imgs.sceptre,
Weapon(ToolKind::Bow) => self.imgs.bow, Weapon(ToolKind::Bow) => self.imgs.bow,
Weapon(ToolKind::Staff) => self.imgs.staff, Weapon(ToolKind::Staff) => self.imgs.staff,
Weapon(ToolKind::Pick) => self.imgs.mining,
_ => self.imgs.swords_crossed, _ => self.imgs.swords_crossed,
}) })
.w_h(20.0, 20.0) .w_h(20.0, 20.0)
@ -3657,12 +3679,15 @@ impl Hud {
pub fn handle_outcome(&mut self, outcome: &Outcome) { pub fn handle_outcome(&mut self, outcome: &Outcome) {
match outcome { match outcome {
Outcome::ExpChange { uid, exp } => self.floaters.exp_floaters.push(ExpFloater { Outcome::ExpChange { uid, exp, xp_pools } => {
self.floaters.exp_floaters.push(ExpFloater {
owner: *uid, owner: *uid,
exp_change: *exp, exp_change: *exp,
timer: 4.0, timer: 4.0,
rand_offset: rand::thread_rng().gen::<(f32, f32)>(), rand_offset: rand::thread_rng().gen::<(f32, f32)>(),
}), xp_pools: xp_pools.clone(),
})
},
Outcome::SkillPointGain { Outcome::SkillPointGain {
uid, uid,
skill_tree, skill_tree,