From e832fa86f18386d699908a78d2c659c94e9accd0 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sat, 5 Jun 2021 02:10:15 +0300 Subject: [PATCH 01/37] Add entity template and make 'loadouts' singular --- assets/common/entity/test.ron | 31 +++++++++++++++++++ .../common/{loadouts => loadout}/default.ron | 0 .../dungeon/tier-0/gnarling.ron | 0 .../dungeon/tier-1/adlet_bow.ron | 0 .../dungeon/tier-1/adlet_spear.ron | 0 .../dungeon/tier-2/sahagin.ron | 0 .../dungeon/tier-3/haniwa.ron | 0 .../dungeon/tier-4/myrmidon.ron | 0 .../dungeon/tier-5/beastmaster.ron | 0 .../dungeon/tier-5/husk.ron | 0 .../dungeon/tier-5/warlock.ron | 0 .../dungeon/tier-5/warlord.ron | 0 assets/common/{loadouts => loadout}/test.ron | 0 .../{loadouts => loadout}/village/guard.ron | 0 .../village/merchant.ron | 0 .../village/villager.ron | 0 common/src/comp/inventory/loadout_builder.rs | 30 +++++++++--------- 17 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 assets/common/entity/test.ron rename assets/common/{loadouts => loadout}/default.ron (100%) rename assets/common/{loadouts => loadout}/dungeon/tier-0/gnarling.ron (100%) rename assets/common/{loadouts => loadout}/dungeon/tier-1/adlet_bow.ron (100%) rename assets/common/{loadouts => loadout}/dungeon/tier-1/adlet_spear.ron (100%) rename assets/common/{loadouts => loadout}/dungeon/tier-2/sahagin.ron (100%) rename assets/common/{loadouts => loadout}/dungeon/tier-3/haniwa.ron (100%) rename assets/common/{loadouts => loadout}/dungeon/tier-4/myrmidon.ron (100%) rename assets/common/{loadouts => loadout}/dungeon/tier-5/beastmaster.ron (100%) rename assets/common/{loadouts => loadout}/dungeon/tier-5/husk.ron (100%) rename assets/common/{loadouts => loadout}/dungeon/tier-5/warlock.ron (100%) rename assets/common/{loadouts => loadout}/dungeon/tier-5/warlord.ron (100%) rename assets/common/{loadouts => loadout}/test.ron (100%) rename assets/common/{loadouts => loadout}/village/guard.ron (100%) rename assets/common/{loadouts => loadout}/village/merchant.ron (100%) rename assets/common/{loadouts => loadout}/village/villager.ron (100%) diff --git a/assets/common/entity/test.ron b/assets/common/entity/test.ron new file mode 100644 index 0000000000..64b15c7be2 --- /dev/null +++ b/assets/common/entity/test.ron @@ -0,0 +1,31 @@ +{ + /// Name of Entity + name: Some("Paddy"), + + /// Body + /// Can be Exact (Body with all fields e.g BodyType, Species, Hair color and such) + /// or Random (will use random if available for this Body) + /// or RandomWith (will use random_with if available for this Body) + body: Humanoid(Random), + + /// Loot + /// Can be Item (with asset_specifier for item) + /// or LootTable (with asset_specifier for loot table) + loot: LootTable("common.loot_tables.humanoids"), + + /// Main and second tools + /// Can be Option (with asset_specifier for item) + /// or Choice + /// (array of pairs with weight of choosing some item and Option) + main_tool: Some(Item("common.items.weapons.axe_1h.orichalcum-0")), + second_tool: None, + + /// Loadout Config as Option (with asset_specifier for loadout) + loadout_config: Some(Loadout("common.loadout.village.merchant")), + + /// Skillset Config as Option (with asset_specifier for skillset) + skillset_config: None, + + /// Meta Info (level, alignment, agency, etc) + meta: {}, +} diff --git a/assets/common/loadouts/default.ron b/assets/common/loadout/default.ron similarity index 100% rename from assets/common/loadouts/default.ron rename to assets/common/loadout/default.ron diff --git a/assets/common/loadouts/dungeon/tier-0/gnarling.ron b/assets/common/loadout/dungeon/tier-0/gnarling.ron similarity index 100% rename from assets/common/loadouts/dungeon/tier-0/gnarling.ron rename to assets/common/loadout/dungeon/tier-0/gnarling.ron diff --git a/assets/common/loadouts/dungeon/tier-1/adlet_bow.ron b/assets/common/loadout/dungeon/tier-1/adlet_bow.ron similarity index 100% rename from assets/common/loadouts/dungeon/tier-1/adlet_bow.ron rename to assets/common/loadout/dungeon/tier-1/adlet_bow.ron diff --git a/assets/common/loadouts/dungeon/tier-1/adlet_spear.ron b/assets/common/loadout/dungeon/tier-1/adlet_spear.ron similarity index 100% rename from assets/common/loadouts/dungeon/tier-1/adlet_spear.ron rename to assets/common/loadout/dungeon/tier-1/adlet_spear.ron diff --git a/assets/common/loadouts/dungeon/tier-2/sahagin.ron b/assets/common/loadout/dungeon/tier-2/sahagin.ron similarity index 100% rename from assets/common/loadouts/dungeon/tier-2/sahagin.ron rename to assets/common/loadout/dungeon/tier-2/sahagin.ron diff --git a/assets/common/loadouts/dungeon/tier-3/haniwa.ron b/assets/common/loadout/dungeon/tier-3/haniwa.ron similarity index 100% rename from assets/common/loadouts/dungeon/tier-3/haniwa.ron rename to assets/common/loadout/dungeon/tier-3/haniwa.ron diff --git a/assets/common/loadouts/dungeon/tier-4/myrmidon.ron b/assets/common/loadout/dungeon/tier-4/myrmidon.ron similarity index 100% rename from assets/common/loadouts/dungeon/tier-4/myrmidon.ron rename to assets/common/loadout/dungeon/tier-4/myrmidon.ron diff --git a/assets/common/loadouts/dungeon/tier-5/beastmaster.ron b/assets/common/loadout/dungeon/tier-5/beastmaster.ron similarity index 100% rename from assets/common/loadouts/dungeon/tier-5/beastmaster.ron rename to assets/common/loadout/dungeon/tier-5/beastmaster.ron diff --git a/assets/common/loadouts/dungeon/tier-5/husk.ron b/assets/common/loadout/dungeon/tier-5/husk.ron similarity index 100% rename from assets/common/loadouts/dungeon/tier-5/husk.ron rename to assets/common/loadout/dungeon/tier-5/husk.ron diff --git a/assets/common/loadouts/dungeon/tier-5/warlock.ron b/assets/common/loadout/dungeon/tier-5/warlock.ron similarity index 100% rename from assets/common/loadouts/dungeon/tier-5/warlock.ron rename to assets/common/loadout/dungeon/tier-5/warlock.ron diff --git a/assets/common/loadouts/dungeon/tier-5/warlord.ron b/assets/common/loadout/dungeon/tier-5/warlord.ron similarity index 100% rename from assets/common/loadouts/dungeon/tier-5/warlord.ron rename to assets/common/loadout/dungeon/tier-5/warlord.ron diff --git a/assets/common/loadouts/test.ron b/assets/common/loadout/test.ron similarity index 100% rename from assets/common/loadouts/test.ron rename to assets/common/loadout/test.ron diff --git a/assets/common/loadouts/village/guard.ron b/assets/common/loadout/village/guard.ron similarity index 100% rename from assets/common/loadouts/village/guard.ron rename to assets/common/loadout/village/guard.ron diff --git a/assets/common/loadouts/village/merchant.ron b/assets/common/loadout/village/merchant.ron similarity index 100% rename from assets/common/loadouts/village/merchant.ron rename to assets/common/loadout/village/merchant.ron diff --git a/assets/common/loadouts/village/villager.ron b/assets/common/loadout/village/villager.ron similarity index 100% rename from assets/common/loadouts/village/villager.ron rename to assets/common/loadout/village/villager.ron diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index f373b4d800..ed508f39e8 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -485,7 +485,7 @@ impl LoadoutBuilder { /// Set default armor items for the loadout. This may vary with game /// updates, but should be safe defaults for a new character. #[must_use] - pub fn defaults(self) -> Self { self.apply_asset_expect("common.loadouts.default") } + pub fn defaults(self) -> Self { self.apply_asset_expect("common.loadout.default") } /// Builds loadout of creature when spawned #[must_use] @@ -529,45 +529,45 @@ impl LoadoutBuilder { match config { LoadoutConfig::Gnarling => match active_tool_kind { Some(ToolKind::Bow | ToolKind::Staff | ToolKind::Spear) => { - builder.apply_asset_expect("common.loadouts.dungeon.tier-0.gnarling") + builder.apply_asset_expect("common.loadout.dungeon.tier-0.gnarling") }, _ => builder, }, LoadoutConfig::Adlet => match active_tool_kind { Some(ToolKind::Bow) => { - builder.apply_asset_expect("common.loadouts.dungeon.tier-1.adlet_bow") + builder.apply_asset_expect("common.loadout.dungeon.tier-1.adlet_bow") }, Some(ToolKind::Spear | ToolKind::Staff) => { - builder.apply_asset_expect("common.loadouts.dungeon.tier-1.adlet_spear") + builder.apply_asset_expect("common.loadout.dungeon.tier-1.adlet_spear") }, _ => builder, }, LoadoutConfig::Sahagin => { - builder.apply_asset_expect("common.loadouts.dungeon.tier-2.sahagin") + builder.apply_asset_expect("common.loadout.dungeon.tier-2.sahagin") }, LoadoutConfig::Haniwa => { - builder.apply_asset_expect("common.loadouts.dungeon.tier-3.haniwa") + builder.apply_asset_expect("common.loadout.dungeon.tier-3.haniwa") }, LoadoutConfig::Myrmidon => { - builder.apply_asset_expect("common.loadouts.dungeon.tier-4.myrmidon") + builder.apply_asset_expect("common.loadout.dungeon.tier-4.myrmidon") }, LoadoutConfig::Husk => { - builder.apply_asset_expect("common.loadouts.dungeon.tier-5.husk") + builder.apply_asset_expect("common.loadout.dungeon.tier-5.husk") }, LoadoutConfig::Beastmaster => { - builder.apply_asset_expect("common.loadouts.dungeon.tier-5.beastmaster") + builder.apply_asset_expect("common.loadout.dungeon.tier-5.beastmaster") }, LoadoutConfig::Warlord => { - builder.apply_asset_expect("common.loadouts.dungeon.tier-5.warlord") + builder.apply_asset_expect("common.loadout.dungeon.tier-5.warlord") }, LoadoutConfig::Warlock => { - builder.apply_asset_expect("common.loadouts.dungeon.tier-5.warlock") + builder.apply_asset_expect("common.loadout.dungeon.tier-5.warlock") }, LoadoutConfig::Villager => builder - .apply_asset_expect("common.loadouts.village.villager") + .apply_asset_expect("common.loadout.village.villager") .bag(ArmorSlot::Bag1, Some(make_potion_bag(10))), LoadoutConfig::Guard => builder - .apply_asset_expect("common.loadouts.village.guard") + .apply_asset_expect("common.loadout.village.guard") .bag(ArmorSlot::Bag1, Some(make_potion_bag(25))), LoadoutConfig::Merchant => { let mut backpack = @@ -685,7 +685,7 @@ impl LoadoutBuilder { } } builder - .apply_asset_expect("common.loadouts.village.merchant") + .apply_asset_expect("common.loadout.village.merchant") .back(Some(backpack)) .bag(ArmorSlot::Bag1, Some(bag1)) .bag(ArmorSlot::Bag2, Some(bag2)) @@ -947,7 +947,7 @@ mod tests { // It just load everything that could // TODO: add some checks, e.g. that Armor(Head) key correspond // to Item with ItemKind Head(_) - let loadouts = LoadoutList::load_expect_cloned("common.loadouts.*").0; + let loadouts = LoadoutList::load_expect_cloned("common.loadout.*").0; for loadout in loadouts { let spec = loadout.0; for (key, entry) in spec { From 102f6d3338bc7027f7ec64fb3518083662ac0540 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sat, 5 Jun 2021 17:30:20 +0300 Subject: [PATCH 02/37] EntityInfo assetization * Rename skillset_config to skillset_preset * Rename loadout_config to loadout_preset * Add skillset_config for asset_specifier of skillset * Add loadout_config for asset_specifier of loadout --- common/src/generation.rs | 22 ++++++++++++++++++---- common/src/states/basic_summon.rs | 9 +++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/common/src/generation.rs b/common/src/generation.rs index e922f604c7..e91763659a 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -25,8 +25,10 @@ pub struct EntityInfo { // TODO: Properly give NPCs skills pub level: Option, pub loot_drop: Option, - pub loadout_config: Option, - pub skillset_config: Option, + pub loadout_config: Option, + pub loadout_preset: Option, + pub skillset_config: Option, + pub skillset_preset: Option, pub pet: Option>, // we can't use DHashMap, do we want to move that into common? pub trading_information: Option, @@ -49,7 +51,9 @@ impl EntityInfo { level: None, loot_drop: None, loadout_config: None, + loadout_preset: None, skillset_config: None, + skillset_preset: None, pet: None, trading_information: None, } @@ -117,16 +121,26 @@ impl EntityInfo { self } - pub fn with_loadout_config(mut self, config: LoadoutConfig) -> Self { + pub fn with_loadout_config(mut self, config: String) -> Self { self.loadout_config = Some(config); self } - pub fn with_skillset_config(mut self, config: SkillSetConfig) -> Self { + pub fn with_loadout_preset(mut self, preset: LoadoutConfig) -> Self { + self.loadout_preset = Some(preset); + self + } + + pub fn with_skillset_config(mut self, config: String) -> Self { self.skillset_config = Some(config); self } + pub fn with_skillset_preset(mut self, preset: SkillSetConfig) -> Self { + self.skillset_preset = Some(preset); + self + } + pub fn with_automatic_name(mut self) -> Self { let npc_names = NPC_NAMES.read(); self.name = match &self.body { diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index 4abd7f8d8e..857ab191ed 100644 --- a/common/src/states/basic_summon.rs +++ b/common/src/states/basic_summon.rs @@ -79,14 +79,14 @@ impl CharacterBehavior for Data { let loadout = LoadoutBuilder::build_loadout( body, None, - self.static_data.summon_info.loadout_config, + self.static_data.summon_info.loadout_preset, None, ) .build(); let stats = comp::Stats::new("Summon".to_string()); let skill_set = SkillSetBuilder::build_skillset( &None, - self.static_data.summon_info.skillset_config, + self.static_data.summon_info.skillset_preset, ) .build(); @@ -175,6 +175,7 @@ pub struct SummonInfo { body: comp::Body, scale: Option, health_scaling: u16, - loadout_config: Option, - skillset_config: Option, + // TODO: use assets for specifying skills and loadouts? + loadout_preset: Option, + skillset_preset: Option, } From e153cbe20e1cfb0d63ca80335ef6a3394a85e7d0 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sat, 5 Jun 2021 18:20:12 +0300 Subject: [PATCH 03/37] Rename LoadoutBuilder::apply_asset_expect to LoadoutBuilder::with_asset_expect --- common/src/comp/inventory/loadout_builder.rs | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index ed508f39e8..87013950cc 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -397,7 +397,7 @@ impl LoadoutBuilder { pub fn from_asset_expect(asset_specifier: &str) -> Self { let loadout = Self::new(); - loadout.apply_asset_expect(asset_specifier) + loadout.with_asset_expect(asset_specifier) } /// # Usage @@ -409,7 +409,7 @@ impl LoadoutBuilder { /// 2) Will panic if path to item specified in loadout file doesn't exist /// 3) Will panic while runs in tests and asset doesn't have "correct" form #[must_use] - pub fn apply_asset_expect(mut self, asset_specifier: &str) -> Self { + pub fn with_asset_expect(mut self, asset_specifier: &str) -> Self { let spec = LoadoutSpec::load_expect(asset_specifier).read().0.clone(); for (key, entry) in spec { let item = match entry.try_to_item(asset_specifier) { @@ -485,7 +485,7 @@ impl LoadoutBuilder { /// Set default armor items for the loadout. This may vary with game /// updates, but should be safe defaults for a new character. #[must_use] - pub fn defaults(self) -> Self { self.apply_asset_expect("common.loadout.default") } + pub fn defaults(self) -> Self { self.with_asset_expect("common.loadout.default") } /// Builds loadout of creature when spawned #[must_use] @@ -529,45 +529,45 @@ impl LoadoutBuilder { match config { LoadoutConfig::Gnarling => match active_tool_kind { Some(ToolKind::Bow | ToolKind::Staff | ToolKind::Spear) => { - builder.apply_asset_expect("common.loadout.dungeon.tier-0.gnarling") + builder.with_asset_expect("common.loadout.dungeon.tier-0.gnarling") }, _ => builder, }, LoadoutConfig::Adlet => match active_tool_kind { Some(ToolKind::Bow) => { - builder.apply_asset_expect("common.loadout.dungeon.tier-1.adlet_bow") + builder.with_asset_expect("common.loadout.dungeon.tier-1.adlet_bow") }, Some(ToolKind::Spear | ToolKind::Staff) => { - builder.apply_asset_expect("common.loadout.dungeon.tier-1.adlet_spear") + builder.with_asset_expect("common.loadout.dungeon.tier-1.adlet_spear") }, _ => builder, }, LoadoutConfig::Sahagin => { - builder.apply_asset_expect("common.loadout.dungeon.tier-2.sahagin") + builder.with_asset_expect("common.loadout.dungeon.tier-2.sahagin") }, LoadoutConfig::Haniwa => { - builder.apply_asset_expect("common.loadout.dungeon.tier-3.haniwa") + builder.with_asset_expect("common.loadout.dungeon.tier-3.haniwa") }, LoadoutConfig::Myrmidon => { - builder.apply_asset_expect("common.loadout.dungeon.tier-4.myrmidon") + builder.with_asset_expect("common.loadout.dungeon.tier-4.myrmidon") }, LoadoutConfig::Husk => { - builder.apply_asset_expect("common.loadout.dungeon.tier-5.husk") + builder.with_asset_expect("common.loadout.dungeon.tier-5.husk") }, LoadoutConfig::Beastmaster => { - builder.apply_asset_expect("common.loadout.dungeon.tier-5.beastmaster") + builder.with_asset_expect("common.loadout.dungeon.tier-5.beastmaster") }, LoadoutConfig::Warlord => { - builder.apply_asset_expect("common.loadout.dungeon.tier-5.warlord") + builder.with_asset_expect("common.loadout.dungeon.tier-5.warlord") }, LoadoutConfig::Warlock => { - builder.apply_asset_expect("common.loadout.dungeon.tier-5.warlock") + builder.with_asset_expect("common.loadout.dungeon.tier-5.warlock") }, LoadoutConfig::Villager => builder - .apply_asset_expect("common.loadout.village.villager") + .with_asset_expect("common.loadout.village.villager") .bag(ArmorSlot::Bag1, Some(make_potion_bag(10))), LoadoutConfig::Guard => builder - .apply_asset_expect("common.loadout.village.guard") + .with_asset_expect("common.loadout.village.guard") .bag(ArmorSlot::Bag1, Some(make_potion_bag(25))), LoadoutConfig::Merchant => { let mut backpack = @@ -685,7 +685,7 @@ impl LoadoutBuilder { } } builder - .apply_asset_expect("common.loadout.village.merchant") + .with_asset_expect("common.loadout.village.merchant") .back(Some(backpack)) .bag(ArmorSlot::Bag1, Some(bag1)) .bag(ArmorSlot::Bag2, Some(bag2)) From aad65c615917e0e9c6f8bc26f14ffec07d9981c0 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sat, 5 Jun 2021 19:22:51 +0300 Subject: [PATCH 04/37] Move traveler loadout declaration to asset * New loadout/world/traveler.ron file to specify traveler loadout * LoadoutBuilder::with_asset_expect now can use passed rng to choose items --- assets/common/loadout/world/traveler.ron | 28 +++++++++ common/src/comp/inventory/loadout_builder.rs | 64 +++++++++++--------- server/src/rtsim/entity.rs | 59 +----------------- 3 files changed, 66 insertions(+), 85 deletions(-) create mode 100644 assets/common/loadout/world/traveler.ron diff --git a/assets/common/loadout/world/traveler.ron b/assets/common/loadout/world/traveler.ron new file mode 100644 index 0000000000..c2126e5496 --- /dev/null +++ b/assets/common/loadout/world/traveler.ron @@ -0,0 +1,28 @@ +({ + ActiveMainhand: Choice([ + (1.0, Some(Item("common.items.weapons.sword.wood-2"))), + (1.0, Some(Item("common.items.weapons.sword.starter"))), + (1.0, Some(Item("common.items.weapons.sword.wood-0"))), + (1.0, Some(Item("common.items.weapons.bow.starter"))), + (1.0, Some(Item("common.items.weapons.bow.hardwood-2"))), + ]), + + Armor(Chest): Item("common.items.npc_armor.chest.leather_blue"), + Armor(Legs): Item("common.items.npc_armor.pants.leather_blue"), + Armor(Shoulders): Item("common.items.armor.swift.shoulder"), + + Armor(Back): Choice([ + (1.0, Some(Item("common.items.armor.hide.rawhide.back"))), + (1.0, Some(Item("common.items.armor.misc.back.backpack"))), + (1.0, Some(Item("common.items.npc_armor.back.backpack_blue"))), + (1.0, Some(Item("common.items.npc_armor.back.leather_blue"))), + (1.0, None), + ]), + + Lantern: Choice([ + (1.0, Some(Item("common.items.lantern.black_0"))), + (1.0, Some(Item("common.items.lantern.blue_0"))), + (1.0, Some(Item("common.items.lantern.green_0"))), + (1.0, Some(Item("common.items.lantern.red_0"))), + ]), +}) diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 87013950cc..5453a9b0c2 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -73,16 +73,16 @@ enum ItemSpec { } impl ItemSpec { - fn try_to_item(&self, asset_specifier: &str) -> Option { + fn try_to_item(&self, asset_specifier: &str, rng: &mut impl Rng) -> Option { match self { ItemSpec::Item(specifier) => Some(Item::new_from_asset_expect(&specifier)), ItemSpec::Choice(items) => { - choose(&items, asset_specifier) + choose(&items, asset_specifier, rng) .as_ref() .and_then(|e| match e { - entry @ ItemSpec::Item { .. } => entry.try_to_item(asset_specifier), - choice @ ItemSpec::Choice { .. } => choice.try_to_item(asset_specifier), + entry @ ItemSpec::Item { .. } => entry.try_to_item(asset_specifier, rng), + choice @ ItemSpec::Choice { .. } => choice.try_to_item(asset_specifier, rng), }) }, } @@ -108,10 +108,12 @@ impl ItemSpec { } } -fn choose<'a>(items: &'a [(f32, Option)], asset_specifier: &str) -> &'a Option { - let mut rng = rand::thread_rng(); - - items.choose_weighted(&mut rng, |item| item.0).map_or_else( +fn choose<'a>( + items: &'a [(f32, Option)], + asset_specifier: &str, + rng: &mut impl Rng, +) -> &'a Option { + items.choose_weighted(rng, |item| item.0).map_or_else( |err| match err { WeightedError::NoItem | WeightedError::AllWeightsZero => &None, WeightedError::InvalidWeight => { @@ -394,10 +396,15 @@ impl LoadoutBuilder { } #[must_use] - pub fn from_asset_expect(asset_specifier: &str) -> Self { + pub fn from_asset_expect(asset_specifier: &str, rng: Option<&mut impl Rng>) -> Self { let loadout = Self::new(); - loadout.with_asset_expect(asset_specifier) + if let Some(rng) = rng { + loadout.with_asset_expect(asset_specifier, rng) + } else { + let rng = &mut rand::thread_rng(); + loadout.with_asset_expect(asset_specifier, rng) + } } /// # Usage @@ -409,10 +416,10 @@ impl LoadoutBuilder { /// 2) Will panic if path to item specified in loadout file doesn't exist /// 3) Will panic while runs in tests and asset doesn't have "correct" form #[must_use] - pub fn with_asset_expect(mut self, asset_specifier: &str) -> Self { + pub fn with_asset_expect(mut self, asset_specifier: &str, rng: &mut impl Rng) -> Self { let spec = LoadoutSpec::load_expect(asset_specifier).read().0.clone(); for (key, entry) in spec { - let item = match entry.try_to_item(asset_specifier) { + let item = match entry.try_to_item(asset_specifier, rng) { Some(item) => item, None => continue, }; @@ -485,7 +492,10 @@ impl LoadoutBuilder { /// Set default armor items for the loadout. This may vary with game /// updates, but should be safe defaults for a new character. #[must_use] - pub fn defaults(self) -> Self { self.with_asset_expect("common.loadout.default") } + pub fn defaults(self) -> Self { + let rng = &mut rand::thread_rng(); + self.with_asset_expect("common.loadout.default", rng) + } /// Builds loadout of creature when spawned #[must_use] @@ -523,51 +533,52 @@ impl LoadoutBuilder { } }); // Creates rest of loadout + let rng = &mut rand::thread_rng(); let loadout_builder = if let Some(config) = config { let builder = Self::new().active_mainhand(active_item); // NOTE: we apply asset after active mainhand so asset has ability override it match config { LoadoutConfig::Gnarling => match active_tool_kind { Some(ToolKind::Bow | ToolKind::Staff | ToolKind::Spear) => { - builder.with_asset_expect("common.loadout.dungeon.tier-0.gnarling") + builder.with_asset_expect("common.loadout.dungeon.tier-0.gnarling", rng) }, _ => builder, }, LoadoutConfig::Adlet => match active_tool_kind { Some(ToolKind::Bow) => { - builder.with_asset_expect("common.loadout.dungeon.tier-1.adlet_bow") + builder.with_asset_expect("common.loadout.dungeon.tier-1.adlet_bow", rng) }, Some(ToolKind::Spear | ToolKind::Staff) => { - builder.with_asset_expect("common.loadout.dungeon.tier-1.adlet_spear") + builder.with_asset_expect("common.loadout.dungeon.tier-1.adlet_spear", rng) }, _ => builder, }, LoadoutConfig::Sahagin => { - builder.with_asset_expect("common.loadout.dungeon.tier-2.sahagin") + builder.with_asset_expect("common.loadout.dungeon.tier-2.sahagin", rng) }, LoadoutConfig::Haniwa => { - builder.with_asset_expect("common.loadout.dungeon.tier-3.haniwa") + builder.with_asset_expect("common.loadout.dungeon.tier-3.haniwa", rng) }, LoadoutConfig::Myrmidon => { - builder.with_asset_expect("common.loadout.dungeon.tier-4.myrmidon") + builder.with_asset_expect("common.loadout.dungeon.tier-4.myrmidon", rng) }, LoadoutConfig::Husk => { - builder.with_asset_expect("common.loadout.dungeon.tier-5.husk") + builder.with_asset_expect("common.loadout.dungeon.tier-5.husk", rng) }, LoadoutConfig::Beastmaster => { - builder.with_asset_expect("common.loadout.dungeon.tier-5.beastmaster") + builder.with_asset_expect("common.loadout.dungeon.tier-5.beastmaster", rng) }, LoadoutConfig::Warlord => { - builder.with_asset_expect("common.loadout.dungeon.tier-5.warlord") + builder.with_asset_expect("common.loadout.dungeon.tier-5.warlord", rng) }, LoadoutConfig::Warlock => { - builder.with_asset_expect("common.loadout.dungeon.tier-5.warlock") + builder.with_asset_expect("common.loadout.dungeon.tier-5.warlock", rng) }, LoadoutConfig::Villager => builder - .with_asset_expect("common.loadout.village.villager") + .with_asset_expect("common.loadout.village.villager", rng) .bag(ArmorSlot::Bag1, Some(make_potion_bag(10))), LoadoutConfig::Guard => builder - .with_asset_expect("common.loadout.village.guard") + .with_asset_expect("common.loadout.village.guard", rng) .bag(ArmorSlot::Bag1, Some(make_potion_bag(25))), LoadoutConfig::Merchant => { let mut backpack = @@ -618,7 +629,6 @@ impl LoadoutBuilder { } } } - let mut rng = rand::thread_rng(); let mut item_with_amount = |item_id: &str, amount: &mut f32| { if *amount > 0.0 { let mut item = Item::new_from_asset_expect(item_id); @@ -685,7 +695,7 @@ impl LoadoutBuilder { } } builder - .with_asset_expect("common.loadout.village.merchant") + .with_asset_expect("common.loadout.village.merchant", rng) .back(Some(backpack)) .bag(ArmorSlot::Bag1, Some(bag1)) .bag(ArmorSlot::Bag2, Some(bag2)) diff --git a/server/src/rtsim/entity.rs b/server/src/rtsim/entity.rs index 940297025d..5405f10cea 100644 --- a/server/src/rtsim/entity.rs +++ b/server/src/rtsim/entity.rs @@ -77,65 +77,8 @@ impl Entity { pub fn get_loadout(&self) -> comp::inventory::loadout::Loadout { let mut rng = self.rng(PERM_LOADOUT); - let main_tool = comp::Item::new_from_asset_expect( - (&[ - "common.items.weapons.sword.wood-2", - "common.items.weapons.sword.starter", - "common.items.weapons.sword.wood-0", - "common.items.weapons.bow.starter", - "common.items.weapons.bow.hardwood-2", - ]) - .choose(&mut rng) - .unwrap(), - ); - let back = match rng.gen_range(0..5) { - 0 => Some(comp::Item::new_from_asset_expect( - "common.items.armor.hide.rawhide.back", - )), - 1 => Some(comp::Item::new_from_asset_expect( - "common.items.armor.misc.back.backpack", - )), - 2 => Some(comp::Item::new_from_asset_expect( - "common.items.npc_armor.back.backpack_blue", - )), - 3 => Some(comp::Item::new_from_asset_expect( - "common.items.npc_armor.back.leather_blue", - )), - _ => None, - }; - - let lantern = match rng.gen_range(0..4) { - 0 => Some(comp::Item::new_from_asset_expect( - "common.items.lantern.black_0", - )), - 1 => Some(comp::Item::new_from_asset_expect( - "common.items.lantern.blue_0", - )), - 2 => Some(comp::Item::new_from_asset_expect( - "common.items.lantern.green_0", - )), - _ => Some(comp::Item::new_from_asset_expect( - "common.items.lantern.red_0", - )), - }; - - let chest = Some(comp::Item::new_from_asset_expect( - "common.items.npc_armor.chest.leather_blue", - )); - let pants = Some(comp::Item::new_from_asset_expect( - "common.items.npc_armor.pants.leather_blue", - )); - let shoulder = Some(comp::Item::new_from_asset_expect( - "common.items.armor.hide.leather.shoulder", - )); - - LoadoutBuilder::build_loadout(self.get_body(), Some(main_tool), None, None) - .back(back) - .lantern(lantern) - .chest(chest) - .pants(pants) - .shoulder(shoulder) + LoadoutBuilder::from_asset_expect("common.loadout.world.traveler", &mut rng) .bag( comp::inventory::slot::ArmorSlot::Bag1, Some(comp::inventory::loadout_builder::make_potion_bag(100)), From 5f3eaddb70c8621e2851a86ce9e02adfc9873886 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sat, 5 Jun 2021 21:05:31 +0300 Subject: [PATCH 05/37] Split LodoutBuilder::build_loadout LoadoutBuilder::build_loadout is a function which has four parameters and 3 of them are Option<>, and although fourth (body) isn't Option<>, it's optional too because it is used only in some combinations of another arguments. Because these combinations produces quirky code flow, it will be better to split it to different methods. So we did following changes to remove it and rewrite code that was using it to use better methods. * Introduce LoadoutPreset as new LoadoutConfig, currently it's only used in Summon ability, because SummonInfo uses Copy and we can't specify String for specifying asset path for loadout. Everything else is rewritten to use asset path to create loadouts. * More builder methods for LoadoutBuilder. Namely: - from_default which is used in server/src/cmd.rs in "/spawn" command. - with_default_equipment, with_default_maintool to use default loadout for specific body - with_preset to use LoadoutPreset * Add new make_loadout field with `fn (loadout_builder, trading_info) -> loadout_builder` to EntityInfo which allows to lazily construct loadout without modifying LoadoutBuilder code * Fix Merchants not having trade site We had heuristic that if something has Merchant LoadoutConfig - it's merchant, which can be false, especially if we create Merchant loadout lazily As side note, we do same check for Guards and it fails too. Too fix it, we introduce new agent::Mark, which explicitly specifies kind of agent for entity * `LoadoutBuilder::build_loadout` was written in a such way that depending on main_tool you will have different loadout. Turns out it was this way only for Adlets though and this behaviour is reproduced by specifying different loadouts directly in world code. --- assets/common/entity/test.ron | 6 +- common/src/comp/agent.rs | 6 + common/src/comp/inventory/loadout_builder.rs | 429 +++++-------------- common/src/generation.rs | 48 ++- common/src/states/basic_summon.rs | 20 +- server/src/cmd.rs | 2 +- server/src/rtsim/entity.rs | 2 +- server/src/sys/terrain.rs | 66 ++- world/src/site/dungeon/mod.rs | 69 +-- world/src/site/settlement/mod.rs | 177 +++++++- 10 files changed, 413 insertions(+), 412 deletions(-) diff --git a/assets/common/entity/test.ron b/assets/common/entity/test.ron index 64b15c7be2..2a70be337c 100644 --- a/assets/common/entity/test.ron +++ b/assets/common/entity/test.ron @@ -6,7 +6,7 @@ /// Can be Exact (Body with all fields e.g BodyType, Species, Hair color and such) /// or Random (will use random if available for this Body) /// or RandomWith (will use random_with if available for this Body) - body: Humanoid(Random), + // body: Humanoid(Random), /// Loot /// Can be Item (with asset_specifier for item) @@ -24,8 +24,8 @@ loadout_config: Some(Loadout("common.loadout.village.merchant")), /// Skillset Config as Option (with asset_specifier for skillset) - skillset_config: None, + // skillset_config: None, /// Meta Info (level, alignment, agency, etc) - meta: {}, + // meta: {}, } diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 3fade71b8c..8b792f361f 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -32,6 +32,12 @@ pub enum Alignment { Passive, } +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Mark { + Merchant, + Guard, +} + impl Alignment { // Always attacks pub fn hostile_towards(self, other: Alignment) -> bool { diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 5453a9b0c2..8e49b3c4ae 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -7,12 +7,11 @@ use crate::{ inventory::{ loadout::Loadout, slot::{ArmorSlot, EquipSlot}, - trade_pricing::TradePricing, }, - item::{tool::ToolKind, Item, ItemKind}, + item::Item, object, quadruped_low, quadruped_medium, theropod, Body, }, - trade::{Good, SiteInformation}, + trade::SiteInformation, }; use hashbrown::HashMap; use rand::{self, distributions::WeightedError, seq::SliceRandom, Rng}; @@ -41,20 +40,9 @@ use tracing::warn; #[derive(Clone)] pub struct LoadoutBuilder(Loadout); -#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug, EnumIter)] -pub enum LoadoutConfig { - Gnarling, - Adlet, - Sahagin, - Haniwa, - Myrmidon, +#[derive(Copy, Clone, PartialEq, Deserialize, Serialize, Debug, EnumIter)] +pub enum LoadoutPreset { Husk, - Beastmaster, - Warlord, - Warlock, - Villager, - Guard, - Merchant, } #[derive(Debug, Deserialize, Clone)] @@ -82,7 +70,9 @@ impl ItemSpec { .as_ref() .and_then(|e| match e { entry @ ItemSpec::Item { .. } => entry.try_to_item(asset_specifier, rng), - choice @ ItemSpec::Choice { .. } => choice.try_to_item(asset_specifier, rng), + choice @ ItemSpec::Choice { .. } => { + choice.try_to_item(asset_specifier, rng) + }, }) }, } @@ -165,7 +155,7 @@ pub fn make_potion_bag(quantity: u32) -> Item { // Also we are using default tools for un-specified species so // it's fine to have wildcards #[allow(clippy::too_many_lines, clippy::match_wildcard_for_single_variants)] -pub fn default_main_tool(body: &Body) -> Option { +fn default_main_tool(body: &Body) -> Option { match body { Body::Golem(golem) => match golem.species { golem::Species::StoneGolem => Some(Item::new_from_asset_expect( @@ -350,63 +340,108 @@ impl LoadoutBuilder { pub fn new() -> Self { Self(Loadout::new_empty()) } #[must_use] - fn with_default_equipment(body: &Body, active_item: Option) -> Self { - let mut builder = Self::new(); - builder = match body { - Body::BipedLarge(biped_large::Body { - species: biped_large::Species::Mindflayer, - .. - }) => builder.chest(Some(Item::new_from_asset_expect( - "common.items.npc_armor.biped_large.mindflayer", - ))), - Body::BipedLarge(biped_large::Body { - species: biped_large::Species::Minotaur, - .. - }) => builder.chest(Some(Item::new_from_asset_expect( - "common.items.npc_armor.biped_large.minotaur", - ))), - Body::BipedLarge(biped_large::Body { - species: biped_large::Species::Tidalwarrior, - .. - }) => builder.chest(Some(Item::new_from_asset_expect( - "common.items.npc_armor.biped_large.tidal_warrior", - ))), - Body::BipedLarge(biped_large::Body { - species: biped_large::Species::Yeti, - .. - }) => builder.chest(Some(Item::new_from_asset_expect( - "common.items.npc_armor.biped_large.yeti", - ))), - Body::BipedLarge(biped_large::Body { - species: biped_large::Species::Harvester, - .. - }) => builder.chest(Some(Item::new_from_asset_expect( - "common.items.npc_armor.biped_large.harvester", - ))), - Body::Golem(golem::Body { - species: golem::Species::ClayGolem, - .. - }) => builder.chest(Some(Item::new_from_asset_expect( - "common.items.npc_armor.golem.claygolem", - ))), - _ => builder, - }; - - builder.active_mainhand(active_item) - } - - #[must_use] + /// Construct new `LoadoutBuilder` from `asset_specifier` + /// Will panic if asset is broken pub fn from_asset_expect(asset_specifier: &str, rng: Option<&mut impl Rng>) -> Self { + // It's impossible to use lambdas because `loadout` is used by value + #![allow(clippy::option_if_let_else)] let loadout = Self::new(); if let Some(rng) = rng { loadout.with_asset_expect(asset_specifier, rng) } else { - let rng = &mut rand::thread_rng(); - loadout.with_asset_expect(asset_specifier, rng) + let fallback_rng = &mut rand::thread_rng(); + loadout.with_asset_expect(asset_specifier, fallback_rng) } } + #[must_use] + /// Construct new default `LoadoutBuilder` for corresponding `body` + /// + /// NOTE: make sure that you check what is default for this body + /// Use it if you don't care much about it, for example in "/spawn" command + pub fn from_default(body: &Body) -> Self { + let loadout = Self::new(); + loadout + .with_default_maintool(body) + .with_default_equipment(body) + } + + #[must_use] + /// Set default active mainhand weapon based on `body` + pub fn with_default_maintool(self, body: &Body) -> Self { + self.active_mainhand(default_main_tool(body)) + } + + #[must_use] + /// Set default equipement based on `body` + pub fn with_default_equipment(mut self, body: &Body) -> Self { + self = match body { + Body::BipedLarge(biped_large::Body { + species: biped_large::Species::Mindflayer, + .. + }) => self.chest(Some(Item::new_from_asset_expect( + "common.items.npc_armor.biped_large.mindflayer", + ))), + Body::BipedLarge(biped_large::Body { + species: biped_large::Species::Minotaur, + .. + }) => self.chest(Some(Item::new_from_asset_expect( + "common.items.npc_armor.biped_large.minotaur", + ))), + Body::BipedLarge(biped_large::Body { + species: biped_large::Species::Tidalwarrior, + .. + }) => self.chest(Some(Item::new_from_asset_expect( + "common.items.npc_armor.biped_large.tidal_warrior", + ))), + Body::BipedLarge(biped_large::Body { + species: biped_large::Species::Yeti, + .. + }) => self.chest(Some(Item::new_from_asset_expect( + "common.items.npc_armor.biped_large.yeti", + ))), + Body::BipedLarge(biped_large::Body { + species: biped_large::Species::Harvester, + .. + }) => self.chest(Some(Item::new_from_asset_expect( + "common.items.npc_armor.biped_large.harvester", + ))), + Body::Golem(golem::Body { + species: golem::Species::ClayGolem, + .. + }) => self.chest(Some(Item::new_from_asset_expect( + "common.items.npc_armor.golem.claygolem", + ))), + _ => self, + }; + + self + } + + #[must_use] + pub fn with_preset(mut self, preset: LoadoutPreset) -> Self { + let rng = &mut rand::thread_rng(); + match preset { + LoadoutPreset::Husk => { + self = self.with_asset_expect("common.loadout.dungeon.tier-5.husk", rng) + }, + } + + self + } + + #[must_use] + pub fn with_creator( + mut self, + creator: fn(LoadoutBuilder, Option<&SiteInformation>) -> LoadoutBuilder, + economy: Option<&SiteInformation>, + ) -> LoadoutBuilder { + self = creator(self, economy); + + self + } + /// # Usage /// Creates new `LoadoutBuilder` with all field replaced from /// `asset_specifier` which corresponds to loadout config @@ -497,219 +532,6 @@ impl LoadoutBuilder { self.with_asset_expect("common.loadout.default", rng) } - /// Builds loadout of creature when spawned - #[must_use] - // The reason why this function is so long is creating merchant inventory - // with all items to sell. - // Maybe we should do it on the caller side? - #[allow( - clippy::too_many_lines, - clippy::cast_precision_loss, - clippy::cast_sign_loss, - clippy::cast_possible_truncation - )] - pub fn build_loadout( - body: Body, - mut main_tool: Option, - config: Option, - economy: Option<&SiteInformation>, - ) -> Self { - // If no main tool is passed in, checks if species has a default main tool - if main_tool.is_none() { - main_tool = default_main_tool(&body); - } - - // Constructs ItemConfig from Item - let active_item = if let Some(ItemKind::Tool(_)) = main_tool.as_ref().map(Item::kind) { - main_tool - } else { - Some(Item::empty()) - }; - let active_tool_kind = active_item.as_ref().and_then(|i| { - if let ItemKind::Tool(tool) = &i.kind() { - Some(tool.kind) - } else { - None - } - }); - // Creates rest of loadout - let rng = &mut rand::thread_rng(); - let loadout_builder = if let Some(config) = config { - let builder = Self::new().active_mainhand(active_item); - // NOTE: we apply asset after active mainhand so asset has ability override it - match config { - LoadoutConfig::Gnarling => match active_tool_kind { - Some(ToolKind::Bow | ToolKind::Staff | ToolKind::Spear) => { - builder.with_asset_expect("common.loadout.dungeon.tier-0.gnarling", rng) - }, - _ => builder, - }, - LoadoutConfig::Adlet => match active_tool_kind { - Some(ToolKind::Bow) => { - builder.with_asset_expect("common.loadout.dungeon.tier-1.adlet_bow", rng) - }, - Some(ToolKind::Spear | ToolKind::Staff) => { - builder.with_asset_expect("common.loadout.dungeon.tier-1.adlet_spear", rng) - }, - _ => builder, - }, - LoadoutConfig::Sahagin => { - builder.with_asset_expect("common.loadout.dungeon.tier-2.sahagin", rng) - }, - LoadoutConfig::Haniwa => { - builder.with_asset_expect("common.loadout.dungeon.tier-3.haniwa", rng) - }, - LoadoutConfig::Myrmidon => { - builder.with_asset_expect("common.loadout.dungeon.tier-4.myrmidon", rng) - }, - LoadoutConfig::Husk => { - builder.with_asset_expect("common.loadout.dungeon.tier-5.husk", rng) - }, - LoadoutConfig::Beastmaster => { - builder.with_asset_expect("common.loadout.dungeon.tier-5.beastmaster", rng) - }, - LoadoutConfig::Warlord => { - builder.with_asset_expect("common.loadout.dungeon.tier-5.warlord", rng) - }, - LoadoutConfig::Warlock => { - builder.with_asset_expect("common.loadout.dungeon.tier-5.warlock", rng) - }, - LoadoutConfig::Villager => builder - .with_asset_expect("common.loadout.village.villager", rng) - .bag(ArmorSlot::Bag1, Some(make_potion_bag(10))), - LoadoutConfig::Guard => builder - .with_asset_expect("common.loadout.village.guard", rng) - .bag(ArmorSlot::Bag1, Some(make_potion_bag(25))), - LoadoutConfig::Merchant => { - let mut backpack = - Item::new_from_asset_expect("common.items.armor.misc.back.backpack"); - let mut coins = economy - .and_then(|e| e.unconsumed_stock.get(&Good::Coin)) - .copied() - .unwrap_or_default() - .round() - .min(rand::thread_rng().gen_range(1000.0..3000.0)) - as u32; - let armor = economy - .and_then(|e| e.unconsumed_stock.get(&Good::Armor)) - .copied() - .unwrap_or_default() - / 10.0; - for s in backpack.slots_mut() { - if coins > 0 { - let mut coin_item = - Item::new_from_asset_expect("common.items.utility.coins"); - coin_item - .set_amount(coins) - .expect("coins should be stackable"); - *s = Some(coin_item); - coins = 0; - } else if armor > 0.0 { - if let Some(item_id) = - TradePricing::random_item(Good::Armor, armor, true) - { - *s = Some(Item::new_from_asset_expect(&item_id)); - } - } - } - let mut bag1 = Item::new_from_asset_expect( - "common.items.armor.misc.bag.reliable_backpack", - ); - let weapon = economy - .and_then(|e| e.unconsumed_stock.get(&Good::Tools)) - .copied() - .unwrap_or_default() - / 10.0; - if weapon > 0.0 { - for i in bag1.slots_mut() { - if let Some(item_id) = - TradePricing::random_item(Good::Tools, weapon, true) - { - *i = Some(Item::new_from_asset_expect(&item_id)); - } - } - } - let mut item_with_amount = |item_id: &str, amount: &mut f32| { - if *amount > 0.0 { - let mut item = Item::new_from_asset_expect(item_id); - // NOTE: Conversion to and from f32 works fine because we make sure the - // number we're converting is ≤ 100. - let max = amount.min(16.min(item.max_amount()) as f32) as u32; - let n = rng.gen_range(1..max.max(2)); - *amount -= if item.set_amount(n).is_ok() { - n as f32 - } else { - 1.0 - }; - Some(item) - } else { - None - } - }; - let mut bag2 = Item::new_from_asset_expect( - "common.items.armor.misc.bag.reliable_backpack", - ); - let mut ingredients = economy - .and_then(|e| e.unconsumed_stock.get(&Good::Ingredients)) - .copied() - .unwrap_or_default() - / 10.0; - for i in bag2.slots_mut() { - if let Some(item_id) = - TradePricing::random_item(Good::Ingredients, ingredients, true) - { - *i = item_with_amount(&item_id, &mut ingredients); - } - } - let mut bag3 = Item::new_from_asset_expect( - "common.items.armor.misc.bag.reliable_backpack", - ); - // TODO: currently econsim spends all its food on population, resulting in none - // for the players to buy; the `.max` is temporary to ensure that there's some - // food for sale at every site, to be used until we have some solution like NPC - // houses as a limit on econsim population growth - let mut food = economy - .and_then(|e| e.unconsumed_stock.get(&Good::Food)) - .copied() - .unwrap_or_default() - .max(10000.0) - / 10.0; - for i in bag3.slots_mut() { - if let Some(item_id) = TradePricing::random_item(Good::Food, food, true) { - *i = item_with_amount(&item_id, &mut food); - } - } - let mut bag4 = Item::new_from_asset_expect( - "common.items.armor.misc.bag.reliable_backpack", - ); - let mut potions = economy - .and_then(|e| e.unconsumed_stock.get(&Good::Potions)) - .copied() - .unwrap_or_default() - / 10.0; - for i in bag4.slots_mut() { - if let Some(item_id) = - TradePricing::random_item(Good::Potions, potions, true) - { - *i = item_with_amount(&item_id, &mut potions); - } - } - builder - .with_asset_expect("common.loadout.village.merchant", rng) - .back(Some(backpack)) - .bag(ArmorSlot::Bag1, Some(bag1)) - .bag(ArmorSlot::Bag2, Some(bag2)) - .bag(ArmorSlot::Bag3, Some(bag3)) - .bag(ArmorSlot::Bag4, Some(bag4)) - }, - } - } else { - Self::with_default_equipment(&body, active_item) - }; - - Self(loadout_builder.build()) - } - #[must_use] pub fn active_mainhand(mut self, item: Option) -> Self { self.0.swap(EquipSlot::ActiveMainhand, item); @@ -838,40 +660,13 @@ mod tests { use rand::thread_rng; use strum::IntoEnumIterator; - // Testing all configs in loadout with weapons of different toolkinds + // Testing all loadout presets // // Things that will be catched - invalid assets paths #[test] - fn test_loadout_configs() { - let test_weapons = vec![ - // Melee - "common.items.weapons.sword.starter", // Sword - "common.items.weapons.axe.starter_axe", // Axe - "common.items.weapons.hammer.starter_hammer", // Hammer - // Ranged - "common.items.weapons.bow.starter", // Bow - "common.items.weapons.staff.starter_staff", // Staff - "common.items.weapons.sceptre.starter_sceptre", // Sceptre - // Other - "common.items.weapons.dagger.starter_dagger", // Dagger - "common.items.weapons.shield.shield_1", // Shield - "common.items.npc_weapons.biped_small.sahagin.wooden_spear", // Spear - // Exotic - "common.items.npc_weapons.unique.beast_claws", // Natural - "common.items.weapons.tool.rake", // Farming - "common.items.tool.pickaxe_stone", // Pick - "common.items.weapons.empty.empty", // Empty - ]; - - for config in LoadoutConfig::iter() { - for test_weapon in &test_weapons { - std::mem::drop(LoadoutBuilder::build_loadout( - Body::Humanoid(comp::humanoid::Body::random()), - Some(Item::new_from_asset_expect(test_weapon)), - Some(config), - None, - )); - } + fn test_loadout_presets() { + for preset in LoadoutPreset::iter() { + std::mem::drop(LoadoutBuilder::default().with_preset(preset)); } } @@ -895,17 +690,11 @@ mod tests { body_type: comp::$species::BodyType::Male, ..body }; - std::mem::drop(LoadoutBuilder::build_loadout( - Body::$body(female_body), - None, - None, - None, + std::mem::drop(LoadoutBuilder::from_default( + &Body::$body(female_body), )); - std::mem::drop(LoadoutBuilder::build_loadout( - Body::$body(male_body), - None, - None, - None, + std::mem::drop(LoadoutBuilder::from_default( + &Body::$body(male_body), )); } }; diff --git a/common/src/generation.rs b/common/src/generation.rs index e91763659a..78e5726bbb 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -1,7 +1,12 @@ use crate::{ - comp::{self, humanoid, inventory::loadout_builder::LoadoutConfig, Alignment, Body, Item}, + comp::{ + self, agent, humanoid, + inventory::loadout_builder::{LoadoutBuilder, LoadoutPreset}, + Alignment, Body, Item, + }, npc::{self, NPC_NAMES}, skillset_builder::SkillSetConfig, + trade, trade::SiteInformation, }; use vek::*; @@ -17,6 +22,7 @@ pub struct EntityInfo { pub is_giant: bool, pub has_agency: bool, pub alignment: Alignment, + pub agent_mark: Option, pub body: Body, pub name: Option, pub main_tool: Option, @@ -25,13 +31,16 @@ pub struct EntityInfo { // TODO: Properly give NPCs skills pub level: Option, pub loot_drop: Option, + // FIXME: using both preset and asset is silly, make it enum + // so it will be correct by construction pub loadout_config: Option, - pub loadout_preset: Option, + pub loadout_preset: Option, + pub make_loadout: Option) -> LoadoutBuilder>, pub skillset_config: Option, pub skillset_preset: Option, pub pet: Option>, // we can't use DHashMap, do we want to move that into common? - pub trading_information: Option, + pub trading_information: Option, //Option>, /* price and available amount */ } @@ -43,6 +52,7 @@ impl EntityInfo { is_giant: false, has_agency: true, alignment: Alignment::Wild, + agent_mark: None, body: Body::Humanoid(humanoid::Body::random()), name: None, main_tool: None, @@ -52,6 +62,7 @@ impl EntityInfo { loot_drop: None, loadout_config: None, loadout_preset: None, + make_loadout: None, skillset_config: None, skillset_preset: None, pet: None, @@ -96,6 +107,11 @@ impl EntityInfo { self } + pub fn with_agent_mark(mut self, agent_mark: agent::Mark) -> Self { + self.agent_mark = Some(agent_mark); + self + } + pub fn with_main_tool(mut self, main_tool: Item) -> Self { self.main_tool = Some(main_tool); self @@ -121,18 +137,21 @@ impl EntityInfo { self } - pub fn with_loadout_config(mut self, config: String) -> Self { - self.loadout_config = Some(config); - self - } - - pub fn with_loadout_preset(mut self, preset: LoadoutConfig) -> Self { + pub fn with_loadout_preset(mut self, preset: LoadoutPreset) -> Self { self.loadout_preset = Some(preset); self } - pub fn with_skillset_config(mut self, config: String) -> Self { - self.skillset_config = Some(config); + pub fn with_loadout_config(mut self, config: &str) -> Self { + self.loadout_config = Some(config.to_owned()); + self + } + + pub fn with_lazy_loadout( + mut self, + creator: fn(LoadoutBuilder, Option<&trade::SiteInformation>) -> LoadoutBuilder, + ) -> Self { + self.make_loadout = Some(creator); self } @@ -141,6 +160,13 @@ impl EntityInfo { self } + // FIXME: Doesn't work for now, because skills can't be loaded from assets for + // now + pub fn with_skillset_config(mut self, config: String) -> Self { + self.skillset_config = Some(config); + self + } + pub fn with_automatic_name(mut self) -> Self { let npc_names = NPC_NAMES.read(); self.name = match &self.body { diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index 857ab191ed..5385dca55a 100644 --- a/common/src/states/basic_summon.rs +++ b/common/src/states/basic_summon.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ self, - inventory::loadout_builder::{LoadoutBuilder, LoadoutConfig}, + inventory::loadout_builder::{LoadoutBuilder, LoadoutPreset}, Behavior, BehaviorCapability, CharacterState, StateUpdate, }, event::{LocalEvent, ServerEvent}, @@ -76,13 +76,15 @@ impl CharacterBehavior for Data { { let body = self.static_data.summon_info.body; - let loadout = LoadoutBuilder::build_loadout( - body, - None, - self.static_data.summon_info.loadout_preset, - None, - ) - .build(); + let mut loadout_builder = + LoadoutBuilder::new().with_default_maintool(&body); + + if let Some(preset) = self.static_data.summon_info.loadout_preset { + loadout_builder = loadout_builder.with_preset(preset); + } + + let loadout = loadout_builder.build(); + let stats = comp::Stats::new("Summon".to_string()); let skill_set = SkillSetBuilder::build_skillset( &None, @@ -176,6 +178,6 @@ pub struct SummonInfo { scale: Option, health_scaling: u16, // TODO: use assets for specifying skills and loadouts? - loadout_preset: Option, + loadout_preset: Option, skillset_preset: Option, } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index a8f747e5b5..1ca186fbb3 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -1005,7 +1005,7 @@ fn handle_spawn( ); let body = body(); - let loadout = LoadoutBuilder::build_loadout(body, None, None, None).build(); + let loadout = LoadoutBuilder::from_default(&body).build(); let inventory = Inventory::new_with_loadout(loadout); let mut entity_base = server diff --git a/server/src/rtsim/entity.rs b/server/src/rtsim/entity.rs index 5405f10cea..eebcb404c6 100644 --- a/server/src/rtsim/entity.rs +++ b/server/src/rtsim/entity.rs @@ -78,7 +78,7 @@ impl Entity { pub fn get_loadout(&self) -> comp::inventory::loadout::Loadout { let mut rng = self.rng(PERM_LOADOUT); - LoadoutBuilder::from_asset_expect("common.loadout.world.traveler", &mut rng) + LoadoutBuilder::from_asset_expect("common.loadout.world.traveler", Some(&mut rng)) .bag( comp::inventory::slot::ArmorSlot::Bag1, Some(comp::inventory::loadout_builder::make_potion_bag(100)), diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index b352de9438..9925a5926a 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -3,12 +3,9 @@ use crate::{ presence::Presence, rtsim::RtSim, settings::Settings, SpawnPoint, Tick, }; use common::{ - comp::{ - self, bird_medium, inventory::loadout_builder::LoadoutConfig, Alignment, - BehaviorCapability, Pos, - }, + comp::{self, agent, bird_medium, Alignment, BehaviorCapability, Pos}, event::{EventBus, ServerEvent}, - generation::get_npc_name, + generation::{get_npc_name, EntityInfo}, npc::NPC_NAMES, terrain::TerrainGrid, LoadoutBuilder, SkillSetBuilder, @@ -178,7 +175,6 @@ impl<'a> System<'a> for Sys { let mut body = entity.body; let name = entity.name.unwrap_or_else(|| "Unnamed".to_string()); let alignment = entity.alignment; - let main_tool = entity.main_tool; let mut stats = comp::Stats::new(name); let mut scale = entity.scale; @@ -198,14 +194,53 @@ impl<'a> System<'a> for Sys { scale = 2.0 + rand::random::(); } - let loadout_config = entity.loadout_config; - let economy = entity.trading_information.as_ref(); - let skillset_config = entity.skillset_config; + let EntityInfo { + skillset_preset, + main_tool, + loadout_preset, + loadout_config, + make_loadout, + trading_information: economy, + .. + } = entity; let skill_set = - SkillSetBuilder::build_skillset(&main_tool, skillset_config).build(); - let loadout = - LoadoutBuilder::build_loadout(body, main_tool, loadout_config, economy).build(); + SkillSetBuilder::build_skillset(&main_tool, skillset_preset).build(); + + let mut loadout_builder = LoadoutBuilder::new(); + let rng = &mut rand::thread_rng(); + + // If main tool is passed, use it. Otherwise fallback to default tool + if let Some(main_tool) = main_tool { + loadout_builder = loadout_builder.active_mainhand(Some(main_tool)); + } else { + loadout_builder = loadout_builder.with_default_maintool(&body); + } + + // If there are configs, apply them + match (loadout_preset, &loadout_config) { + (Some(preset), Some(config)) => { + loadout_builder = loadout_builder.with_preset(preset); + loadout_builder = loadout_builder.with_asset_expect(&config, rng); + }, + (Some(preset), None) => { + loadout_builder = loadout_builder.with_preset(preset); + }, + (None, Some(config)) => { + loadout_builder = loadout_builder.with_asset_expect(&config, rng); + }, + // If not, use default equipement for this body + (None, None) => { + loadout_builder = loadout_builder.with_default_equipment(&body); + }, + } + + // Evaluate lazy function for loadout creation + if let Some(make_loadout) = make_loadout { + loadout_builder = loadout_builder.with_creator(make_loadout, economy.as_ref()); + } + + let loadout = loadout_builder.build(); let health = comp::Health::new(body, entity.level.unwrap_or(0)); let poise = comp::Poise::new(body); @@ -219,7 +254,7 @@ impl<'a> System<'a> for Sys { }, _ => false, }; - let trade_for_site = if matches!(loadout_config, Some(LoadoutConfig::Merchant)) { + let trade_for_site = if matches!(entity.agent_mark, Some(agent::Mark::Merchant)) { economy.map(|e| e.id) } else { None @@ -250,10 +285,7 @@ impl<'a> System<'a> for Sys { can_speak.then(|| BehaviorCapability::SPEAK), ) .with_trade_site(trade_for_site), - matches!( - loadout_config, - Some(comp::inventory::loadout_builder::LoadoutConfig::Guard) - ), + matches!(entity.agent_mark, Some(agent::Mark::Guard)), )) } else { None diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 266fba7ed4..c52ce446eb 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -11,10 +11,7 @@ use crate::{ use common::{ assets::{AssetExt, AssetHandle}, astar::Astar, - comp::{ - inventory::loadout_builder, - {self}, - }, + comp::{self}, generation::{ChunkSupplement, EntityInfo}, lottery::{LootSpec, Lottery}, store::{Id, Store}, @@ -936,8 +933,8 @@ fn enemy_0(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { ), )) .with_name("Gnarling") - .with_loadout_config(loadout_builder::LoadoutConfig::Gnarling) - .with_skillset_config(common::skillset_builder::SkillSetConfig::Gnarling) + .with_loadout_config("common.loadout.dungeon.tier-0.gnarling") + .with_skillset_preset(common::skillset_builder::SkillSetConfig::Gnarling) .with_loot_drop(chosen.read().choose().to_item()) .with_main_tool(comp::Item::new_from_asset_expect( match dynamic_rng.gen_range(0..5) { @@ -951,21 +948,31 @@ fn enemy_0(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { fn enemy_1(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-1.enemy"); - entity + let adlet = entity .with_body(comp::Body::BipedSmall( comp::biped_small::Body::random_with(dynamic_rng, &comp::biped_small::Species::Adlet), )) .with_name("Adlet") - .with_loadout_config(loadout_builder::LoadoutConfig::Adlet) - .with_skillset_config(common::skillset_builder::SkillSetConfig::Adlet) - .with_loot_drop(chosen.read().choose().to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..5) { - 0 => "common.items.npc_weapons.biped_small.adlet.adlet_bow", - 1 => "common.items.npc_weapons.biped_small.adlet.gnoll_staff", - _ => "common.items.npc_weapons.biped_small.adlet.wooden_spear", - }, - )) + .with_skillset_preset(common::skillset_builder::SkillSetConfig::Adlet) + .with_loot_drop(chosen.read().choose().to_item()); + + match dynamic_rng.gen_range(0..5) { + 0 => adlet + .with_main_tool(comp::Item::new_from_asset_expect( + "common.items.npc_weapons.biped_small.adlet.adlet_bow", + )) + .with_loadout_config("common.loadout.dungeon.tier-1.adlet_bow"), + 1 => adlet + .with_main_tool(comp::Item::new_from_asset_expect( + "common.items.npc_weapons.biped_small.adlet.adlet_staff", + )) + .with_loadout_config("common.loadout.dungeon.tier-1.adlet_spear"), + _ => adlet + .with_main_tool(comp::Item::new_from_asset_expect( + "common.items.npc_weapons.biped_small.adlet.adlet_spear", + )) + .with_loadout_config("common.loadout.dungeon.tier-1.adlet_spear"), + } } fn enemy_2(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { @@ -976,8 +983,8 @@ fn enemy_2(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { comp::biped_small::Body::random_with(dynamic_rng, &comp::biped_small::Species::Sahagin), )) .with_name("Sahagin") - .with_loadout_config(loadout_builder::LoadoutConfig::Sahagin) - .with_skillset_config(common::skillset_builder::SkillSetConfig::Sahagin) + .with_loadout_config("common.loadout.dungeon.tier-2.sahagin") + .with_skillset_preset(common::skillset_builder::SkillSetConfig::Sahagin) .with_loot_drop(chosen.read().choose().to_item()) .with_main_tool(comp::Item::new_from_asset_expect( match dynamic_rng.gen_range(0..5) { @@ -1005,8 +1012,8 @@ fn enemy_3(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { ), )) .with_name("Haniwa") - .with_loadout_config(loadout_builder::LoadoutConfig::Haniwa) - .with_skillset_config(common::skillset_builder::SkillSetConfig::Haniwa) + .with_loadout_config("common.loadout.dungeon.tier-3.haniwa") + .with_skillset_preset(common::skillset_builder::SkillSetConfig::Haniwa) .with_loot_drop(chosen.read().choose().to_item()) .with_main_tool(comp::Item::new_from_asset_expect( match dynamic_rng.gen_range(0..5) { @@ -1028,8 +1035,8 @@ fn enemy_4(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { ), )) .with_name("Myrmidon") - .with_loadout_config(loadout_builder::LoadoutConfig::Myrmidon) - .with_skillset_config(common::skillset_builder::SkillSetConfig::Myrmidon) + .with_loadout_config("common.loadout.dungeon.tier-4.myrmidon") + .with_skillset_preset(common::skillset_builder::SkillSetConfig::Myrmidon) .with_loot_drop(chosen.read().choose().to_item()) .with_main_tool(comp::Item::new_from_asset_expect( match dynamic_rng.gen_range(0..5) { @@ -1052,16 +1059,16 @@ fn enemy_5(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { 1 => entity .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) .with_name("Cultist Warlock") - .with_loadout_config(loadout_builder::LoadoutConfig::Warlock) - .with_skillset_config(common::skillset_builder::SkillSetConfig::Warlock) + .with_loadout_config("common.loadout.dungeon.tier-5.warlock") + .with_skillset_preset(common::skillset_builder::SkillSetConfig::Warlock) .with_loot_drop(chosen.read().choose().to_item()) .with_main_tool(comp::Item::new_from_asset_expect( "common.items.weapons.staff.cultist_staff", )), _ => entity .with_name("Cultist Warlord") - .with_loadout_config(loadout_builder::LoadoutConfig::Warlord) - .with_skillset_config(common::skillset_builder::SkillSetConfig::Warlord) + .with_loadout_config("common.loadout.dungeon.tier-5.warlord") + .with_skillset_preset(common::skillset_builder::SkillSetConfig::Warlord) .with_loot_drop(chosen.read().choose().to_item()) .with_main_tool(comp::Item::new_from_asset_expect( match dynamic_rng.gen_range(0..6) { @@ -1176,7 +1183,7 @@ fn boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec) -> Vec "common.items.weapons.axe.malachite_axe-0", @@ -1321,7 +1328,7 @@ fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec entity @@ -960,27 +967,28 @@ impl Settlement { "common.items.weapons.bow.eldwood-0", )) .with_name("Merchant") + .with_agent_mark(agent::Mark::Merchant) + .with_economy(&economy) + .with_lazy_loadout(merchant_loadout) .with_level(dynamic_rng.gen_range(10..15)) - .with_loadout_config(loadout_builder::LoadoutConfig::Merchant) - .with_skillset_config( + .with_skillset_preset( common::skillset_builder::SkillSetConfig::Merchant, - ) - .with_economy(&economy), + ), _ => entity .with_main_tool(Item::new_from_asset_expect( match dynamic_rng.gen_range(0..7) { - 0 => "common.items.weapons.tool.broom", - 1 => "common.items.weapons.tool.hoe", - 2 => "common.items.weapons.tool.pickaxe", - 3 => "common.items.weapons.tool.pitchfork", - 4 => "common.items.weapons.tool.rake", - 5 => "common.items.weapons.tool.shovel-0", - _ => "common.items.weapons.tool.shovel-1", - //_ => "common.items.weapons.bow.starter", TODO: Re-Add this when we have a better way of distributing npc_weapons here - }, + 0 => "common.items.weapons.tool.broom", + 1 => "common.items.weapons.tool.hoe", + 2 => "common.items.weapons.tool.pickaxe", + 3 => "common.items.weapons.tool.pitchfork", + 4 => "common.items.weapons.tool.rake", + 5 => "common.items.weapons.tool.shovel-0", + _ => "common.items.weapons.tool.shovel-1", + //_ => "common.items.weapons.bow.starter", TODO: Re-Add this when we have a better way of distributing npc_weapons here + }, )) - .with_loadout_config(loadout_builder::LoadoutConfig::Villager) - .with_skillset_config( + .with_lazy_loadout(villager_loadout) + .with_skillset_preset( common::skillset_builder::SkillSetConfig::Villager, ), } @@ -1043,6 +1051,137 @@ impl Settlement { } } +fn merchant_loadout( + loadout_builder: LoadoutBuilder, + economy: Option<&trade::SiteInformation>, +) -> LoadoutBuilder { + let rng = &mut rand::thread_rng(); + let mut backpack = Item::new_from_asset_expect("common.items.armor.misc.back.backpack"); + let mut coins = economy + .and_then(|e| e.unconsumed_stock.get(&Good::Coin)) + .copied() + .unwrap_or_default() + .round() + .min(rand::thread_rng().gen_range(1000.0..3000.0)) as u32; + let armor = economy + .and_then(|e| e.unconsumed_stock.get(&Good::Armor)) + .copied() + .unwrap_or_default() + / 10.0; + for s in backpack.slots_mut() { + if coins > 0 { + let mut coin_item = Item::new_from_asset_expect("common.items.utility.coins"); + coin_item + .set_amount(coins) + .expect("coins should be stackable"); + *s = Some(coin_item); + coins = 0; + } else if armor > 0.0 { + if let Some(item_id) = TradePricing::random_item(Good::Armor, armor, true) { + *s = Some(Item::new_from_asset_expect(&item_id)); + } + } + } + let mut bag1 = Item::new_from_asset_expect("common.items.armor.misc.bag.reliable_backpack"); + let weapon = economy + .and_then(|e| e.unconsumed_stock.get(&Good::Tools)) + .copied() + .unwrap_or_default() + / 10.0; + if weapon > 0.0 { + for i in bag1.slots_mut() { + if let Some(item_id) = TradePricing::random_item(Good::Tools, weapon, true) { + *i = Some(Item::new_from_asset_expect(&item_id)); + } + } + } + let mut item_with_amount = |item_id: &str, amount: &mut f32| { + if *amount > 0.0 { + let mut item = Item::new_from_asset_expect(item_id); + // NOTE: Conversion to and from f32 works fine because we make sure the + // number we're converting is ≤ 100. + let max = amount.min(16.min(item.max_amount()) as f32) as u32; + let n = rng.gen_range(1..max.max(2)); + *amount -= if item.set_amount(n).is_ok() { + n as f32 + } else { + 1.0 + }; + Some(item) + } else { + None + } + }; + let mut bag2 = Item::new_from_asset_expect("common.items.armor.misc.bag.reliable_backpack"); + let mut ingredients = economy + .and_then(|e| e.unconsumed_stock.get(&Good::Ingredients)) + .copied() + .unwrap_or_default() + / 10.0; + for i in bag2.slots_mut() { + if let Some(item_id) = TradePricing::random_item(Good::Ingredients, ingredients, true) { + *i = item_with_amount(&item_id, &mut ingredients); + } + } + let mut bag3 = Item::new_from_asset_expect("common.items.armor.misc.bag.reliable_backpack"); + // TODO: currently econsim spends all its food on population, resulting in none + // for the players to buy; the `.max` is temporary to ensure that there's some + // food for sale at every site, to be used until we have some solution like NPC + // houses as a limit on econsim population growth + let mut food = economy + .and_then(|e| e.unconsumed_stock.get(&Good::Food)) + .copied() + .unwrap_or_default() + .max(10000.0) + / 10.0; + for i in bag3.slots_mut() { + if let Some(item_id) = TradePricing::random_item(Good::Food, food, true) { + *i = item_with_amount(&item_id, &mut food); + } + } + let mut bag4 = Item::new_from_asset_expect("common.items.armor.misc.bag.reliable_backpack"); + let mut potions = economy + .and_then(|e| e.unconsumed_stock.get(&Good::Potions)) + .copied() + .unwrap_or_default() + / 10.0; + for i in bag4.slots_mut() { + if let Some(item_id) = TradePricing::random_item(Good::Potions, potions, true) { + *i = item_with_amount(&item_id, &mut potions); + } + } + + loadout_builder + .with_asset_expect("common.loadout.village.merchant", rng) + .back(Some(backpack)) + .bag(ArmorSlot::Bag1, Some(bag1)) + .bag(ArmorSlot::Bag2, Some(bag2)) + .bag(ArmorSlot::Bag3, Some(bag3)) + .bag(ArmorSlot::Bag4, Some(bag4)) +} + +fn guard_loadout( + loadout_builder: LoadoutBuilder, + _economy: Option<&trade::SiteInformation>, +) -> LoadoutBuilder { + let rng = &mut rand::thread_rng(); + + loadout_builder + .with_asset_expect("common.loadout.village.guard", rng) + .bag(ArmorSlot::Bag1, Some(make_potion_bag(25))) +} + +fn villager_loadout( + loadout_builder: LoadoutBuilder, + _economy: Option<&trade::SiteInformation>, +) -> LoadoutBuilder { + let rng = &mut rand::thread_rng(); + + loadout_builder + .with_asset_expect("common.loadout.village.villager", rng) + .bag(ArmorSlot::Bag1, Some(make_potion_bag(10))) +} + #[derive(Copy, Clone, PartialEq)] pub enum Crop { Corn, From f5bf991eb0ddb0a97689f8dbf2e12bb6dbb008d2 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sun, 6 Jun 2021 18:55:04 +0300 Subject: [PATCH 06/37] Start to load EntityInfo from assets in dungeons * All enemies in dungeons are now specify loadout_config, name and main_tool in assets * Add more variance to the enemies names --- assets/common/entity/dungeon/tier-0/bow.ron | 8 ++ assets/common/entity/dungeon/tier-0/spear.ron | 8 ++ assets/common/entity/dungeon/tier-0/staff.ron | 8 ++ assets/common/entity/dungeon/tier-1/bow.ron | 8 ++ assets/common/entity/dungeon/tier-1/spear.ron | 8 ++ assets/common/entity/dungeon/tier-1/staff.ron | 8 ++ assets/common/entity/dungeon/tier-2/bow.ron | 8 ++ assets/common/entity/dungeon/tier-2/spear.ron | 8 ++ assets/common/entity/dungeon/tier-2/staff.ron | 8 ++ assets/common/entity/dungeon/tier-3/bow.ron | 8 ++ assets/common/entity/dungeon/tier-3/spear.ron | 8 ++ assets/common/entity/dungeon/tier-3/staff.ron | 8 ++ assets/common/entity/dungeon/tier-4/bow.ron | 8 ++ assets/common/entity/dungeon/tier-4/spear.ron | 8 ++ assets/common/entity/dungeon/tier-4/staff.ron | 8 ++ .../common/entity/dungeon/tier-5/warlock.ron | 8 ++ .../common/entity/dungeon/tier-5/warlord.ron | 14 ++ assets/common/entity/test.ron | 18 +-- common/src/comp/inventory/loadout_builder.rs | 6 +- common/src/generation.rs | 92 +++++++++++- world/src/site/dungeon/mod.rs | 132 +++++++----------- 21 files changed, 292 insertions(+), 98 deletions(-) create mode 100644 assets/common/entity/dungeon/tier-0/bow.ron create mode 100644 assets/common/entity/dungeon/tier-0/spear.ron create mode 100644 assets/common/entity/dungeon/tier-0/staff.ron create mode 100644 assets/common/entity/dungeon/tier-1/bow.ron create mode 100644 assets/common/entity/dungeon/tier-1/spear.ron create mode 100644 assets/common/entity/dungeon/tier-1/staff.ron create mode 100644 assets/common/entity/dungeon/tier-2/bow.ron create mode 100644 assets/common/entity/dungeon/tier-2/spear.ron create mode 100644 assets/common/entity/dungeon/tier-2/staff.ron create mode 100644 assets/common/entity/dungeon/tier-3/bow.ron create mode 100644 assets/common/entity/dungeon/tier-3/spear.ron create mode 100644 assets/common/entity/dungeon/tier-3/staff.ron create mode 100644 assets/common/entity/dungeon/tier-4/bow.ron create mode 100644 assets/common/entity/dungeon/tier-4/spear.ron create mode 100644 assets/common/entity/dungeon/tier-4/staff.ron create mode 100644 assets/common/entity/dungeon/tier-5/warlock.ron create mode 100644 assets/common/entity/dungeon/tier-5/warlord.ron diff --git a/assets/common/entity/dungeon/tier-0/bow.ron b/assets/common/entity/dungeon/tier-0/bow.ron new file mode 100644 index 0000000000..0ba5a289d6 --- /dev/null +++ b/assets/common/entity/dungeon/tier-0/bow.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Gnarling Stalker"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.adlet_bow")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-0.gnarling"), +) diff --git a/assets/common/entity/dungeon/tier-0/spear.ron b/assets/common/entity/dungeon/tier-0/spear.ron new file mode 100644 index 0000000000..ffc2e9cf4f --- /dev/null +++ b/assets/common/entity/dungeon/tier-0/spear.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Gnarling Mugger"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.wooden_spear")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-0.gnarling"), +) diff --git a/assets/common/entity/dungeon/tier-0/staff.ron b/assets/common/entity/dungeon/tier-0/staff.ron new file mode 100644 index 0000000000..b0b6a7a751 --- /dev/null +++ b/assets/common/entity/dungeon/tier-0/staff.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Gnarling Shaman"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.gnoll_staff")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-0.gnarling"), +) diff --git a/assets/common/entity/dungeon/tier-1/bow.ron b/assets/common/entity/dungeon/tier-1/bow.ron new file mode 100644 index 0000000000..fde472fdb5 --- /dev/null +++ b/assets/common/entity/dungeon/tier-1/bow.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Adlet Tracker"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.adlet_bow")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-1.adlet_bow"), +) diff --git a/assets/common/entity/dungeon/tier-1/spear.ron b/assets/common/entity/dungeon/tier-1/spear.ron new file mode 100644 index 0000000000..48a07585be --- /dev/null +++ b/assets/common/entity/dungeon/tier-1/spear.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Adlet Hunter"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.wooden_spear")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-1.adlet_spear"), +) diff --git a/assets/common/entity/dungeon/tier-1/staff.ron b/assets/common/entity/dungeon/tier-1/staff.ron new file mode 100644 index 0000000000..820c12436c --- /dev/null +++ b/assets/common/entity/dungeon/tier-1/staff.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Adlet Shaman"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.gnoll_staff")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-1.adlet_spear"), +) diff --git a/assets/common/entity/dungeon/tier-2/bow.ron b/assets/common/entity/dungeon/tier-2/bow.ron new file mode 100644 index 0000000000..8ea21fa08c --- /dev/null +++ b/assets/common/entity/dungeon/tier-2/bow.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Sahagin Sniper"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.adlet_bow")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-2.sahagin"), +) diff --git a/assets/common/entity/dungeon/tier-2/spear.ron b/assets/common/entity/dungeon/tier-2/spear.ron new file mode 100644 index 0000000000..bd8d7c9e4e --- /dev/null +++ b/assets/common/entity/dungeon/tier-2/spear.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Sahagin Spearman"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.wooden_spear")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-2.sahagin"), +) diff --git a/assets/common/entity/dungeon/tier-2/staff.ron b/assets/common/entity/dungeon/tier-2/staff.ron new file mode 100644 index 0000000000..73cb84ee55 --- /dev/null +++ b/assets/common/entity/dungeon/tier-2/staff.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Sahagin Sorcerer"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.gnoll_staff")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-2.sahagin"), +) diff --git a/assets/common/entity/dungeon/tier-3/bow.ron b/assets/common/entity/dungeon/tier-3/bow.ron new file mode 100644 index 0000000000..d1a9d98924 --- /dev/null +++ b/assets/common/entity/dungeon/tier-3/bow.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Haniwa Archer"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.adlet_bow")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-3.haniwa"), +) diff --git a/assets/common/entity/dungeon/tier-3/spear.ron b/assets/common/entity/dungeon/tier-3/spear.ron new file mode 100644 index 0000000000..5e303990af --- /dev/null +++ b/assets/common/entity/dungeon/tier-3/spear.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Haniwa Guard"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.wooden_spear")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-3.haniwa"), +) diff --git a/assets/common/entity/dungeon/tier-3/staff.ron b/assets/common/entity/dungeon/tier-3/staff.ron new file mode 100644 index 0000000000..ef130792c0 --- /dev/null +++ b/assets/common/entity/dungeon/tier-3/staff.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Haniwa Sorcerer"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.gnoll_staff")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-3.haniwa"), +) diff --git a/assets/common/entity/dungeon/tier-4/bow.ron b/assets/common/entity/dungeon/tier-4/bow.ron new file mode 100644 index 0000000000..7a9006fb5a --- /dev/null +++ b/assets/common/entity/dungeon/tier-4/bow.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Myrmidon Marksman"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.adlet_bow")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-4.myrmidon"), +) diff --git a/assets/common/entity/dungeon/tier-4/spear.ron b/assets/common/entity/dungeon/tier-4/spear.ron new file mode 100644 index 0000000000..3ae610eb7e --- /dev/null +++ b/assets/common/entity/dungeon/tier-4/spear.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Myrmidon Hoplite"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.wooden_spear")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-4.myrmidon"), +) diff --git a/assets/common/entity/dungeon/tier-4/staff.ron b/assets/common/entity/dungeon/tier-4/staff.ron new file mode 100644 index 0000000000..23d0521872 --- /dev/null +++ b/assets/common/entity/dungeon/tier-4/staff.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Myrmidon Wizard"), + + main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.gnoll_staff")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-4.myrmidon"), +) diff --git a/assets/common/entity/dungeon/tier-5/warlock.ron b/assets/common/entity/dungeon/tier-5/warlock.ron new file mode 100644 index 0000000000..f151381032 --- /dev/null +++ b/assets/common/entity/dungeon/tier-5/warlock.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Cultist Warlock"), + + main_tool: Some(Item("common.items.weapons.staff.cultist_staff")), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-5.warlock"), +) diff --git a/assets/common/entity/dungeon/tier-5/warlord.ron b/assets/common/entity/dungeon/tier-5/warlord.ron new file mode 100644 index 0000000000..8369c3f29c --- /dev/null +++ b/assets/common/entity/dungeon/tier-5/warlord.ron @@ -0,0 +1,14 @@ +EntityConfig ( + name: Some("Cultist Warlord"), + + main_tool: Some(Choice([ + (1.0, Some(Item("common.items.weapons.axe_1h.orichalcum-0"))), + (2.0, Some(Item("common.items.weapons.sword.cultist"))), + (1.0, Some(Item("common.items.weapons.hammer.cultist_purp_2h-0"))), + (1.0, Some(Item("common.items.weapons.hammer_1h.orichalcum-0"))), + (1.0, Some(Item("common.items.weapons.bow.velorite"))), + ])), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-5.warlord"), +) diff --git a/assets/common/entity/test.ron b/assets/common/entity/test.ron index 2a70be337c..040cc162b2 100644 --- a/assets/common/entity/test.ron +++ b/assets/common/entity/test.ron @@ -1,4 +1,4 @@ -{ +EntityConfig ( /// Name of Entity name: Some("Paddy"), @@ -8,11 +8,6 @@ /// or RandomWith (will use random_with if available for this Body) // body: Humanoid(Random), - /// Loot - /// Can be Item (with asset_specifier for item) - /// or LootTable (with asset_specifier for loot table) - loot: LootTable("common.loot_tables.humanoids"), - /// Main and second tools /// Can be Option (with asset_specifier for item) /// or Choice @@ -20,12 +15,17 @@ main_tool: Some(Item("common.items.weapons.axe_1h.orichalcum-0")), second_tool: None, - /// Loadout Config as Option (with asset_specifier for loadout) - loadout_config: Some(Loadout("common.loadout.village.merchant")), + /// Loadout Config (with asset_specifier for loadout) + loadout_config: Some("common.loadout.village.merchant"), /// Skillset Config as Option (with asset_specifier for skillset) // skillset_config: None, + /// Loot + /// Can be Item (with asset_specifier for item) + /// or LootTable (with asset_specifier for loot table) + // loot: LootTable("common.loot_tables.humanoids"), + /// Meta Info (level, alignment, agency, etc) // meta: {}, -} +) diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 8e49b3c4ae..fff159b3fe 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -46,7 +46,7 @@ pub enum LoadoutPreset { } #[derive(Debug, Deserialize, Clone)] -enum ItemSpec { +pub enum ItemSpec { /// One specific item. /// Example: /// Item("common.items.armor.steel.foot") @@ -61,7 +61,7 @@ enum ItemSpec { } impl ItemSpec { - fn try_to_item(&self, asset_specifier: &str, rng: &mut impl Rng) -> Option { + pub fn try_to_item(&self, asset_specifier: &str, rng: &mut impl Rng) -> Option { match self { ItemSpec::Item(specifier) => Some(Item::new_from_asset_expect(&specifier)), @@ -80,7 +80,7 @@ impl ItemSpec { #[cfg(test)] // Read everything and checks if it's loading - fn validate(&self, key: EquipSlot) { + pub fn validate(&self, key: EquipSlot) { match self { ItemSpec::Item(specifier) => std::mem::drop(Item::new_from_asset_expect(&specifier)), ItemSpec::Choice(items) => { diff --git a/common/src/generation.rs b/common/src/generation.rs index 78e5726bbb..79959c5552 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -1,7 +1,8 @@ use crate::{ + assets::{self, AssetExt}, comp::{ self, agent, humanoid, - inventory::loadout_builder::{LoadoutBuilder, LoadoutPreset}, + inventory::loadout_builder::{ItemSpec, LoadoutBuilder, LoadoutPreset}, Alignment, Body, Item, }, npc::{self, NPC_NAMES}, @@ -9,10 +10,21 @@ use crate::{ trade, trade::SiteInformation, }; +use serde::Deserialize; use vek::*; -pub enum EntityTemplate { - Traveller, +#[derive(Debug, Deserialize, Clone)] +struct EntityConfig { + name: Option, + main_tool: Option, + second_tool: Option, + loadout_config: Option, +} + +impl assets::Asset for EntityConfig { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; } #[derive(Clone)] @@ -70,6 +82,44 @@ impl EntityInfo { } } + pub fn with_asset_expect(self, asset_specifier: &str) -> Self { + let config = EntityConfig::load_expect(asset_specifier).read().clone(); + + self.with_entity_config(config, Some(asset_specifier)) + } + + // helper function to apply config + fn with_entity_config(mut self, config: EntityConfig, asset_specifier: Option<&str>) -> Self { + let EntityConfig { + name, + main_tool, + second_tool, + loadout_config, + } = config; + + if let Some(name) = name { + self = self.with_name(name); + } + + let rng = &mut rand::thread_rng(); + if let Some(main_tool) = + main_tool.and_then(|i| i.try_to_item(asset_specifier.unwrap_or("??"), rng)) + { + self = self.with_main_tool(main_tool); + } + if let Some(second_tool) = + second_tool.and_then(|i| i.try_to_item(asset_specifier.unwrap_or("??"), rng)) + { + self = self.with_main_tool(second_tool); + } + + if let Some(loadout_config) = loadout_config { + self = self.with_loadout_config(&loadout_config); + } + + self + } + pub fn do_if(mut self, cond: bool, f: impl FnOnce(Self) -> Self) -> Self { if cond { self = f(self); @@ -224,3 +274,39 @@ pub fn get_npc_name< ) -> &'a str { &body_data.species[&species].generic } + +#[cfg(test)] +mod tests { + use super::*; + use assets::Error; + + #[test] + fn test_all_entity_assets() { + #[derive(Clone)] + struct EntityList(Vec); + impl assets::Compound for EntityList { + fn load( + cache: &assets::AssetCache, + specifier: &str, + ) -> Result { + let list = cache + .load::(specifier)? + .read() + .iter() + .map(|spec| EntityConfig::load_cloned(spec)) + .collect::>()?; + + Ok(Self(list)) + } + } + + // It just load everything that could + // TODO: add some checks, e.g. that Armor(Head) key correspond + // to Item with ItemKind Head(_) + let entity_configs = EntityList::load_expect_cloned("common.entity.*").0; + for config in entity_configs { + let pos = Vec3::new(0.0, 0.0, 0.0); + std::mem::drop(EntityInfo::at(pos).with_entity_config(config, None)); + } + } +} diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index c52ce446eb..86b8401b06 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -925,24 +925,21 @@ impl Floor { fn enemy_0(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-0.enemy"); - entity + let gnarling = entity .with_body(comp::Body::BipedSmall( comp::biped_small::Body::random_with( dynamic_rng, &comp::biped_small::Species::Gnarling, ), )) - .with_name("Gnarling") - .with_loadout_config("common.loadout.dungeon.tier-0.gnarling") - .with_skillset_preset(common::skillset_builder::SkillSetConfig::Gnarling) .with_loot_drop(chosen.read().choose().to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..5) { - 0 => "common.items.npc_weapons.biped_small.gnarling.adlet_bow", - 1 => "common.items.npc_weapons.biped_small.gnarling.gnoll_staff", - _ => "common.items.npc_weapons.biped_small.gnarling.wooden_spear", - }, - )) + .with_skillset_preset(common::skillset_builder::SkillSetConfig::Gnarling); + + match dynamic_rng.gen_range(0..5) { + 0 => gnarling.with_asset_expect("common.entity.dungeon.tier-0.bow"), + 1 => gnarling.with_asset_expect("common.entity.dungeon.tier-0.staff"), + _ => gnarling.with_asset_expect("common.entity.dungeon.tier-0.spear"), + } } fn enemy_1(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { @@ -952,48 +949,33 @@ fn enemy_1(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { .with_body(comp::Body::BipedSmall( comp::biped_small::Body::random_with(dynamic_rng, &comp::biped_small::Species::Adlet), )) - .with_name("Adlet") .with_skillset_preset(common::skillset_builder::SkillSetConfig::Adlet) .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { - 0 => adlet - .with_main_tool(comp::Item::new_from_asset_expect( - "common.items.npc_weapons.biped_small.adlet.adlet_bow", - )) - .with_loadout_config("common.loadout.dungeon.tier-1.adlet_bow"), - 1 => adlet - .with_main_tool(comp::Item::new_from_asset_expect( - "common.items.npc_weapons.biped_small.adlet.adlet_staff", - )) - .with_loadout_config("common.loadout.dungeon.tier-1.adlet_spear"), - _ => adlet - .with_main_tool(comp::Item::new_from_asset_expect( - "common.items.npc_weapons.biped_small.adlet.adlet_spear", - )) - .with_loadout_config("common.loadout.dungeon.tier-1.adlet_spear"), + 0 => adlet.with_asset_expect("common.entity.dungeon.tier-1.bow"), + 1 => adlet.with_asset_expect("common.entity.dungeon.tier-1.staff"), + _ => adlet.with_asset_expect("common.entity.dungeon.tier-1.spear"), } } fn enemy_2(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-2.enemy"); - entity + let sahagin = entity .with_body(comp::Body::BipedSmall( comp::biped_small::Body::random_with(dynamic_rng, &comp::biped_small::Species::Sahagin), )) - .with_name("Sahagin") - .with_loadout_config("common.loadout.dungeon.tier-2.sahagin") .with_skillset_preset(common::skillset_builder::SkillSetConfig::Sahagin) - .with_loot_drop(chosen.read().choose().to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..5) { - 0 => "common.items.npc_weapons.biped_small.sahagin.adlet_bow", - 1 => "common.items.npc_weapons.biped_small.sahagin.gnoll_staff", - _ => "common.items.npc_weapons.biped_small.sahagin.wooden_spear", - }, - )) + .with_loot_drop(chosen.read().choose().to_item()); + + match dynamic_rng.gen_range(0..5) { + 0 => sahagin.with_asset_expect("common.entity.dungeon.tier-2.bow"), + 1 => sahagin.with_asset_expect("common.entity.dungeon.tier-2.staff"), + _ => sahagin.with_asset_expect("common.entity.dungeon.tier-2.spear"), + } } + fn enemy_3(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-3.enemy"); @@ -1004,48 +986,45 @@ fn enemy_3(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { .with_loot_drop(comp::Item::new_from_asset_expect( "common.items.crafting_ing.stones", )), - _ => entity - .with_body(comp::Body::BipedSmall( - comp::biped_small::Body::random_with( - dynamic_rng, - &comp::biped_small::Species::Haniwa, - ), - )) - .with_name("Haniwa") - .with_loadout_config("common.loadout.dungeon.tier-3.haniwa") - .with_skillset_preset(common::skillset_builder::SkillSetConfig::Haniwa) - .with_loot_drop(chosen.read().choose().to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..5) { - 0 => "common.items.npc_weapons.biped_small.haniwa.adlet_bow", - 1 => "common.items.npc_weapons.biped_small.haniwa.gnoll_staff", - _ => "common.items.npc_weapons.biped_small.haniwa.wooden_spear", - }, - )), + _ => { + let haniwa = entity + .with_body(comp::Body::BipedSmall( + comp::biped_small::Body::random_with( + dynamic_rng, + &comp::biped_small::Species::Haniwa, + ), + )) + .with_skillset_preset(common::skillset_builder::SkillSetConfig::Haniwa) + .with_loot_drop(chosen.read().choose().to_item()); + + match dynamic_rng.gen_range(0..5) { + 0 => haniwa.with_asset_expect("common.entity.dungeon.tier-3.bow"), + 1 => haniwa.with_asset_expect("common.entity.dungeon.tier-3.staff"), + _ => haniwa.with_asset_expect("common.entity.dungeon.tier-3.spear"), + } + }, } } fn enemy_4(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-4.enemy"); - entity + let myrmidon = entity .with_body(comp::Body::BipedSmall( comp::biped_small::Body::random_with( dynamic_rng, &comp::biped_small::Species::Myrmidon, ), )) - .with_name("Myrmidon") - .with_loadout_config("common.loadout.dungeon.tier-4.myrmidon") .with_skillset_preset(common::skillset_builder::SkillSetConfig::Myrmidon) - .with_loot_drop(chosen.read().choose().to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..5) { - 0 => "common.items.npc_weapons.biped_small.myrmidon.adlet_bow", - 1 => "common.items.npc_weapons.biped_small.myrmidon.gnoll_staff", - _ => "common.items.npc_weapons.biped_small.myrmidon.wooden_spear", - }, - )) + .with_loot_drop(chosen.read().choose().to_item()); + + match dynamic_rng.gen_range(0..5) { + 0 => myrmidon.with_asset_expect("common.entity.dungeon.tier-4.bow"), + 1 => myrmidon.with_asset_expect("common.entity.dungeon.tier-4.staff"), + _ => myrmidon.with_asset_expect("common.entity.dungeon.tier-4.spear"), + } } + fn enemy_5(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-5.enemy"); @@ -1058,27 +1037,14 @@ fn enemy_5(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { )), 1 => entity .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) - .with_name("Cultist Warlock") - .with_loadout_config("common.loadout.dungeon.tier-5.warlock") .with_skillset_preset(common::skillset_builder::SkillSetConfig::Warlock) .with_loot_drop(chosen.read().choose().to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - "common.items.weapons.staff.cultist_staff", - )), + .with_asset_expect("common.entity.dungeon.tier-5.warlock"), _ => entity - .with_name("Cultist Warlord") - .with_loadout_config("common.loadout.dungeon.tier-5.warlord") + .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) .with_skillset_preset(common::skillset_builder::SkillSetConfig::Warlord) .with_loot_drop(chosen.read().choose().to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..6) { - 0 => "common.items.weapons.axe_1h.orichalcum-0", - 1..=2 => "common.items.weapons.sword.cultist", - 3 => "common.items.weapons.hammer.cultist_purp_2h-0", - 4 => "common.items.weapons.hammer_1h.orichalcum-0", - _ => "common.items.weapons.bow.bone-1", - }, - )), + .with_asset_expect("common.entity.dungeon.tier-5.warlord"), } } From 171c66d53d7af45d8f15ebea17e2c45cf33de2a8 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sun, 6 Jun 2021 20:38:17 +0300 Subject: [PATCH 07/37] docs --- common/src/comp/inventory/loadout_builder.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index fff159b3fe..6382dd393f 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -79,7 +79,11 @@ impl ItemSpec { } #[cfg(test)] - // Read everything and checks if it's loading + /// # Usage + /// Read everything and checks if it's loading + /// + /// # Panics + /// 1) If weights are invalid pub fn validate(&self, key: EquipSlot) { match self { ItemSpec::Item(specifier) => std::mem::drop(Item::new_from_asset_expect(&specifier)), From 45e5554ff51f9ffc704f80ba6d55cc4e636db1e5 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sun, 6 Jun 2021 21:47:12 +0300 Subject: [PATCH 08/37] Fix issue with villagers not having any item --- common/src/comp/inventory/loadout_builder.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 6382dd393f..4bf4cab6ff 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -159,8 +159,8 @@ pub fn make_potion_bag(quantity: u32) -> Item { // Also we are using default tools for un-specified species so // it's fine to have wildcards #[allow(clippy::too_many_lines, clippy::match_wildcard_for_single_variants)] -fn default_main_tool(body: &Body) -> Option { - match body { +fn default_main_tool(body: &Body) -> Item { + let maybe_tool = match body { Body::Golem(golem) => match golem.species { golem::Species::StoneGolem => Some(Item::new_from_asset_expect( "common.items.npc_weapons.unique.stone_golems_fist", @@ -332,7 +332,9 @@ fn default_main_tool(body: &Body) -> Option { )), }, _ => None, - } + }; + + maybe_tool.unwrap_or_else(Item::empty) } impl Default for LoadoutBuilder { @@ -374,7 +376,7 @@ impl LoadoutBuilder { #[must_use] /// Set default active mainhand weapon based on `body` pub fn with_default_maintool(self, body: &Body) -> Self { - self.active_mainhand(default_main_tool(body)) + self.active_mainhand(Some(default_main_tool(body))) } #[must_use] From c3a120c551be74970d0b9e09205dfad63dc42f6b Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Mon, 7 Jun 2021 01:54:15 +0300 Subject: [PATCH 09/37] Post refactoring --- .../custom/mindflayer/summonminions.ron | 4 ++-- .../abilities/custom/tidalwarrior/totem.ron | 2 +- .../entity/dungeon/tier-5/beastmaster.ron | 12 +++++++++++ assets/common/entity/dungeon/tier-5/husk.ron | 8 ++++++++ common/src/comp/inventory/loadout_builder.rs | 10 +++++----- common/src/generation.rs | 17 ++++------------ common/src/states/basic_summon.rs | 12 +++++------ server/src/sys/terrain.rs | 20 ++++++------------- world/src/site/dungeon/mod.rs | 13 ++---------- 9 files changed, 46 insertions(+), 52 deletions(-) create mode 100644 assets/common/entity/dungeon/tier-5/beastmaster.ron create mode 100644 assets/common/entity/dungeon/tier-5/husk.ron diff --git a/assets/common/abilities/custom/mindflayer/summonminions.ron b/assets/common/abilities/custom/mindflayer/summonminions.ron index d26ecb51a4..99117ce2c0 100644 --- a/assets/common/abilities/custom/mindflayer/summonminions.ron +++ b/assets/common/abilities/custom/mindflayer/summonminions.ron @@ -10,7 +10,7 @@ BasicSummon( )), scale: None, health_scaling: 80, - loadout_config: Some(Husk), + loadout_config: Some(HuskSummon), skillset_config: None, ), -) \ No newline at end of file +) diff --git a/assets/common/abilities/custom/tidalwarrior/totem.ron b/assets/common/abilities/custom/tidalwarrior/totem.ron index 343f0e4c2d..31df64a7cd 100644 --- a/assets/common/abilities/custom/tidalwarrior/totem.ron +++ b/assets/common/abilities/custom/tidalwarrior/totem.ron @@ -10,4 +10,4 @@ BasicSummon( loadout_config: None, skillset_config: None, ), -) \ No newline at end of file +) diff --git a/assets/common/entity/dungeon/tier-5/beastmaster.ron b/assets/common/entity/dungeon/tier-5/beastmaster.ron new file mode 100644 index 0000000000..c34e9bc99e --- /dev/null +++ b/assets/common/entity/dungeon/tier-5/beastmaster.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Beastmaster"), + + main_tool: Some(Choice([ + (1.0, Some(Item("common.items.weapons.axe.malachite_axe-0"))), + (1.0, Some(Item("common.items.weapons.sword.bloodsteel-1"))), + (1.0, Some(Item("common.items.weapons.bow.velorite"))), + ])), + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-5.beastmaster"), +) diff --git a/assets/common/entity/dungeon/tier-5/husk.ron b/assets/common/entity/dungeon/tier-5/husk.ron new file mode 100644 index 0000000000..4e8bd6d35b --- /dev/null +++ b/assets/common/entity/dungeon/tier-5/husk.ron @@ -0,0 +1,8 @@ +EntityConfig ( + name: Some("Cultist Husk"), + + main_tool: None, + second_tool: None, + + loadout_config: Some("common.loadout.dungeon.tier-5.husk"), +) diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 4bf4cab6ff..0d8d6469a4 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -41,8 +41,8 @@ use tracing::warn; pub struct LoadoutBuilder(Loadout); #[derive(Copy, Clone, PartialEq, Deserialize, Serialize, Debug, EnumIter)] -pub enum LoadoutPreset { - Husk, +pub enum Preset { + HuskSummon, } #[derive(Debug, Deserialize, Clone)] @@ -426,10 +426,10 @@ impl LoadoutBuilder { } #[must_use] - pub fn with_preset(mut self, preset: LoadoutPreset) -> Self { + pub fn with_preset(mut self, preset: Preset) -> Self { let rng = &mut rand::thread_rng(); match preset { - LoadoutPreset::Husk => { + Preset::HuskSummon => { self = self.with_asset_expect("common.loadout.dungeon.tier-5.husk", rng) }, } @@ -671,7 +671,7 @@ mod tests { // Things that will be catched - invalid assets paths #[test] fn test_loadout_presets() { - for preset in LoadoutPreset::iter() { + for preset in Preset::iter() { std::mem::drop(LoadoutBuilder::default().with_preset(preset)); } } diff --git a/common/src/generation.rs b/common/src/generation.rs index 79959c5552..4cf13c361e 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -2,7 +2,7 @@ use crate::{ assets::{self, AssetExt}, comp::{ self, agent, humanoid, - inventory::loadout_builder::{ItemSpec, LoadoutBuilder, LoadoutPreset}, + inventory::loadout_builder::{ItemSpec, LoadoutBuilder}, Alignment, Body, Item, }, npc::{self, NPC_NAMES}, @@ -43,10 +43,7 @@ pub struct EntityInfo { // TODO: Properly give NPCs skills pub level: Option, pub loot_drop: Option, - // FIXME: using both preset and asset is silly, make it enum - // so it will be correct by construction pub loadout_config: Option, - pub loadout_preset: Option, pub make_loadout: Option) -> LoadoutBuilder>, pub skillset_config: Option, pub skillset_preset: Option, @@ -73,7 +70,6 @@ impl EntityInfo { level: None, loot_drop: None, loadout_config: None, - loadout_preset: None, make_loadout: None, skillset_config: None, skillset_preset: None, @@ -114,7 +110,7 @@ impl EntityInfo { } if let Some(loadout_config) = loadout_config { - self = self.with_loadout_config(&loadout_config); + self = self.with_loadout_config(loadout_config); } self @@ -187,13 +183,8 @@ impl EntityInfo { self } - pub fn with_loadout_preset(mut self, preset: LoadoutPreset) -> Self { - self.loadout_preset = Some(preset); - self - } - - pub fn with_loadout_config(mut self, config: &str) -> Self { - self.loadout_config = Some(config.to_owned()); + pub fn with_loadout_config(mut self, config: String) -> Self { + self.loadout_config = Some(config); self } diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index 5385dca55a..8b69c6a3ab 100644 --- a/common/src/states/basic_summon.rs +++ b/common/src/states/basic_summon.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ self, - inventory::loadout_builder::{LoadoutBuilder, LoadoutPreset}, + inventory::loadout_builder::{self, LoadoutBuilder}, Behavior, BehaviorCapability, CharacterState, StateUpdate, }, event::{LocalEvent, ServerEvent}, @@ -79,7 +79,7 @@ impl CharacterBehavior for Data { let mut loadout_builder = LoadoutBuilder::new().with_default_maintool(&body); - if let Some(preset) = self.static_data.summon_info.loadout_preset { + if let Some(preset) = self.static_data.summon_info.loadout_config { loadout_builder = loadout_builder.with_preset(preset); } @@ -88,7 +88,7 @@ impl CharacterBehavior for Data { let stats = comp::Stats::new("Summon".to_string()); let skill_set = SkillSetBuilder::build_skillset( &None, - self.static_data.summon_info.skillset_preset, + self.static_data.summon_info.skillset_config, ) .build(); @@ -177,7 +177,7 @@ pub struct SummonInfo { body: comp::Body, scale: Option, health_scaling: u16, - // TODO: use assets for specifying skills and loadouts? - loadout_preset: Option, - skillset_preset: Option, + // TODO: use assets for specifying skills and loadout? + loadout_config: Option, + skillset_config: Option, } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 9925a5926a..b4decd5bad 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -197,7 +197,6 @@ impl<'a> System<'a> for Sys { let EntityInfo { skillset_preset, main_tool, - loadout_preset, loadout_config, make_loadout, trading_information: economy, @@ -217,20 +216,13 @@ impl<'a> System<'a> for Sys { loadout_builder = loadout_builder.with_default_maintool(&body); } - // If there are configs, apply them - match (loadout_preset, &loadout_config) { - (Some(preset), Some(config)) => { - loadout_builder = loadout_builder.with_preset(preset); - loadout_builder = loadout_builder.with_asset_expect(&config, rng); + // If there is config, apply it. + // If not, use default equipement for this body. + match loadout_config { + Some(asset) => { + loadout_builder = loadout_builder.with_asset_expect(&asset, rng); }, - (Some(preset), None) => { - loadout_builder = loadout_builder.with_preset(preset); - }, - (None, Some(config)) => { - loadout_builder = loadout_builder.with_asset_expect(&config, rng); - }, - // If not, use default equipement for this body - (None, None) => { + None => { loadout_builder = loadout_builder.with_default_equipment(&body); }, } diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 86b8401b06..dc06ebc758 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -1257,17 +1257,9 @@ fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec "common.items.weapons.axe.malachite_axe-0", - 1 => "common.items.weapons.sword.bloodsteel-1", - _ => "common.items.weapons.bow.velorite", - }, - )), + .with_asset_expect("common.entity.dungeon.tier-5.beastmaster"), ); entities.resize_with(entities.len() + 2, || { EntityInfo::at(tile_wcenter.map(|e| e as f32)) @@ -1292,9 +1284,8 @@ fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec Date: Mon, 7 Jun 2021 14:38:08 +0300 Subject: [PATCH 10/37] SkillSetBuilder pre-refactoring --- common/src/comp/inventory/loadout_builder.rs | 4 +- common/src/skillset_builder.rs | 68 ++++++++++++++++---- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 0d8d6469a4..a2d7a12a25 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -112,7 +112,7 @@ fn choose<'a>( WeightedError::NoItem | WeightedError::AllWeightsZero => &None, WeightedError::InvalidWeight => { let err = format!("Negative values of probability in {}.", asset_specifier); - if cfg!(tests) { + if cfg!(any(debug_assertions, test)) { panic!("{}", err); } else { warn!("{}", err); @@ -121,7 +121,7 @@ fn choose<'a>( }, WeightedError::TooMany => { let err = format!("More than u32::MAX values in {}.", asset_specifier); - if cfg!(tests) { + if cfg!(any(debug_assertions, test)) { panic!("{}", err); } else { warn!("{}", err); diff --git a/common/src/skillset_builder.rs b/common/src/skillset_builder.rs index e23fa36a29..d61ed16be7 100644 --- a/common/src/skillset_builder.rs +++ b/common/src/skillset_builder.rs @@ -682,25 +682,57 @@ impl SkillSetBuilder { } } + #[must_use] + /// # Panics + /// will panic only in tests + /// 1) If added skill doesn't have any group + /// 2) If added skill already applied + /// 3) If added skill wasn't applied at the end pub fn with_skill(mut self, skill: Skill, level: Option) -> Self { - if let Some(skill_group) = skill.skill_group_kind() { - for _ in 0..level.unwrap_or(1) { - self.0 - .add_skill_points(skill_group, self.0.skill_cost(skill)); - self.0.unlock_skill(skill); - if !self.0.has_skill(skill) { - warn!( - "Failed to add skill: {:?}. Verify that it has the appropriate skill \ - group available and meets all prerequisite skills.", - skill - ); - } - } + #![warn(clippy::pedantic)] + let group = if let Some(skill_group) = skill.skill_group_kind() { + skill_group } else { - warn!( + let err = format!( "Tried to add skill: {:?} which does not have an associated skill group.", skill ); + if cfg!(test) { + panic!("{}", err); + } else { + warn!("{}", err); + } + return self; + }; + + let SkillSetBuilder(ref mut skill_set) = self; + if skill_is_applied(skill_set, skill, level) { + let err = format!( + "Tried to add skill: {:?} with level {:?} which is already applied", + skill, level, + ); + if cfg!(test) { + panic!("{}", err); + } else { + warn!("{}", err); + } + return self; + } + for _ in 0..level.unwrap_or(1) { + skill_set.add_skill_points(group, skill_set.skill_cost(skill)); + skill_set.unlock_skill(skill); + } + if !skill_is_applied(skill_set, skill, level) { + let err = format!( + "Failed to add skill: {:?}. Verify that it has the appropriate skill group \ + available and meets all prerequisite skills.", + skill + ); + if cfg!(test) { + panic!("{}", err); + } else { + warn!("{}", err); + } } self } @@ -711,3 +743,11 @@ impl SkillSetBuilder { pub fn build(self) -> SkillSet { self.0 } } + +fn skill_is_applied(skill_set: &SkillSet, skill: Skill, level: Option) -> bool { + if let Ok(applied_level) = skill_set.skill_level(skill) { + applied_level == level + } else { + false + } +} From 0c9f05b8d1b0bb315dcfc7cb10fa91770a774b4b Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Tue, 8 Jun 2021 00:58:05 +0300 Subject: [PATCH 11/37] Load skillsets from assets Done: support loading from assets in skillset_builder.rs entity_config field with skillset asset field move every SkillSet config to assets tests for skillset assets tests for assets in entity configs --- assets/common/entity/dungeon/tier-0/bow.ron | 3 +- assets/common/entity/dungeon/tier-0/spear.ron | 3 +- assets/common/entity/dungeon/tier-0/staff.ron | 3 +- assets/common/entity/dungeon/tier-1/bow.ron | 3 +- assets/common/entity/dungeon/tier-1/spear.ron | 3 +- assets/common/entity/dungeon/tier-1/staff.ron | 3 +- assets/common/entity/dungeon/tier-2/bow.ron | 3 +- assets/common/entity/dungeon/tier-2/spear.ron | 3 +- assets/common/entity/dungeon/tier-2/staff.ron | 3 +- assets/common/entity/dungeon/tier-3/bow.ron | 3 +- assets/common/entity/dungeon/tier-3/spear.ron | 3 +- assets/common/entity/dungeon/tier-3/staff.ron | 3 +- assets/common/entity/dungeon/tier-4/bow.ron | 3 +- assets/common/entity/dungeon/tier-4/spear.ron | 3 +- assets/common/entity/dungeon/tier-4/staff.ron | 3 +- .../entity/dungeon/tier-5/beastmaster.ron | 4 +- assets/common/entity/dungeon/tier-5/boss.ron | 9 + assets/common/entity/dungeon/tier-5/husk.ron | 3 +- .../common/entity/dungeon/tier-5/warlock.ron | 3 +- .../common/entity/dungeon/tier-5/warlord.ron | 3 +- assets/common/entity/test.ron | 6 +- assets/common/entity/village/guard.ron | 9 + assets/common/entity/village/merchant.ron | 9 + assets/common/entity/village/villager.ron | 16 + assets/common/loadout/world/traveler.ron | 2 +- assets/common/skillset/dungeon/tier-0/bow.ron | 12 + assets/common/skillset/dungeon/tier-1/bow.ron | 12 + assets/common/skillset/dungeon/tier-2/bow.ron | 12 + assets/common/skillset/dungeon/tier-3/bow.ron | 12 + assets/common/skillset/dungeon/tier-4/bow.ron | 12 + assets/common/skillset/dungeon/tier-5/axe.ron | 21 + assets/common/skillset/dungeon/tier-5/bow.ron | 21 + .../common/skillset/dungeon/tier-5/enemy.ron | 8 + .../common/skillset/dungeon/tier-5/hammer.ron | 21 + .../skillset/dungeon/tier-5/mindflayer.ron | 21 + .../common/skillset/dungeon/tier-5/staff.ron | 21 + .../common/skillset/dungeon/tier-5/sword.ron | 19 + assets/common/skillset/village/guard.ron | 24 + assets/common/skillset/village/merchant.ron | 17 + common/base/src/lib.rs | 21 + common/src/comp/inventory/loadout_builder.rs | 30 +- common/src/generation.rs | 72 +- common/src/skillset_builder.rs | 817 +++--------------- common/src/states/basic_summon.rs | 41 +- server/src/sys/terrain.rs | 57 +- world/src/site/dungeon/mod.rs | 13 +- world/src/site/settlement/mod.rs | 32 +- 47 files changed, 585 insertions(+), 840 deletions(-) create mode 100644 assets/common/entity/dungeon/tier-5/boss.ron create mode 100644 assets/common/entity/village/guard.ron create mode 100644 assets/common/entity/village/merchant.ron create mode 100644 assets/common/entity/village/villager.ron create mode 100644 assets/common/skillset/dungeon/tier-0/bow.ron create mode 100644 assets/common/skillset/dungeon/tier-1/bow.ron create mode 100644 assets/common/skillset/dungeon/tier-2/bow.ron create mode 100644 assets/common/skillset/dungeon/tier-3/bow.ron create mode 100644 assets/common/skillset/dungeon/tier-4/bow.ron create mode 100644 assets/common/skillset/dungeon/tier-5/axe.ron create mode 100644 assets/common/skillset/dungeon/tier-5/bow.ron create mode 100644 assets/common/skillset/dungeon/tier-5/enemy.ron create mode 100644 assets/common/skillset/dungeon/tier-5/hammer.ron create mode 100644 assets/common/skillset/dungeon/tier-5/mindflayer.ron create mode 100644 assets/common/skillset/dungeon/tier-5/staff.ron create mode 100644 assets/common/skillset/dungeon/tier-5/sword.ron create mode 100644 assets/common/skillset/village/guard.ron create mode 100644 assets/common/skillset/village/merchant.ron diff --git a/assets/common/entity/dungeon/tier-0/bow.ron b/assets/common/entity/dungeon/tier-0/bow.ron index 0ba5a289d6..e135808266 100644 --- a/assets/common/entity/dungeon/tier-0/bow.ron +++ b/assets/common/entity/dungeon/tier-0/bow.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.adlet_bow")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-0.gnarling"), + loadout_asset: Some("common.loadout.dungeon.tier-0.gnarling"), + skillset_asset: Some("common.skillset.dungeon.tier-0.bow"), ) diff --git a/assets/common/entity/dungeon/tier-0/spear.ron b/assets/common/entity/dungeon/tier-0/spear.ron index ffc2e9cf4f..72c3b46fa3 100644 --- a/assets/common/entity/dungeon/tier-0/spear.ron +++ b/assets/common/entity/dungeon/tier-0/spear.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.wooden_spear")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-0.gnarling"), + loadout_asset: Some("common.loadout.dungeon.tier-0.gnarling"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-0/staff.ron b/assets/common/entity/dungeon/tier-0/staff.ron index b0b6a7a751..d8928f39fe 100644 --- a/assets/common/entity/dungeon/tier-0/staff.ron +++ b/assets/common/entity/dungeon/tier-0/staff.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.gnoll_staff")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-0.gnarling"), + loadout_asset: Some("common.loadout.dungeon.tier-0.gnarling"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-1/bow.ron b/assets/common/entity/dungeon/tier-1/bow.ron index fde472fdb5..e7ea1ed0dc 100644 --- a/assets/common/entity/dungeon/tier-1/bow.ron +++ b/assets/common/entity/dungeon/tier-1/bow.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.adlet_bow")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-1.adlet_bow"), + loadout_asset: Some("common.loadout.dungeon.tier-1.adlet_bow"), + skillset_asset: Some("common.skillset.dungeon.tier-1.bow"), ) diff --git a/assets/common/entity/dungeon/tier-1/spear.ron b/assets/common/entity/dungeon/tier-1/spear.ron index 48a07585be..0f2d3322e5 100644 --- a/assets/common/entity/dungeon/tier-1/spear.ron +++ b/assets/common/entity/dungeon/tier-1/spear.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.wooden_spear")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-1.adlet_spear"), + loadout_asset: Some("common.loadout.dungeon.tier-1.adlet_spear"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-1/staff.ron b/assets/common/entity/dungeon/tier-1/staff.ron index 820c12436c..522ec1d415 100644 --- a/assets/common/entity/dungeon/tier-1/staff.ron +++ b/assets/common/entity/dungeon/tier-1/staff.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.gnoll_staff")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-1.adlet_spear"), + loadout_asset: Some("common.loadout.dungeon.tier-1.adlet_spear"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-2/bow.ron b/assets/common/entity/dungeon/tier-2/bow.ron index 8ea21fa08c..73950982ee 100644 --- a/assets/common/entity/dungeon/tier-2/bow.ron +++ b/assets/common/entity/dungeon/tier-2/bow.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.adlet_bow")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-2.sahagin"), + loadout_asset: Some("common.loadout.dungeon.tier-2.sahagin"), + skillset_asset: Some("common.skillset.dungeon.tier-2.bow"), ) diff --git a/assets/common/entity/dungeon/tier-2/spear.ron b/assets/common/entity/dungeon/tier-2/spear.ron index bd8d7c9e4e..c9499cfa47 100644 --- a/assets/common/entity/dungeon/tier-2/spear.ron +++ b/assets/common/entity/dungeon/tier-2/spear.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.wooden_spear")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-2.sahagin"), + loadout_asset: Some("common.loadout.dungeon.tier-2.sahagin"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-2/staff.ron b/assets/common/entity/dungeon/tier-2/staff.ron index 73cb84ee55..4ecbcd121e 100644 --- a/assets/common/entity/dungeon/tier-2/staff.ron +++ b/assets/common/entity/dungeon/tier-2/staff.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.gnoll_staff")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-2.sahagin"), + loadout_asset: Some("common.loadout.dungeon.tier-2.sahagin"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-3/bow.ron b/assets/common/entity/dungeon/tier-3/bow.ron index d1a9d98924..e642c37333 100644 --- a/assets/common/entity/dungeon/tier-3/bow.ron +++ b/assets/common/entity/dungeon/tier-3/bow.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.adlet_bow")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-3.haniwa"), + loadout_asset: Some("common.loadout.dungeon.tier-3.haniwa"), + skillset_asset: Some("common.skillset.dungeon.tier-3.bow"), ) diff --git a/assets/common/entity/dungeon/tier-3/spear.ron b/assets/common/entity/dungeon/tier-3/spear.ron index 5e303990af..4c107bdd73 100644 --- a/assets/common/entity/dungeon/tier-3/spear.ron +++ b/assets/common/entity/dungeon/tier-3/spear.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.wooden_spear")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-3.haniwa"), + loadout_asset: Some("common.loadout.dungeon.tier-3.haniwa"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-3/staff.ron b/assets/common/entity/dungeon/tier-3/staff.ron index ef130792c0..adfe8857e8 100644 --- a/assets/common/entity/dungeon/tier-3/staff.ron +++ b/assets/common/entity/dungeon/tier-3/staff.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.gnoll_staff")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-3.haniwa"), + loadout_asset: Some("common.loadout.dungeon.tier-3.haniwa"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-4/bow.ron b/assets/common/entity/dungeon/tier-4/bow.ron index 7a9006fb5a..04cf0e7b02 100644 --- a/assets/common/entity/dungeon/tier-4/bow.ron +++ b/assets/common/entity/dungeon/tier-4/bow.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.adlet_bow")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-4.myrmidon"), + loadout_asset: Some("common.loadout.dungeon.tier-4.myrmidon"), + skillset_asset: Some("common.skillset.dungeon.tier-4.bow"), ) diff --git a/assets/common/entity/dungeon/tier-4/spear.ron b/assets/common/entity/dungeon/tier-4/spear.ron index 3ae610eb7e..d73950e575 100644 --- a/assets/common/entity/dungeon/tier-4/spear.ron +++ b/assets/common/entity/dungeon/tier-4/spear.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.wooden_spear")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-4.myrmidon"), + loadout_asset: Some("common.loadout.dungeon.tier-4.myrmidon"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-4/staff.ron b/assets/common/entity/dungeon/tier-4/staff.ron index 23d0521872..a022a4b330 100644 --- a/assets/common/entity/dungeon/tier-4/staff.ron +++ b/assets/common/entity/dungeon/tier-4/staff.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.gnoll_staff")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-4.myrmidon"), + loadout_asset: Some("common.loadout.dungeon.tier-4.myrmidon"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-5/beastmaster.ron b/assets/common/entity/dungeon/tier-5/beastmaster.ron index c34e9bc99e..4c2421ac72 100644 --- a/assets/common/entity/dungeon/tier-5/beastmaster.ron +++ b/assets/common/entity/dungeon/tier-5/beastmaster.ron @@ -8,5 +8,7 @@ EntityConfig ( ])), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-5.beastmaster"), + loadout_asset: Some("common.loadout.dungeon.tier-5.beastmaster"), + // TODO: make skillset for him, I'm just too lazy + skillset_asset: Some("common.skillset.dungeon.tier-5.enemy"), ) diff --git a/assets/common/entity/dungeon/tier-5/boss.ron b/assets/common/entity/dungeon/tier-5/boss.ron new file mode 100644 index 0000000000..042951cee6 --- /dev/null +++ b/assets/common/entity/dungeon/tier-5/boss.ron @@ -0,0 +1,9 @@ +EntityConfig ( + name: Some("Mindflayer"), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: Some("common.skillset.dungeon.tier-5.mindflayer"), +) diff --git a/assets/common/entity/dungeon/tier-5/husk.ron b/assets/common/entity/dungeon/tier-5/husk.ron index 4e8bd6d35b..6c23289a44 100644 --- a/assets/common/entity/dungeon/tier-5/husk.ron +++ b/assets/common/entity/dungeon/tier-5/husk.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: None, second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-5.husk"), + loadout_asset: Some("common.loadout.dungeon.tier-5.husk"), + skillset_asset: None, ) diff --git a/assets/common/entity/dungeon/tier-5/warlock.ron b/assets/common/entity/dungeon/tier-5/warlock.ron index f151381032..8e5fe4ae70 100644 --- a/assets/common/entity/dungeon/tier-5/warlock.ron +++ b/assets/common/entity/dungeon/tier-5/warlock.ron @@ -4,5 +4,6 @@ EntityConfig ( main_tool: Some(Item("common.items.weapons.staff.cultist_staff")), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-5.warlock"), + loadout_asset: Some("common.loadout.dungeon.tier-5.warlock"), + skillset_asset: Some("common.skillset.dungeon.tier-5.enemy"), ) diff --git a/assets/common/entity/dungeon/tier-5/warlord.ron b/assets/common/entity/dungeon/tier-5/warlord.ron index 8369c3f29c..37430fc373 100644 --- a/assets/common/entity/dungeon/tier-5/warlord.ron +++ b/assets/common/entity/dungeon/tier-5/warlord.ron @@ -10,5 +10,6 @@ EntityConfig ( ])), second_tool: None, - loadout_config: Some("common.loadout.dungeon.tier-5.warlord"), + loadout_asset: Some("common.loadout.dungeon.tier-5.warlord"), + skillset_asset: Some("common.skillset.dungeon.tier-5.enemy"), ) diff --git a/assets/common/entity/test.ron b/assets/common/entity/test.ron index 040cc162b2..915f98ade2 100644 --- a/assets/common/entity/test.ron +++ b/assets/common/entity/test.ron @@ -16,10 +16,10 @@ EntityConfig ( second_tool: None, /// Loadout Config (with asset_specifier for loadout) - loadout_config: Some("common.loadout.village.merchant"), + loadout_asset: Some("common.loadout.village.merchant"), - /// Skillset Config as Option (with asset_specifier for skillset) - // skillset_config: None, + /// Skillset Config (with asset_specifier for skillset) + skillset_asset: Some("common.skillset.village.merchant"), /// Loot /// Can be Item (with asset_specifier for item) diff --git a/assets/common/entity/village/guard.ron b/assets/common/entity/village/guard.ron new file mode 100644 index 0000000000..93d770cb2c --- /dev/null +++ b/assets/common/entity/village/guard.ron @@ -0,0 +1,9 @@ +EntityConfig ( + name: Some("Guard"), + + main_tool: Some(Item("common.items.weapons.sword.iron-4")), + second_tool: None, + + loadout_asset: None, + skillset_asset: Some("common.skillset.village.guard"), +) diff --git a/assets/common/entity/village/merchant.ron b/assets/common/entity/village/merchant.ron new file mode 100644 index 0000000000..07dd497fdf --- /dev/null +++ b/assets/common/entity/village/merchant.ron @@ -0,0 +1,9 @@ +EntityConfig ( + name: Some("Merchant"), + + main_tool: Some(Item("common.items.weapons.bow.eldwood-0")), + second_tool: None, + + loadout_asset: None, + skillset_asset: Some("common.skillset.village.merchant"), +) diff --git a/assets/common/entity/village/villager.ron b/assets/common/entity/village/villager.ron new file mode 100644 index 0000000000..45a92d5abb --- /dev/null +++ b/assets/common/entity/village/villager.ron @@ -0,0 +1,16 @@ +EntityConfig ( + name: None, + + main_tool: Some(Choice([ + (1.0, Some(Item("common.items.weapons.tool.broom"))), + (1.0, Some(Item("common.items.weapons.tool.hoe"))), + (1.0, Some(Item("common.items.weapons.tool.pickaxe"))), + (1.0, Some(Item("common.items.weapons.tool.rake"))), + (1.0, Some(Item("common.items.weapons.tool.shovel-0"))), + (1.0, Some(Item("common.items.weapons.tool.shovel-1"))), + ])), + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/loadout/world/traveler.ron b/assets/common/loadout/world/traveler.ron index c2126e5496..2a38bafaef 100644 --- a/assets/common/loadout/world/traveler.ron +++ b/assets/common/loadout/world/traveler.ron @@ -9,7 +9,7 @@ Armor(Chest): Item("common.items.npc_armor.chest.leather_blue"), Armor(Legs): Item("common.items.npc_armor.pants.leather_blue"), - Armor(Shoulders): Item("common.items.armor.swift.shoulder"), + Armor(Shoulders): Item("common.items.armor.hide.leather.shoulder"), Armor(Back): Choice([ (1.0, Some(Item("common.items.armor.hide.rawhide.back"))), diff --git a/assets/common/skillset/dungeon/tier-0/bow.ron b/assets/common/skillset/dungeon/tier-0/bow.ron new file mode 100644 index 0000000000..5a99b61b66 --- /dev/null +++ b/assets/common/skillset/dungeon/tier-0/bow.ron @@ -0,0 +1,12 @@ +([ + Group(Weapon(Bow)), + + // Charged + Skill((Bow(CDamage), Some(1))), + Skill((Bow(CKnockback), Some(1))), + Skill((Bow(CSpeed), Some(1))), + Skill((Bow(CMove), Some(1))), + + // Repeater + Skill((Bow(RDamage), Some(1))), +]) diff --git a/assets/common/skillset/dungeon/tier-1/bow.ron b/assets/common/skillset/dungeon/tier-1/bow.ron new file mode 100644 index 0000000000..5a99b61b66 --- /dev/null +++ b/assets/common/skillset/dungeon/tier-1/bow.ron @@ -0,0 +1,12 @@ +([ + Group(Weapon(Bow)), + + // Charged + Skill((Bow(CDamage), Some(1))), + Skill((Bow(CKnockback), Some(1))), + Skill((Bow(CSpeed), Some(1))), + Skill((Bow(CMove), Some(1))), + + // Repeater + Skill((Bow(RDamage), Some(1))), +]) diff --git a/assets/common/skillset/dungeon/tier-2/bow.ron b/assets/common/skillset/dungeon/tier-2/bow.ron new file mode 100644 index 0000000000..5a99b61b66 --- /dev/null +++ b/assets/common/skillset/dungeon/tier-2/bow.ron @@ -0,0 +1,12 @@ +([ + Group(Weapon(Bow)), + + // Charged + Skill((Bow(CDamage), Some(1))), + Skill((Bow(CKnockback), Some(1))), + Skill((Bow(CSpeed), Some(1))), + Skill((Bow(CMove), Some(1))), + + // Repeater + Skill((Bow(RDamage), Some(1))), +]) diff --git a/assets/common/skillset/dungeon/tier-3/bow.ron b/assets/common/skillset/dungeon/tier-3/bow.ron new file mode 100644 index 0000000000..5a99b61b66 --- /dev/null +++ b/assets/common/skillset/dungeon/tier-3/bow.ron @@ -0,0 +1,12 @@ +([ + Group(Weapon(Bow)), + + // Charged + Skill((Bow(CDamage), Some(1))), + Skill((Bow(CKnockback), Some(1))), + Skill((Bow(CSpeed), Some(1))), + Skill((Bow(CMove), Some(1))), + + // Repeater + Skill((Bow(RDamage), Some(1))), +]) diff --git a/assets/common/skillset/dungeon/tier-4/bow.ron b/assets/common/skillset/dungeon/tier-4/bow.ron new file mode 100644 index 0000000000..5a99b61b66 --- /dev/null +++ b/assets/common/skillset/dungeon/tier-4/bow.ron @@ -0,0 +1,12 @@ +([ + Group(Weapon(Bow)), + + // Charged + Skill((Bow(CDamage), Some(1))), + Skill((Bow(CKnockback), Some(1))), + Skill((Bow(CSpeed), Some(1))), + Skill((Bow(CMove), Some(1))), + + // Repeater + Skill((Bow(RDamage), Some(1))), +]) diff --git a/assets/common/skillset/dungeon/tier-5/axe.ron b/assets/common/skillset/dungeon/tier-5/axe.ron new file mode 100644 index 0000000000..abfcaae590 --- /dev/null +++ b/assets/common/skillset/dungeon/tier-5/axe.ron @@ -0,0 +1,21 @@ +([ + Group(Weapon(Axe)), + + // DoubleStrike + Skill((Axe(DsCombo), None)), + Skill((Axe(DsDamage), Some(1))), + Skill((Axe(DsSpeed), Some(1))), + Skill((Axe(DsRegen), Some(1))), + + // Spin + Skill((Axe(SInfinite), None)), + Skill((Axe(SHelicopter), None)), + Skill((Axe(SDamage), Some(1))), + Skill((Axe(SSpeed), Some(1))), + + // Leap + Skill((Axe(UnlockLeap), None)), + Skill((Axe(LDamage), Some(1))), + Skill((Axe(LKnockback), Some(1))), + Skill((Axe(LDistance), Some(1))), +]) diff --git a/assets/common/skillset/dungeon/tier-5/bow.ron b/assets/common/skillset/dungeon/tier-5/bow.ron new file mode 100644 index 0000000000..a1cd8b0def --- /dev/null +++ b/assets/common/skillset/dungeon/tier-5/bow.ron @@ -0,0 +1,21 @@ +([ + Group(Weapon(Bow)), + + // Charged + Skill((Bow(CDamage), Some(1))), + Skill((Bow(CRegen), Some(1))), + Skill((Bow(CKnockback), Some(1))), + Skill((Bow(CSpeed), Some(1))), + Skill((Bow(CMove), Some(1))), + + // Repeater + Skill((Bow(RDamage), Some(1))), + Skill((Bow(RSpeed), Some(1))), + + // Shotgun + Skill((Bow(UnlockShotgun), None)), + Skill((Bow(SDamage), Some(1))), + Skill((Bow(SSpread), Some(1))), + Skill((Bow(SArrows), Some(1))), + Skill((Bow(SCost), Some(1))), +]) diff --git a/assets/common/skillset/dungeon/tier-5/enemy.ron b/assets/common/skillset/dungeon/tier-5/enemy.ron new file mode 100644 index 0000000000..0e5ff1c8e5 --- /dev/null +++ b/assets/common/skillset/dungeon/tier-5/enemy.ron @@ -0,0 +1,8 @@ +([ + // Just gather everything + Tree("common.skillset.dungeon.tier-5.sword"), + Tree("common.skillset.dungeon.tier-5.axe"), + Tree("common.skillset.dungeon.tier-5.hammer"), + Tree("common.skillset.dungeon.tier-5.bow"), + Tree("common.skillset.dungeon.tier-5.staff"), +]) diff --git a/assets/common/skillset/dungeon/tier-5/hammer.ron b/assets/common/skillset/dungeon/tier-5/hammer.ron new file mode 100644 index 0000000000..5f0e7561a3 --- /dev/null +++ b/assets/common/skillset/dungeon/tier-5/hammer.ron @@ -0,0 +1,21 @@ +([ + Group(Weapon(Hammer)), + + // Single Strike, as single as you are + Skill((Hammer(SsKnockback), Some(1))), + Skill((Hammer(SsDamage), Some(1))), + Skill((Hammer(SsSpeed), Some(1))), + Skill((Hammer(SsRegen), Some(1))), + + // Charged + Skill((Hammer(CKnockback), Some(1))), + Skill((Hammer(CDamage), Some(1))), + Skill((Hammer(CDrain), Some(1))), + + // Leap + Skill((Hammer(UnlockLeap), None)), + Skill((Hammer(LDamage), Some(1))), + Skill((Hammer(LDistance), Some(1))), + Skill((Hammer(LKnockback), Some(1))), + Skill((Hammer(LRange), Some(1))), +]) diff --git a/assets/common/skillset/dungeon/tier-5/mindflayer.ron b/assets/common/skillset/dungeon/tier-5/mindflayer.ron new file mode 100644 index 0000000000..09cba3e4bd --- /dev/null +++ b/assets/common/skillset/dungeon/tier-5/mindflayer.ron @@ -0,0 +1,21 @@ +([ + Group(Weapon(Staff)), + + // Fireball + Skill((Staff(BDamage), Some(3))), + Skill((Staff(BRegen), Some(2))), + Skill((Staff(BRadius), Some(2))), + + // Flamethrower + Skill((Staff(FDamage), Some(3))), + Skill((Staff(FRange), Some(2))), + Skill((Staff(FDrain), Some(2))), + Skill((Staff(FVelocity), Some(2))), + + // Shockwave + Skill((Staff(UnlockShockwave), None)), + Skill((Staff(SDamage), Some(2))), + Skill((Staff(SKnockback), Some(2))), + Skill((Staff(SRange), Some(2))), + Skill((Staff(SCost), Some(2))), +]) diff --git a/assets/common/skillset/dungeon/tier-5/staff.ron b/assets/common/skillset/dungeon/tier-5/staff.ron new file mode 100644 index 0000000000..4c3dc1af3a --- /dev/null +++ b/assets/common/skillset/dungeon/tier-5/staff.ron @@ -0,0 +1,21 @@ +([ + Group(Weapon(Staff)), + + // Fireball + Skill((Staff(BDamage), Some(1))), + Skill((Staff(BRegen), Some(1))), + Skill((Staff(BRadius), Some(1))), + + // Flamethrower + Skill((Staff(FRange), Some(1))), + Skill((Staff(FDrain), Some(1))), + Skill((Staff(FDamage), Some(1))), + Skill((Staff(FVelocity), Some(1))), + + // Shockwave + Skill((Staff(UnlockShockwave), None)), + Skill((Staff(SDamage), Some(1))), + Skill((Staff(SKnockback), Some(1))), + Skill((Staff(SRange), Some(1))), + Skill((Staff(SCost), Some(1))), +]) diff --git a/assets/common/skillset/dungeon/tier-5/sword.ron b/assets/common/skillset/dungeon/tier-5/sword.ron new file mode 100644 index 0000000000..2f945fc42f --- /dev/null +++ b/assets/common/skillset/dungeon/tier-5/sword.ron @@ -0,0 +1,19 @@ +([ + Group(Weapon(Sword)), + + // TripleStrike + Skill((Sword(TsCombo), None)), + Skill((Sword(TsDamage), Some(1))), + Skill((Sword(TsRegen), Some(1))), + + // Dash + Skill((Sword(DDamage), Some(1))), + Skill((Sword(DCost), Some(1))), + Skill((Sword(DDrain), Some(1))), + + // Spin of death + Skill((Sword(UnlockSpin), None)), + Skill((Sword(SDamage), Some(1))), + Skill((Sword(SSpins), Some(2))), + Skill((Sword(SCost), Some(1))), +]) diff --git a/assets/common/skillset/village/guard.ron b/assets/common/skillset/village/guard.ron new file mode 100644 index 0000000000..ad041b0f2d --- /dev/null +++ b/assets/common/skillset/village/guard.ron @@ -0,0 +1,24 @@ +([ + Group(Weapon(Sword)), + + // TripleStrike + Skill((Sword(TsCombo), None)), + Skill((Sword(TsDamage), Some(1))), + Skill((Sword(TsRegen), Some(1))), + Skill((Sword(TsSpeed), Some(1))), + + // Dash + Skill((Sword(DDamage), Some(1))), + Skill((Sword(DCost), Some(1))), + Skill((Sword(DDrain), Some(1))), + Skill((Sword(DScaling), Some(1))), + Skill((Sword(DSpeed), None)), + Skill((Sword(DInfinite), None)), + + // Spin of death + Skill((Sword(UnlockSpin), None)), + Skill((Sword(SDamage), Some(1))), + Skill((Sword(SSpeed), Some(1))), + Skill((Sword(SSpins), Some(2))), + Skill((Sword(SCost), Some(1))), +]) diff --git a/assets/common/skillset/village/merchant.ron b/assets/common/skillset/village/merchant.ron new file mode 100644 index 0000000000..06355a4ff4 --- /dev/null +++ b/assets/common/skillset/village/merchant.ron @@ -0,0 +1,17 @@ +([ + Group(Weapon(Bow)), + + // Charged + Skill((Bow(CDamage), Some(1))), + Skill((Bow(CRegen), Some(1))), + Skill((Bow(CKnockback), Some(1))), + Skill((Bow(CSpeed), Some(1))), + + // Repeater + Skill((Bow(RDamage), Some(1))), + Skill((Bow(RCost), Some(1))), + + // Shotgun + Skill((Bow(UnlockShotgun), None)), + Skill((Bow(SCost), Some(1))), +]) diff --git a/common/base/src/lib.rs b/common/base/src/lib.rs index 3325612858..d05b952127 100644 --- a/common/base/src/lib.rs +++ b/common/base/src/lib.rs @@ -23,6 +23,27 @@ macro_rules! plot { }; } +// Panic in debug or tests, warn in release +#[macro_export] +macro_rules! dev_panic { + ($msg:expr) => { + if cfg!(any(debug_assertions, test)) { + panic!("{}", $msg); + } else { + tracing::warn!("{}", $msg); + } + }; + + ($msg:expr, or return $result:expr) => { + if cfg!(any(debug_assertions, test)) { + panic!("{}", $msg); + } else { + tracing::warn!("{}", $msg); + return $result; + } + }; +} + // https://discordapp.com/channels/676678179678715904/676685797524766720/723358438943621151 #[macro_export] macro_rules! span { diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index a2d7a12a25..eb8fc67148 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -45,6 +45,14 @@ pub enum Preset { HuskSummon, } +#[derive(Debug, Deserialize, Clone)] +pub struct LoadoutSpec(HashMap); +impl assets::Asset for LoadoutSpec { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} + #[derive(Debug, Deserialize, Clone)] pub enum ItemSpec { /// One specific item. @@ -112,35 +120,17 @@ fn choose<'a>( WeightedError::NoItem | WeightedError::AllWeightsZero => &None, WeightedError::InvalidWeight => { let err = format!("Negative values of probability in {}.", asset_specifier); - if cfg!(any(debug_assertions, test)) { - panic!("{}", err); - } else { - warn!("{}", err); - &None - } + common_base::dev_panic!(err, or return &None) }, WeightedError::TooMany => { let err = format!("More than u32::MAX values in {}.", asset_specifier); - if cfg!(any(debug_assertions, test)) { - panic!("{}", err); - } else { - warn!("{}", err); - &None - } + common_base::dev_panic!(err, or return &None) }, }, |(_p, itemspec)| itemspec, ) } -#[derive(Debug, Deserialize, Clone)] -pub struct LoadoutSpec(HashMap); -impl assets::Asset for LoadoutSpec { - type Loader = assets::RonLoader; - - const EXTENSION: &'static str = "ron"; -} - #[must_use] pub fn make_potion_bag(quantity: u32) -> Item { let mut bag = Item::new_from_asset_expect("common.items.armor.misc.bag.tiny_leather_pouch"); diff --git a/common/src/generation.rs b/common/src/generation.rs index 4cf13c361e..f31aa7628e 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -6,7 +6,6 @@ use crate::{ Alignment, Body, Item, }, npc::{self, NPC_NAMES}, - skillset_builder::SkillSetConfig, trade, trade::SiteInformation, }; @@ -18,7 +17,8 @@ struct EntityConfig { name: Option, main_tool: Option, second_tool: Option, - loadout_config: Option, + loadout_asset: Option, + skillset_asset: Option, } impl assets::Asset for EntityConfig { @@ -43,10 +43,9 @@ pub struct EntityInfo { // TODO: Properly give NPCs skills pub level: Option, pub loot_drop: Option, - pub loadout_config: Option, + pub loadout_asset: Option, pub make_loadout: Option) -> LoadoutBuilder>, - pub skillset_config: Option, - pub skillset_preset: Option, + pub skillset_asset: Option, pub pet: Option>, // we can't use DHashMap, do we want to move that into common? pub trading_information: Option, @@ -69,10 +68,9 @@ impl EntityInfo { scale: 1.0, level: None, loot_drop: None, - loadout_config: None, + loadout_asset: None, make_loadout: None, - skillset_config: None, - skillset_preset: None, + skillset_asset: None, pet: None, trading_information: None, } @@ -90,7 +88,8 @@ impl EntityInfo { name, main_tool, second_tool, - loadout_config, + loadout_asset, + skillset_asset, } = config; if let Some(name) = name { @@ -109,8 +108,12 @@ impl EntityInfo { self = self.with_main_tool(second_tool); } - if let Some(loadout_config) = loadout_config { - self = self.with_loadout_config(loadout_config); + if let Some(loadout_asset) = loadout_asset { + self = self.with_loadout_asset(loadout_asset); + } + + if let Some(skillset_asset) = skillset_asset { + self = self.with_skillset_asset(skillset_asset); } self @@ -183,8 +186,8 @@ impl EntityInfo { self } - pub fn with_loadout_config(mut self, config: String) -> Self { - self.loadout_config = Some(config); + pub fn with_loadout_asset(mut self, asset: String) -> Self { + self.loadout_asset = Some(asset); self } @@ -196,15 +199,8 @@ impl EntityInfo { self } - pub fn with_skillset_preset(mut self, preset: SkillSetConfig) -> Self { - self.skillset_preset = Some(preset); - self - } - - // FIXME: Doesn't work for now, because skills can't be loaded from assets for - // now - pub fn with_skillset_config(mut self, config: String) -> Self { - self.skillset_config = Some(config); + pub fn with_skillset_asset(mut self, asset: String) -> Self { + self.skillset_asset = Some(asset); self } @@ -269,6 +265,7 @@ pub fn get_npc_name< #[cfg(test)] mod tests { use super::*; + use crate::{comp::inventory::slot::EquipSlot, SkillSetBuilder}; use assets::Error; #[test] @@ -292,12 +289,35 @@ mod tests { } // It just load everything that could - // TODO: add some checks, e.g. that Armor(Head) key correspond - // to Item with ItemKind Head(_) let entity_configs = EntityList::load_expect_cloned("common.entity.*").0; for config in entity_configs { - let pos = Vec3::new(0.0, 0.0, 0.0); - std::mem::drop(EntityInfo::at(pos).with_entity_config(config, None)); + let EntityConfig { + main_tool, + second_tool, + loadout_asset, + skillset_asset, + .. + } = config; + + if let Some(main_tool) = main_tool { + main_tool.validate(EquipSlot::ActiveMainhand); + } + + if let Some(second_tool) = second_tool { + second_tool.validate(EquipSlot::ActiveOffhand); + } + + if let Some(loadout_asset) = loadout_asset { + let rng = &mut rand::thread_rng(); + let builder = LoadoutBuilder::default(); + // we need to just load it check if it exists, + // because all loadouts are tested in LoadoutBuilder module + std::mem::drop(builder.with_asset_expect(&loadout_asset, rng)); + } + + if let Some(skillset_asset) = skillset_asset { + std::mem::drop(SkillSetBuilder::from_asset_expect(&skillset_asset)); + } } } } diff --git a/common/src/skillset_builder.rs b/common/src/skillset_builder.rs index d61ed16be7..8fddd2b7b1 100644 --- a/common/src/skillset_builder.rs +++ b/common/src/skillset_builder.rs @@ -1,30 +1,56 @@ -use crate::comp::{ - item::{tool::ToolKind, Item, ItemKind}, - skills::{ - AxeSkill, BowSkill, HammerSkill, Skill, SkillGroupKind, SkillSet, StaffSkill, SwordSkill, - }, -}; +#![warn(clippy::pedantic)] +//#![warn(clippy::nursery)] +use crate::comp::skills::{Skill, SkillGroupKind, SkillSet}; + +use crate::assets::{self, AssetExt}; use serde::{Deserialize, Serialize}; use tracing::warn; #[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug)] -pub enum SkillSetConfig { - Adlet, - Gnarling, - Sahagin, - Haniwa, - Myrmidon, - Guard, - Villager, - Merchant, - Outcast, - Highwayman, - Bandit, - CultistNovice, - CultistAcolyte, - Warlord, - Warlock, - Mindflayer, +pub enum Preset { + Empty, +} + +#[derive(Debug, Deserialize, Clone)] +struct SkillSetTree(Vec); +impl assets::Asset for SkillSetTree { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} + +#[derive(Debug, Deserialize, Clone)] +enum SkillNode { + Tree(String), + Skill((Skill, Option)), + Group(SkillGroupKind), +} + +#[must_use] +fn skills_from_asset_expect(asset_specifier: &str) -> Vec<(Skill, Option)> { + let nodes = SkillSetTree::load_expect(asset_specifier).read().0.clone(); + + skills_from_nodes(nodes) +} + +#[must_use] +fn skills_from_nodes(nodes: Vec) -> Vec<(Skill, Option)> { + let mut skills = Vec::new(); + for node in nodes { + match node { + SkillNode::Tree(asset) => { + skills.append(&mut skills_from_asset_expect(&asset)); + }, + SkillNode::Skill(req) => { + skills.push(req); + }, + SkillNode::Group(group) => { + skills.push((Skill::UnlockGroup(group), None)); + }, + } + } + + skills } pub struct SkillSetBuilder(SkillSet); @@ -34,652 +60,40 @@ impl Default for SkillSetBuilder { } impl SkillSetBuilder { - pub fn build_skillset(main_tool: &Option, config: Option) -> Self { - let active_item = main_tool.as_ref().and_then(|ic| { - if let ItemKind::Tool(tool) = &ic.kind() { - Some(tool.kind) - } else { - None - } - }); + /// Creates `SkillSetBuilder` from `asset_specifier` + #[must_use] + pub fn from_asset_expect(asset_specifier: &str) -> Self { + let builder = Self::default(); - use SkillSetConfig::*; - match config { - Some(Adlet) => { - match active_item { - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::CSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CMove), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - }, - _ => Self::default(), - } - }, - Some(Gnarling) => { - match active_item { - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::CSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CMove), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - }, - _ => Self::default(), - } - }, - Some(Sahagin) => { - match active_item { - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::CSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CMove), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - }, - _ => Self::default(), - } - }, - Some(Haniwa) => { - match active_item { - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::CSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CMove), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - }, - _ => Self::default(), - } - }, - Some(Myrmidon) => { - match active_item { - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::CSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CMove), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - }, - _ => Self::default(), - } - }, - Some(Guard) => { - if let Some(ToolKind::Sword) = active_item { - // Sword - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Sword)) - .with_skill(Skill::Sword(SwordSkill::TsCombo), None) - .with_skill(Skill::Sword(SwordSkill::TsDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::TsRegen), Some(1)) - .with_skill(Skill::Sword(SwordSkill::TsSpeed), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DCost), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDrain), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DScaling), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DSpeed), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DInfinite), None) - .with_skill(Skill::Sword(SwordSkill::UnlockSpin), None) - .with_skill(Skill::Sword(SwordSkill::SDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SSpeed), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SSpins), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SCost), Some(1)) - } else { - Self::default() - } - }, - Some(Outcast) => { - match active_item { - Some(ToolKind::Sword) => { - // Sword - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Sword)) - .with_skill(Skill::Sword(SwordSkill::TsCombo), None) - .with_skill(Skill::Sword(SwordSkill::DDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DCost), Some(1)) - }, - Some(ToolKind::Axe) => { - // Axe - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)) - .with_skill(Skill::Axe(AxeSkill::DsCombo), None) - .with_skill(Skill::Axe(AxeSkill::SInfinite), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SSpeed), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SCost), Some(1)) - }, - Some(ToolKind::Hammer) => { - // Hammer - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Hammer)) - .with_skill(Skill::Hammer(HammerSkill::SsKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsSpeed), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CSpeed), Some(1)) - }, - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::ProjSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::RSpeed), Some(1)) - }, - Some(ToolKind::Staff) => { - // Staff - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Staff)) - .with_skill(Skill::Staff(StaffSkill::FDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FDrain), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FVelocity), Some(1)) - }, - _ => Self::default(), - } - }, - Some(Highwayman) => { - match active_item { - Some(ToolKind::Sword) => { - // Sword - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Sword)) - .with_skill(Skill::Sword(SwordSkill::TsCombo), None) - .with_skill(Skill::Sword(SwordSkill::TsDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::UnlockSpin), None) - .with_skill(Skill::Sword(SwordSkill::SSpins), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SCost), Some(1)) - }, - Some(ToolKind::Axe) => { - // Axe - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)) - .with_skill(Skill::Axe(AxeSkill::DsCombo), None) - .with_skill(Skill::Axe(AxeSkill::DsDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SInfinite), None) - .with_skill(Skill::Axe(AxeSkill::SDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SSpeed), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SCost), Some(1)) - .with_skill(Skill::Axe(AxeSkill::UnlockLeap), None) - }, - Some(ToolKind::Hammer) => { - // Hammer - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Hammer)) - .with_skill(Skill::Hammer(HammerSkill::SsKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsSpeed), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::UnlockLeap), None) - .with_skill(Skill::Hammer(HammerSkill::LKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LRange), Some(1)) - }, - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::CSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CMove), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::UnlockShotgun), None) - .with_skill(Skill::Bow(BowSkill::SArrows), Some(1)) - }, - Some(ToolKind::Staff) => { - // Staff - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Staff)) - .with_skill(Skill::Staff(StaffSkill::BRegen), Some(1)) - .with_skill(Skill::Staff(StaffSkill::BRadius), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FRange), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FVelocity), Some(1)) - .with_skill(Skill::Staff(StaffSkill::UnlockShockwave), None) - }, - _ => Self::default(), - } - }, - Some(Bandit) | Some(Merchant) => { - match active_item { - Some(ToolKind::Sword) => { - // Sword - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Sword)) - .with_skill(Skill::Sword(SwordSkill::TsCombo), None) - .with_skill(Skill::Sword(SwordSkill::TsDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DCost), Some(1)) - .with_skill(Skill::Sword(SwordSkill::UnlockSpin), None) - .with_skill(Skill::Sword(SwordSkill::SDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SSpins), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SCost), Some(1)) - }, - Some(ToolKind::Axe) => { - // Axe - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)) - .with_skill(Skill::Axe(AxeSkill::DsCombo), None) - .with_skill(Skill::Axe(AxeSkill::DsSpeed), Some(1)) - .with_skill(Skill::Axe(AxeSkill::DsRegen), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SInfinite), None) - .with_skill(Skill::Axe(AxeSkill::SDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::UnlockLeap), None) - .with_skill(Skill::Axe(AxeSkill::LKnockback), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LCost), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LDistance), Some(1)) - }, - Some(ToolKind::Hammer) => { - // Hammer - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Hammer)) - .with_skill(Skill::Hammer(HammerSkill::SsKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsRegen), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::UnlockLeap), None) - .with_skill(Skill::Hammer(HammerSkill::LDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LCost), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LDistance), Some(1)) - }, - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::ProjSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CRegen), Some(1)) - .with_skill(Skill::Bow(BowSkill::CSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::RCost), Some(1)) - .with_skill(Skill::Bow(BowSkill::UnlockShotgun), None) - .with_skill(Skill::Bow(BowSkill::SCost), Some(1)) - .with_skill(Skill::Bow(BowSkill::RCost), Some(1)) - }, - Some(ToolKind::Staff) => { - // Staff - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Staff)) - .with_skill(Skill::Staff(StaffSkill::FDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FRange), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FDrain), Some(1)) - .with_skill(Skill::Staff(StaffSkill::UnlockShockwave), None) - .with_skill(Skill::Staff(StaffSkill::SDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::SRange), Some(1)) - }, - _ => Self::default(), - } - }, - Some(CultistNovice) => { - match active_item { - Some(ToolKind::Sword) => { - // Sword - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Sword)) - .with_skill(Skill::Sword(SwordSkill::TsCombo), None) - .with_skill(Skill::Sword(SwordSkill::TsRegen), Some(1)) - .with_skill(Skill::Sword(SwordSkill::TsSpeed), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DCost), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDrain), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DScaling), Some(1)) - .with_skill(Skill::Sword(SwordSkill::UnlockSpin), None) - .with_skill(Skill::Sword(SwordSkill::SSpeed), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SSpins), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SCost), Some(1)) - }, - Some(ToolKind::Axe) => { - // Axe - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)) - .with_skill(Skill::Axe(AxeSkill::DsCombo), None) - .with_skill(Skill::Axe(AxeSkill::SInfinite), None) - .with_skill(Skill::Axe(AxeSkill::SHelicopter), None) - .with_skill(Skill::Axe(AxeSkill::SDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::UnlockLeap), None) - .with_skill(Skill::Axe(AxeSkill::LKnockback), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LDistance), Some(1)) - }, - Some(ToolKind::Hammer) => { - // Hammer - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Hammer)) - .with_skill(Skill::Hammer(HammerSkill::SsKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsSpeed), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsRegen), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CDrain), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CSpeed), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::UnlockLeap), None) - .with_skill(Skill::Hammer(HammerSkill::LDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LKnockback), Some(1)) - }, - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::ProjSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::RSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::RCost), Some(1)) - .with_skill(Skill::Bow(BowSkill::UnlockShotgun), None) - .with_skill(Skill::Bow(BowSkill::SDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::SArrows), Some(1)) - .with_skill(Skill::Bow(BowSkill::SSpread), Some(1)) - }, - Some(ToolKind::Staff) => { - // Staff - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Staff)) - .with_skill(Skill::Staff(StaffSkill::BDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::BRadius), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FRange), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FDrain), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FVelocity), Some(1)) - .with_skill(Skill::Staff(StaffSkill::UnlockShockwave), None) - .with_skill(Skill::Staff(StaffSkill::SDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::SRange), Some(1)) - }, - _ => Self::default(), - } - }, - Some(CultistAcolyte) => { - match active_item { - Some(ToolKind::Sword) => { - // Sword - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Sword)) - .with_skill(Skill::Sword(SwordSkill::TsCombo), None) - .with_skill(Skill::Sword(SwordSkill::TsDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::TsSpeed), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DScaling), Some(1)) - .with_skill(Skill::Sword(SwordSkill::UnlockSpin), None) - .with_skill(Skill::Sword(SwordSkill::SDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SSpeed), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SSpins), Some(1)) - }, - Some(ToolKind::Axe) => { - // Axe - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)) - .with_skill(Skill::Axe(AxeSkill::DsCombo), None) - .with_skill(Skill::Axe(AxeSkill::DsDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SInfinite), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SCost), Some(1)) - .with_skill(Skill::Axe(AxeSkill::UnlockLeap), None) - .with_skill(Skill::Axe(AxeSkill::LDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LKnockback), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LCost), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LDistance), Some(1)) - }, - Some(ToolKind::Hammer) => { - // Hammer - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Hammer)) - .with_skill(Skill::Hammer(HammerSkill::SsKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsRegen), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CDrain), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::UnlockLeap), None) - .with_skill(Skill::Hammer(HammerSkill::LDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LRange), Some(1)) - }, - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::CSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::RCost), Some(1)) - .with_skill(Skill::Bow(BowSkill::UnlockShotgun), None) - .with_skill(Skill::Bow(BowSkill::SDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::SSpread), Some(1)) - .with_skill(Skill::Bow(BowSkill::SArrows), Some(1)) - .with_skill(Skill::Bow(BowSkill::SCost), Some(1)) - }, - Some(ToolKind::Staff) => { - // Staff - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Staff)) - .with_skill(Skill::Staff(StaffSkill::BDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::BRadius), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FRange), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FVelocity), Some(1)) - .with_skill(Skill::Staff(StaffSkill::UnlockShockwave), None) - .with_skill(Skill::Staff(StaffSkill::SDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::SKnockback), Some(1)) - .with_skill(Skill::Staff(StaffSkill::SRange), Some(1)) - }, - _ => Self::default(), - } - }, - Some(Warlord) => { - match active_item { - Some(ToolKind::Sword) => { - // Sword - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Sword)) - .with_skill(Skill::Sword(SwordSkill::TsCombo), None) - .with_skill(Skill::Sword(SwordSkill::TsDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::TsRegen), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DCost), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDrain), Some(1)) - .with_skill(Skill::Sword(SwordSkill::UnlockSpin), None) - .with_skill(Skill::Sword(SwordSkill::SDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SSpins), Some(2)) - .with_skill(Skill::Sword(SwordSkill::SCost), Some(1)) - }, - Some(ToolKind::Axe) => { - // Axe - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)) - .with_skill(Skill::Axe(AxeSkill::DsCombo), None) - .with_skill(Skill::Axe(AxeSkill::DsDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::DsSpeed), Some(1)) - .with_skill(Skill::Axe(AxeSkill::DsRegen), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SInfinite), None) - .with_skill(Skill::Axe(AxeSkill::SHelicopter), None) - .with_skill(Skill::Axe(AxeSkill::SDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SSpeed), Some(1)) - .with_skill(Skill::Axe(AxeSkill::UnlockLeap), None) - .with_skill(Skill::Axe(AxeSkill::LDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LKnockback), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LDistance), Some(1)) - }, - Some(ToolKind::Hammer) => { - // Hammer - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Hammer)) - .with_skill(Skill::Hammer(HammerSkill::SsKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsSpeed), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsRegen), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CDrain), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::UnlockLeap), None) - .with_skill(Skill::Hammer(HammerSkill::LDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LDistance), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LRange), Some(1)) - }, - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CRegen), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::CSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CMove), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::RSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::UnlockShotgun), None) - .with_skill(Skill::Bow(BowSkill::SDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::SSpread), Some(1)) - .with_skill(Skill::Bow(BowSkill::SArrows), Some(1)) - .with_skill(Skill::Bow(BowSkill::SCost), Some(1)) - }, - Some(ToolKind::Staff) => { - // Staff - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Staff)) - .with_skill(Skill::Staff(StaffSkill::BDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::BRegen), Some(1)) - .with_skill(Skill::Staff(StaffSkill::BRadius), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FDrain), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FVelocity), Some(1)) - .with_skill(Skill::Staff(StaffSkill::UnlockShockwave), None) - .with_skill(Skill::Staff(StaffSkill::SDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::SKnockback), Some(1)) - .with_skill(Skill::Staff(StaffSkill::SCost), Some(1)) - }, - _ => Self::default(), - } - }, - Some(Warlock) => { - match active_item { - Some(ToolKind::Sword) => { - // Sword - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Sword)) - .with_skill(Skill::Sword(SwordSkill::TsCombo), None) - .with_skill(Skill::Sword(SwordSkill::TsDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::TsRegen), Some(1)) - .with_skill(Skill::Sword(SwordSkill::TsSpeed), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDamage), Some(2)) - .with_skill(Skill::Sword(SwordSkill::DCost), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DDrain), Some(1)) - .with_skill(Skill::Sword(SwordSkill::DScaling), Some(1)) - .with_skill(Skill::Sword(SwordSkill::UnlockSpin), None) - .with_skill(Skill::Sword(SwordSkill::SDamage), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SSpeed), Some(1)) - .with_skill(Skill::Sword(SwordSkill::SSpins), Some(2)) - .with_skill(Skill::Sword(SwordSkill::SCost), Some(1)) - }, - Some(ToolKind::Axe) => { - // Axe - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)) - .with_skill(Skill::Axe(AxeSkill::DsCombo), None) - .with_skill(Skill::Axe(AxeSkill::DsDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::DsSpeed), Some(1)) - .with_skill(Skill::Axe(AxeSkill::DsRegen), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SInfinite), None) - .with_skill(Skill::Axe(AxeSkill::SHelicopter), None) - .with_skill(Skill::Axe(AxeSkill::SDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SSpeed), Some(1)) - .with_skill(Skill::Axe(AxeSkill::SCost), Some(1)) - .with_skill(Skill::Axe(AxeSkill::UnlockLeap), None) - .with_skill(Skill::Axe(AxeSkill::LDamage), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LKnockback), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LCost), Some(1)) - .with_skill(Skill::Axe(AxeSkill::LDistance), Some(1)) - }, - Some(ToolKind::Hammer) => { - // Hammer - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Hammer)) - .with_skill(Skill::Hammer(HammerSkill::SsKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsSpeed), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::SsRegen), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CDrain), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::CSpeed), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::UnlockLeap), None) - .with_skill(Skill::Hammer(HammerSkill::LDamage), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LCost), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LDistance), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LKnockback), Some(1)) - .with_skill(Skill::Hammer(HammerSkill::LRange), Some(1)) - }, - Some(ToolKind::Bow) => { - // Bow - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Bow)) - .with_skill(Skill::Bow(BowSkill::ProjSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::CRegen), Some(1)) - .with_skill(Skill::Bow(BowSkill::CKnockback), Some(1)) - .with_skill(Skill::Bow(BowSkill::CSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::CMove), Some(1)) - .with_skill(Skill::Bow(BowSkill::RDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::RSpeed), Some(1)) - .with_skill(Skill::Bow(BowSkill::RCost), Some(1)) - .with_skill(Skill::Bow(BowSkill::UnlockShotgun), None) - .with_skill(Skill::Bow(BowSkill::SDamage), Some(1)) - .with_skill(Skill::Bow(BowSkill::SSpread), Some(1)) - .with_skill(Skill::Bow(BowSkill::SArrows), Some(1)) - .with_skill(Skill::Bow(BowSkill::SCost), Some(1)) - }, - Some(ToolKind::Staff) => { - // Staff - Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Staff)) - .with_skill(Skill::Staff(StaffSkill::BDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::BRegen), Some(1)) - .with_skill(Skill::Staff(StaffSkill::BRadius), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FRange), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FDrain), Some(1)) - .with_skill(Skill::Staff(StaffSkill::FVelocity), Some(1)) - .with_skill(Skill::Staff(StaffSkill::UnlockShockwave), None) - .with_skill(Skill::Staff(StaffSkill::SDamage), Some(1)) - .with_skill(Skill::Staff(StaffSkill::SKnockback), Some(1)) - .with_skill(Skill::Staff(StaffSkill::SRange), Some(1)) - .with_skill(Skill::Staff(StaffSkill::SCost), Some(1)) - }, - _ => Self::default(), - } - }, - Some(Mindflayer) => Self::default() - .with_skill_group(SkillGroupKind::Weapon(ToolKind::Staff)) - .with_skill(Skill::Staff(StaffSkill::BDamage), Some(3)) - .with_skill(Skill::Staff(StaffSkill::BRegen), Some(2)) - .with_skill(Skill::Staff(StaffSkill::BRadius), Some(2)) - .with_skill(Skill::Staff(StaffSkill::FDamage), Some(3)) - .with_skill(Skill::Staff(StaffSkill::FRange), Some(2)) - .with_skill(Skill::Staff(StaffSkill::FDrain), Some(2)) - .with_skill(Skill::Staff(StaffSkill::FVelocity), Some(2)) - .with_skill(Skill::Staff(StaffSkill::UnlockShockwave), None) - .with_skill(Skill::Staff(StaffSkill::SDamage), Some(2)) - .with_skill(Skill::Staff(StaffSkill::SKnockback), Some(2)) - .with_skill(Skill::Staff(StaffSkill::SRange), Some(2)) - .with_skill(Skill::Staff(StaffSkill::SCost), Some(2)), - Some(Villager) | None => Self::default(), + builder.with_asset_expect(asset_specifier) + } + + /// Applies `asset_specifier` with needed skill tree + #[must_use] + pub fn with_asset_expect(mut self, asset_specifier: &str) -> Self { + let tree = skills_from_asset_expect(asset_specifier); + for (skill, level) in tree { + self = self.with_skill(skill, level); } + + self + } + + /// Creates `SkillSetBuilder` for given preset + #[must_use] + pub fn from_preset(preset: Preset) -> Self { + let builder = Self::default(); + + builder.with_preset(preset) + } + + /// Applies preset + #[must_use] + pub const fn with_preset(self, preset: Preset) -> Self { + match preset { + Preset::Empty => {}, + } + self } #[must_use] @@ -689,7 +103,6 @@ impl SkillSetBuilder { /// 2) If added skill already applied /// 3) If added skill wasn't applied at the end pub fn with_skill(mut self, skill: Skill, level: Option) -> Self { - #![warn(clippy::pedantic)] let group = if let Some(skill_group) = skill.skill_group_kind() { skill_group } else { @@ -697,12 +110,7 @@ impl SkillSetBuilder { "Tried to add skill: {:?} which does not have an associated skill group.", skill ); - if cfg!(test) { - panic!("{}", err); - } else { - warn!("{}", err); - } - return self; + common_base::dev_panic!(err, or return self); }; let SkillSetBuilder(ref mut skill_set) = self; @@ -711,12 +119,7 @@ impl SkillSetBuilder { "Tried to add skill: {:?} with level {:?} which is already applied", skill, level, ); - if cfg!(test) { - panic!("{}", err); - } else { - warn!("{}", err); - } - return self; + common_base::dev_panic!(err, or return self); } for _ in 0..level.unwrap_or(1) { skill_set.add_skill_points(group, skill_set.skill_cost(skill)); @@ -728,22 +131,16 @@ impl SkillSetBuilder { available and meets all prerequisite skills.", skill ); - if cfg!(test) { - panic!("{}", err); - } else { - warn!("{}", err); - } + common_base::dev_panic!(err); } self } - pub fn with_skill_group(self, skill_group: SkillGroupKind) -> Self { - self.with_skill(Skill::UnlockGroup(skill_group), None) - } - + #[must_use] pub fn build(self) -> SkillSet { self.0 } } +#[must_use] fn skill_is_applied(skill_set: &SkillSet, skill: Skill, level: Option) -> bool { if let Ok(applied_level) = skill_set.skill_level(skill) { applied_level == level @@ -751,3 +148,45 @@ fn skill_is_applied(skill_set: &SkillSet, skill: Skill, level: Option) -> b false } } + +#[cfg(test)] +mod tests { + use super::*; + use assets::Error; + + #[test] + fn test_all_skillset_assets() { + #[derive(Clone)] + struct SkillSetList(Vec); + + impl assets::Compound for SkillSetList { + fn load( + cache: &assets::AssetCache, + specifier: &str, + ) -> Result { + let list = cache + .load::(specifier)? + .read() + .iter() + .map(|spec| SkillSetTree::load_cloned(spec)) + .collect::>()?; + + Ok(Self(list)) + } + } + + let skillsets = SkillSetList::load_expect_cloned("common.skillset.*").0; + for skillset in skillsets { + std::mem::drop({ + let mut skillset_builder = SkillSetBuilder::default(); + let nodes = skillset.0; + let tree = skills_from_nodes(nodes); + for (skill, level) in tree { + skillset_builder = skillset_builder.with_skill(skill, level); + } + + skillset_builder + }); + } + } +} diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index 8b69c6a3ab..1c094b8883 100644 --- a/common/src/states/basic_summon.rs +++ b/common/src/states/basic_summon.rs @@ -6,7 +6,7 @@ use crate::{ }, event::{LocalEvent, ServerEvent}, outcome::Outcome, - skillset_builder::{SkillSetBuilder, SkillSetConfig}, + skillset_builder::{self, SkillSetBuilder}, states::{ behavior::{CharacterBehavior, JoinData}, utils::*, @@ -74,24 +74,33 @@ impl CharacterBehavior for Data { > self.static_data.cast_duration * self.summon_count / self.static_data.summon_amount { - let body = self.static_data.summon_info.body; + let SummonInfo { + body, + loadout_config, + skillset_config, + .. + } = self.static_data.summon_info; - let mut loadout_builder = - LoadoutBuilder::new().with_default_maintool(&body); + let loadout = { + let loadout_builder = + LoadoutBuilder::new().with_default_maintool(&body); + if let Some(preset) = loadout_config { + loadout_builder.with_preset(preset).build() + } else { + loadout_builder.build() + } + }; - if let Some(preset) = self.static_data.summon_info.loadout_config { - loadout_builder = loadout_builder.with_preset(preset); - } - - let loadout = loadout_builder.build(); + let skill_set = { + let skillset_builder = SkillSetBuilder::default(); + if let Some(preset) = skillset_config { + skillset_builder.with_preset(preset).build() + } else { + skillset_builder.build() + } + }; let stats = comp::Stats::new("Summon".to_string()); - let skill_set = SkillSetBuilder::build_skillset( - &None, - self.static_data.summon_info.skillset_config, - ) - .build(); - // Send server event to create npc update.server_events.push_front(ServerEvent::CreateNpc { pos: *data.pos, @@ -179,5 +188,5 @@ pub struct SummonInfo { health_scaling: u16, // TODO: use assets for specifying skills and loadout? loadout_config: Option, - skillset_config: Option, + skillset_config: Option, } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index b4decd5bad..458c710b28 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -195,44 +195,49 @@ impl<'a> System<'a> for Sys { } let EntityInfo { - skillset_preset, + skillset_asset, main_tool, - loadout_config, + loadout_asset, make_loadout, trading_information: economy, .. } = entity; - let skill_set = - SkillSetBuilder::build_skillset(&main_tool, skillset_preset).build(); + let skill_set = { + let skillset_builder = SkillSetBuilder::default(); + if let Some(skillset_asset) = skillset_asset { + skillset_builder.with_asset_expect(&skillset_asset).build() + } else { + skillset_builder.build() + } + }; - let mut loadout_builder = LoadoutBuilder::new(); - let rng = &mut rand::thread_rng(); + let loadout = { + let mut loadout_builder = LoadoutBuilder::new(); + let rng = &mut rand::thread_rng(); - // If main tool is passed, use it. Otherwise fallback to default tool - if let Some(main_tool) = main_tool { - loadout_builder = loadout_builder.active_mainhand(Some(main_tool)); - } else { - loadout_builder = loadout_builder.with_default_maintool(&body); - } + // If main tool is passed, use it. Otherwise fallback to default tool + if let Some(main_tool) = main_tool { + loadout_builder = loadout_builder.active_mainhand(Some(main_tool)); + } else { + loadout_builder = loadout_builder.with_default_maintool(&body); + } - // If there is config, apply it. - // If not, use default equipement for this body. - match loadout_config { - Some(asset) => { + // If there is config, apply it. + // If not, use default equipement for this body. + if let Some(asset) = loadout_asset { loadout_builder = loadout_builder.with_asset_expect(&asset, rng); - }, - None => { + } else { loadout_builder = loadout_builder.with_default_equipment(&body); - }, - } + } - // Evaluate lazy function for loadout creation - if let Some(make_loadout) = make_loadout { - loadout_builder = loadout_builder.with_creator(make_loadout, economy.as_ref()); - } - - let loadout = loadout_builder.build(); + // Evaluate lazy function for loadout creation + if let Some(make_loadout) = make_loadout { + loadout_builder = + loadout_builder.with_creator(make_loadout, economy.as_ref()); + } + loadout_builder.build() + }; let health = comp::Health::new(body, entity.level.unwrap_or(0)); let poise = comp::Poise::new(body); diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index dc06ebc758..2439027220 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -932,8 +932,7 @@ fn enemy_0(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { &comp::biped_small::Species::Gnarling, ), )) - .with_loot_drop(chosen.read().choose().to_item()) - .with_skillset_preset(common::skillset_builder::SkillSetConfig::Gnarling); + .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { 0 => gnarling.with_asset_expect("common.entity.dungeon.tier-0.bow"), @@ -949,7 +948,6 @@ fn enemy_1(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { .with_body(comp::Body::BipedSmall( comp::biped_small::Body::random_with(dynamic_rng, &comp::biped_small::Species::Adlet), )) - .with_skillset_preset(common::skillset_builder::SkillSetConfig::Adlet) .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { @@ -966,7 +964,6 @@ fn enemy_2(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { .with_body(comp::Body::BipedSmall( comp::biped_small::Body::random_with(dynamic_rng, &comp::biped_small::Species::Sahagin), )) - .with_skillset_preset(common::skillset_builder::SkillSetConfig::Sahagin) .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { @@ -994,7 +991,6 @@ fn enemy_3(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { &comp::biped_small::Species::Haniwa, ), )) - .with_skillset_preset(common::skillset_builder::SkillSetConfig::Haniwa) .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { @@ -1015,7 +1011,6 @@ fn enemy_4(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { &comp::biped_small::Species::Myrmidon, ), )) - .with_skillset_preset(common::skillset_builder::SkillSetConfig::Myrmidon) .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { @@ -1037,12 +1032,10 @@ fn enemy_5(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { )), 1 => entity .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) - .with_skillset_preset(common::skillset_builder::SkillSetConfig::Warlock) .with_loot_drop(chosen.read().choose().to_item()) .with_asset_expect("common.entity.dungeon.tier-5.warlock"), _ => entity .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) - .with_skillset_preset(common::skillset_builder::SkillSetConfig::Warlord) .with_loot_drop(chosen.read().choose().to_item()) .with_asset_expect("common.entity.dungeon.tier-5.warlord"), } @@ -1147,9 +1140,8 @@ fn boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec) -> Vec entity - .with_main_tool(Item::new_from_asset_expect( - "common.items.weapons.sword.iron-4", - )) - .with_name("Guard") .with_agent_mark(agent::Mark::Guard) .with_lazy_loadout(guard_loadout) .with_level(dynamic_rng.gen_range(10..15)) - .with_skillset_preset( - common::skillset_builder::SkillSetConfig::Guard, - ), + .with_asset_expect("common.entity.village.guard"), 1 | 2 => entity - .with_main_tool(Item::new_from_asset_expect( - "common.items.weapons.bow.eldwood-0", - )) - .with_name("Merchant") .with_agent_mark(agent::Mark::Merchant) .with_economy(&economy) .with_lazy_loadout(merchant_loadout) .with_level(dynamic_rng.gen_range(10..15)) - .with_skillset_preset( - common::skillset_builder::SkillSetConfig::Merchant, - ), + .with_asset_expect("common.entity.village.merchant"), _ => entity - .with_main_tool(Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..7) { - 0 => "common.items.weapons.tool.broom", - 1 => "common.items.weapons.tool.hoe", - 2 => "common.items.weapons.tool.pickaxe", - 3 => "common.items.weapons.tool.pitchfork", - 4 => "common.items.weapons.tool.rake", - 5 => "common.items.weapons.tool.shovel-0", - _ => "common.items.weapons.tool.shovel-1", - //_ => "common.items.weapons.bow.starter", TODO: Re-Add this when we have a better way of distributing npc_weapons here - }, - )) .with_lazy_loadout(villager_loadout) - .with_skillset_preset( - common::skillset_builder::SkillSetConfig::Villager, - ), + .with_asset_expect("common.entity.village.villager"), } }); From 229b253a1762e5bd2d202e0d110306939e13de01 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Tue, 8 Jun 2021 21:27:35 +0300 Subject: [PATCH 12/37] remove Mindflayer skillset --- assets/common/entity/dungeon/tier-5/boss.ron | 2 +- .../skillset/dungeon/tier-5/mindflayer.ron | 21 ------------------- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 assets/common/skillset/dungeon/tier-5/mindflayer.ron diff --git a/assets/common/entity/dungeon/tier-5/boss.ron b/assets/common/entity/dungeon/tier-5/boss.ron index 042951cee6..ffa83720a7 100644 --- a/assets/common/entity/dungeon/tier-5/boss.ron +++ b/assets/common/entity/dungeon/tier-5/boss.ron @@ -5,5 +5,5 @@ EntityConfig ( second_tool: None, loadout_asset: None, - skillset_asset: Some("common.skillset.dungeon.tier-5.mindflayer"), + skillset_asset: None, ) diff --git a/assets/common/skillset/dungeon/tier-5/mindflayer.ron b/assets/common/skillset/dungeon/tier-5/mindflayer.ron deleted file mode 100644 index 09cba3e4bd..0000000000 --- a/assets/common/skillset/dungeon/tier-5/mindflayer.ron +++ /dev/null @@ -1,21 +0,0 @@ -([ - Group(Weapon(Staff)), - - // Fireball - Skill((Staff(BDamage), Some(3))), - Skill((Staff(BRegen), Some(2))), - Skill((Staff(BRadius), Some(2))), - - // Flamethrower - Skill((Staff(FDamage), Some(3))), - Skill((Staff(FRange), Some(2))), - Skill((Staff(FDrain), Some(2))), - Skill((Staff(FVelocity), Some(2))), - - // Shockwave - Skill((Staff(UnlockShockwave), None)), - Skill((Staff(SDamage), Some(2))), - Skill((Staff(SKnockback), Some(2))), - Skill((Staff(SRange), Some(2))), - Skill((Staff(SCost), Some(2))), -]) From a4cc1e24ee3b7fbc2d9caa2c5515befb72c2aafc Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Wed, 9 Jun 2021 00:58:15 +0300 Subject: [PATCH 13/37] Move body to EntityConfig assets * currently works only for random and random_with, uses FromStr for NpcKind --- assets/common/entity/dungeon/tier-0/bow.ron | 1 + assets/common/entity/dungeon/tier-0/spear.ron | 1 + assets/common/entity/dungeon/tier-0/staff.ron | 1 + assets/common/entity/dungeon/tier-1/bow.ron | 1 + assets/common/entity/dungeon/tier-1/spear.ron | 1 + assets/common/entity/dungeon/tier-1/staff.ron | 1 + assets/common/entity/dungeon/tier-2/bow.ron | 1 + assets/common/entity/dungeon/tier-2/spear.ron | 1 + assets/common/entity/dungeon/tier-2/staff.ron | 1 + assets/common/entity/dungeon/tier-3/bow.ron | 1 + assets/common/entity/dungeon/tier-3/spear.ron | 1 + assets/common/entity/dungeon/tier-3/staff.ron | 1 + assets/common/entity/dungeon/tier-4/bow.ron | 1 + assets/common/entity/dungeon/tier-4/spear.ron | 1 + assets/common/entity/dungeon/tier-4/staff.ron | 1 + .../entity/dungeon/tier-5/beastmaster.ron | 1 + assets/common/entity/dungeon/tier-5/boss.ron | 1 + assets/common/entity/dungeon/tier-5/husk.ron | 1 + .../common/entity/dungeon/tier-5/warlock.ron | 1 + .../common/entity/dungeon/tier-5/warlord.ron | 1 + assets/common/entity/test.ron | 2 +- assets/common/entity/village/guard.ron | 2 ++ assets/common/entity/village/merchant.ron | 2 ++ assets/common/entity/village/villager.ron | 2 ++ common/src/generation.rs | 35 ++++++++++++++++++- world/src/site/dungeon/mod.rs | 28 --------------- 26 files changed, 61 insertions(+), 30 deletions(-) diff --git a/assets/common/entity/dungeon/tier-0/bow.ron b/assets/common/entity/dungeon/tier-0/bow.ron index e135808266..1c3f4539f2 100644 --- a/assets/common/entity/dungeon/tier-0/bow.ron +++ b/assets/common/entity/dungeon/tier-0/bow.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Gnarling Stalker"), + body: Some(RandomWith("gnarling")), main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.adlet_bow")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-0/spear.ron b/assets/common/entity/dungeon/tier-0/spear.ron index 72c3b46fa3..05cd042552 100644 --- a/assets/common/entity/dungeon/tier-0/spear.ron +++ b/assets/common/entity/dungeon/tier-0/spear.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Gnarling Mugger"), + body: Some(RandomWith("gnarling")), main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.wooden_spear")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-0/staff.ron b/assets/common/entity/dungeon/tier-0/staff.ron index d8928f39fe..43b1948197 100644 --- a/assets/common/entity/dungeon/tier-0/staff.ron +++ b/assets/common/entity/dungeon/tier-0/staff.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Gnarling Shaman"), + body: Some(RandomWith("gnarling")), main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.gnoll_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-1/bow.ron b/assets/common/entity/dungeon/tier-1/bow.ron index e7ea1ed0dc..51c93c9929 100644 --- a/assets/common/entity/dungeon/tier-1/bow.ron +++ b/assets/common/entity/dungeon/tier-1/bow.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Adlet Tracker"), + body: Some(RandomWith("adlet")), main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.adlet_bow")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-1/spear.ron b/assets/common/entity/dungeon/tier-1/spear.ron index 0f2d3322e5..9943b5b3c7 100644 --- a/assets/common/entity/dungeon/tier-1/spear.ron +++ b/assets/common/entity/dungeon/tier-1/spear.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Adlet Hunter"), + body: Some(RandomWith("adlet")), main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.wooden_spear")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-1/staff.ron b/assets/common/entity/dungeon/tier-1/staff.ron index 522ec1d415..d196d2654f 100644 --- a/assets/common/entity/dungeon/tier-1/staff.ron +++ b/assets/common/entity/dungeon/tier-1/staff.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Adlet Shaman"), + body: Some(RandomWith("adlet")), main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.gnoll_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-2/bow.ron b/assets/common/entity/dungeon/tier-2/bow.ron index 73950982ee..435199764c 100644 --- a/assets/common/entity/dungeon/tier-2/bow.ron +++ b/assets/common/entity/dungeon/tier-2/bow.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Sahagin Sniper"), + body: Some(RandomWith("sahagin")), main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.adlet_bow")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-2/spear.ron b/assets/common/entity/dungeon/tier-2/spear.ron index c9499cfa47..b7270579ff 100644 --- a/assets/common/entity/dungeon/tier-2/spear.ron +++ b/assets/common/entity/dungeon/tier-2/spear.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Sahagin Spearman"), + body: Some(RandomWith("sahagin")), main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.wooden_spear")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-2/staff.ron b/assets/common/entity/dungeon/tier-2/staff.ron index 4ecbcd121e..1bf74fc93e 100644 --- a/assets/common/entity/dungeon/tier-2/staff.ron +++ b/assets/common/entity/dungeon/tier-2/staff.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Sahagin Sorcerer"), + body: Some(RandomWith("sahagin")), main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.gnoll_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-3/bow.ron b/assets/common/entity/dungeon/tier-3/bow.ron index e642c37333..a667ca255b 100644 --- a/assets/common/entity/dungeon/tier-3/bow.ron +++ b/assets/common/entity/dungeon/tier-3/bow.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Haniwa Archer"), + body: Some(RandomWith("haniwa")), main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.adlet_bow")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-3/spear.ron b/assets/common/entity/dungeon/tier-3/spear.ron index 4c107bdd73..9d74bd0ba6 100644 --- a/assets/common/entity/dungeon/tier-3/spear.ron +++ b/assets/common/entity/dungeon/tier-3/spear.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Haniwa Guard"), + body: Some(RandomWith("haniwa")), main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.wooden_spear")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-3/staff.ron b/assets/common/entity/dungeon/tier-3/staff.ron index adfe8857e8..649c669272 100644 --- a/assets/common/entity/dungeon/tier-3/staff.ron +++ b/assets/common/entity/dungeon/tier-3/staff.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Haniwa Sorcerer"), + body: Some(RandomWith("haniwa")), main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.gnoll_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-4/bow.ron b/assets/common/entity/dungeon/tier-4/bow.ron index 04cf0e7b02..205cca7248 100644 --- a/assets/common/entity/dungeon/tier-4/bow.ron +++ b/assets/common/entity/dungeon/tier-4/bow.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Myrmidon Marksman"), + body: Some(RandomWith("myrmidon")), main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.adlet_bow")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-4/spear.ron b/assets/common/entity/dungeon/tier-4/spear.ron index d73950e575..c5688f6fe9 100644 --- a/assets/common/entity/dungeon/tier-4/spear.ron +++ b/assets/common/entity/dungeon/tier-4/spear.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Myrmidon Hoplite"), + body: Some(RandomWith("myrmidon")), main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.wooden_spear")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-4/staff.ron b/assets/common/entity/dungeon/tier-4/staff.ron index a022a4b330..2b4926db59 100644 --- a/assets/common/entity/dungeon/tier-4/staff.ron +++ b/assets/common/entity/dungeon/tier-4/staff.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Myrmidon Wizard"), + body: Some(RandomWith("myrmidon")), main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.gnoll_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-5/beastmaster.ron b/assets/common/entity/dungeon/tier-5/beastmaster.ron index 4c2421ac72..3bf5cac4f2 100644 --- a/assets/common/entity/dungeon/tier-5/beastmaster.ron +++ b/assets/common/entity/dungeon/tier-5/beastmaster.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Beastmaster"), + body: Some(RandomWith("humanoid")), main_tool: Some(Choice([ (1.0, Some(Item("common.items.weapons.axe.malachite_axe-0"))), diff --git a/assets/common/entity/dungeon/tier-5/boss.ron b/assets/common/entity/dungeon/tier-5/boss.ron index ffa83720a7..9f7c6d376a 100644 --- a/assets/common/entity/dungeon/tier-5/boss.ron +++ b/assets/common/entity/dungeon/tier-5/boss.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Mindflayer"), + body: Some(RandomWith("mindflayer")), main_tool: None, second_tool: None, diff --git a/assets/common/entity/dungeon/tier-5/husk.ron b/assets/common/entity/dungeon/tier-5/husk.ron index 6c23289a44..0409d98436 100644 --- a/assets/common/entity/dungeon/tier-5/husk.ron +++ b/assets/common/entity/dungeon/tier-5/husk.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Cultist Husk"), + body: Some(RandomWith("husk")), main_tool: None, second_tool: None, diff --git a/assets/common/entity/dungeon/tier-5/warlock.ron b/assets/common/entity/dungeon/tier-5/warlock.ron index 8e5fe4ae70..4553ea5ff9 100644 --- a/assets/common/entity/dungeon/tier-5/warlock.ron +++ b/assets/common/entity/dungeon/tier-5/warlock.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Cultist Warlock"), + body: Some(RandomWith("humanoid")), main_tool: Some(Item("common.items.weapons.staff.cultist_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-5/warlord.ron b/assets/common/entity/dungeon/tier-5/warlord.ron index 37430fc373..53d3605fb0 100644 --- a/assets/common/entity/dungeon/tier-5/warlord.ron +++ b/assets/common/entity/dungeon/tier-5/warlord.ron @@ -1,5 +1,6 @@ EntityConfig ( name: Some("Cultist Warlord"), + body: Some(RandomWith("humanoid")), main_tool: Some(Choice([ (1.0, Some(Item("common.items.weapons.axe_1h.orichalcum-0"))), diff --git a/assets/common/entity/test.ron b/assets/common/entity/test.ron index 915f98ade2..e967f36b6c 100644 --- a/assets/common/entity/test.ron +++ b/assets/common/entity/test.ron @@ -6,7 +6,7 @@ EntityConfig ( /// Can be Exact (Body with all fields e.g BodyType, Species, Hair color and such) /// or Random (will use random if available for this Body) /// or RandomWith (will use random_with if available for this Body) - // body: Humanoid(Random), + body: Some(RandomWith("humanoid")), /// Main and second tools /// Can be Option (with asset_specifier for item) diff --git a/assets/common/entity/village/guard.ron b/assets/common/entity/village/guard.ron index 93d770cb2c..6332487f7a 100644 --- a/assets/common/entity/village/guard.ron +++ b/assets/common/entity/village/guard.ron @@ -1,5 +1,7 @@ EntityConfig ( name: Some("Guard"), + // body is specified outsite + body: None, main_tool: Some(Item("common.items.weapons.sword.iron-4")), second_tool: None, diff --git a/assets/common/entity/village/merchant.ron b/assets/common/entity/village/merchant.ron index 07dd497fdf..c1b39be174 100644 --- a/assets/common/entity/village/merchant.ron +++ b/assets/common/entity/village/merchant.ron @@ -1,5 +1,7 @@ EntityConfig ( name: Some("Merchant"), + // body is specified outsite + body: None, main_tool: Some(Item("common.items.weapons.bow.eldwood-0")), second_tool: None, diff --git a/assets/common/entity/village/villager.ron b/assets/common/entity/village/villager.ron index 45a92d5abb..bef5f5bbad 100644 --- a/assets/common/entity/village/villager.ron +++ b/assets/common/entity/village/villager.ron @@ -1,5 +1,7 @@ EntityConfig ( name: None, + // body is specified outsite + body: None, main_tool: Some(Choice([ (1.0, Some(Item("common.items.weapons.tool.broom"))), diff --git a/common/src/generation.rs b/common/src/generation.rs index f31aa7628e..131815bbe2 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -12,9 +12,15 @@ use crate::{ use serde::Deserialize; use vek::*; +#[derive(Debug, Deserialize, Clone)] +enum BodyKind { + RandomWith(String), +} + #[derive(Debug, Deserialize, Clone)] struct EntityConfig { name: Option, + body: Option, main_tool: Option, second_tool: Option, loadout_asset: Option, @@ -86,6 +92,7 @@ impl EntityInfo { fn with_entity_config(mut self, config: EntityConfig, asset_specifier: Option<&str>) -> Self { let EntityConfig { name, + body, main_tool, second_tool, loadout_asset, @@ -96,6 +103,19 @@ impl EntityInfo { self = self.with_name(name); } + if let Some(body) = body { + match body { + BodyKind::RandomWith(string) => { + let npc::NpcBody(_body_kind, mut body_creator) = + string.parse::().unwrap_or_else(|err| { + panic!("failed to parse body {:?}. Err: {:?}", &string, err) + }); + let body = body_creator(); + self = self.with_body(body); + }, + } + } + let rng = &mut rand::thread_rng(); if let Some(main_tool) = main_tool.and_then(|i| i.try_to_item(asset_specifier.unwrap_or("??"), rng)) @@ -296,7 +316,8 @@ mod tests { second_tool, loadout_asset, skillset_asset, - .. + name: _name, + body, } = config; if let Some(main_tool) = main_tool { @@ -307,6 +328,18 @@ mod tests { second_tool.validate(EquipSlot::ActiveOffhand); } + if let Some(body) = body { + match body { + BodyKind::RandomWith(string) => { + let npc::NpcBody(_body_kind, mut body_creator) = + string.parse::().unwrap_or_else(|err| { + panic!("failed to parse body {:?}. Err: {:?}", &string, err) + }); + std::mem::drop(body_creator()); + }, + } + } + if let Some(loadout_asset) = loadout_asset { let rng = &mut rand::thread_rng(); let builder = LoadoutBuilder::default(); diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 2439027220..7fc3cd0410 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -926,12 +926,6 @@ fn enemy_0(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-0.enemy"); let gnarling = entity - .with_body(comp::Body::BipedSmall( - comp::biped_small::Body::random_with( - dynamic_rng, - &comp::biped_small::Species::Gnarling, - ), - )) .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { @@ -945,9 +939,6 @@ fn enemy_1(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-1.enemy"); let adlet = entity - .with_body(comp::Body::BipedSmall( - comp::biped_small::Body::random_with(dynamic_rng, &comp::biped_small::Species::Adlet), - )) .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { @@ -961,9 +952,6 @@ fn enemy_2(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-2.enemy"); let sahagin = entity - .with_body(comp::Body::BipedSmall( - comp::biped_small::Body::random_with(dynamic_rng, &comp::biped_small::Species::Sahagin), - )) .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { @@ -985,12 +973,6 @@ fn enemy_3(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { )), _ => { let haniwa = entity - .with_body(comp::Body::BipedSmall( - comp::biped_small::Body::random_with( - dynamic_rng, - &comp::biped_small::Species::Haniwa, - ), - )) .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { @@ -1005,12 +987,6 @@ fn enemy_4(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-4.enemy"); let myrmidon = entity - .with_body(comp::Body::BipedSmall( - comp::biped_small::Body::random_with( - dynamic_rng, - &comp::biped_small::Species::Myrmidon, - ), - )) .with_loot_drop(chosen.read().choose().to_item()); match dynamic_rng.gen_range(0..5) { @@ -1031,11 +1007,9 @@ fn enemy_5(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { "common.items.crafting_ing.twigs", )), 1 => entity - .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) .with_loot_drop(chosen.read().choose().to_item()) .with_asset_expect("common.entity.dungeon.tier-5.warlock"), _ => entity - .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) .with_loot_drop(chosen.read().choose().to_item()) .with_asset_expect("common.entity.dungeon.tier-5.warlord"), } @@ -1308,7 +1282,6 @@ mod tests { boss_fallback(&mut dynamic_rng, tile_wcenter); } - //FIXME: it will miss items with rng branching #[test] fn test_creating_enemies() { let mut dynamic_rng = rand::thread_rng(); @@ -1322,7 +1295,6 @@ mod tests { enemy_fallback(&mut dynamic_rng, raw_entity); } - //FIXME: it will miss items with rng branching #[test] fn test_creating_minibosses() { let mut dynamic_rng = rand::thread_rng(); From 057aa7fecffcf7e17f4f149d7c2b5885514c12af Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Wed, 9 Jun 2021 01:07:48 +0300 Subject: [PATCH 14/37] Move loot tables to entityconfigs * Moved all entities in dungeons to assets --- .../common/entity/dungeon/fallback/boss.ron | 12 + .../common/entity/dungeon/fallback/enemy.ron | 20 ++ .../entity/dungeon/fallback/miniboss.ron | 12 + assets/common/entity/dungeon/tier-0/boss.ron | 12 + assets/common/entity/dungeon/tier-0/bow.ron | 2 + .../common/entity/dungeon/tier-0/miniboss.ron | 12 + assets/common/entity/dungeon/tier-0/spear.ron | 2 + assets/common/entity/dungeon/tier-0/staff.ron | 2 + assets/common/entity/dungeon/tier-1/boss.ron | 12 + assets/common/entity/dungeon/tier-1/bow.ron | 2 + assets/common/entity/dungeon/tier-1/rat.ron | 12 + assets/common/entity/dungeon/tier-1/spear.ron | 2 + assets/common/entity/dungeon/tier-1/staff.ron | 2 + assets/common/entity/dungeon/tier-2/boss.ron | 12 + assets/common/entity/dungeon/tier-2/bow.ron | 2 + .../common/entity/dungeon/tier-2/hakulaq.ron | 12 + assets/common/entity/dungeon/tier-2/spear.ron | 2 + assets/common/entity/dungeon/tier-2/staff.ron | 2 + .../entity/dungeon/tier-3/bonerattler.ron | 12 + assets/common/entity/dungeon/tier-3/boss.ron | 12 + assets/common/entity/dungeon/tier-3/bow.ron | 2 + .../common/entity/dungeon/tier-3/sentry.ron | 12 + assets/common/entity/dungeon/tier-3/spear.ron | 2 + assets/common/entity/dungeon/tier-3/staff.ron | 2 + assets/common/entity/dungeon/tier-4/boss.ron | 12 + assets/common/entity/dungeon/tier-4/bow.ron | 2 + .../common/entity/dungeon/tier-4/miniboss.ron | 12 + assets/common/entity/dungeon/tier-4/spear.ron | 2 + assets/common/entity/dungeon/tier-4/staff.ron | 2 + .../entity/dungeon/tier-5/beastmaster.ron | 4 +- assets/common/entity/dungeon/tier-5/boss.ron | 2 + assets/common/entity/dungeon/tier-5/hound.ron | 12 + assets/common/entity/dungeon/tier-5/husk.ron | 2 + .../common/entity/dungeon/tier-5/turret.ron | 12 + .../common/entity/dungeon/tier-5/warlock.ron | 2 + .../common/entity/dungeon/tier-5/warlord.ron | 2 + assets/common/entity/test.ron | 5 +- assets/common/entity/village/guard.ron | 2 + assets/common/entity/village/merchant.ron | 3 + assets/common/entity/village/villager.ron | 2 + common/src/generation.rs | 38 +- world/src/site/dungeon/mod.rs | 337 +++++------------- 42 files changed, 373 insertions(+), 256 deletions(-) create mode 100644 assets/common/entity/dungeon/fallback/boss.ron create mode 100644 assets/common/entity/dungeon/fallback/enemy.ron create mode 100644 assets/common/entity/dungeon/fallback/miniboss.ron create mode 100644 assets/common/entity/dungeon/tier-0/boss.ron create mode 100644 assets/common/entity/dungeon/tier-0/miniboss.ron create mode 100644 assets/common/entity/dungeon/tier-1/boss.ron create mode 100644 assets/common/entity/dungeon/tier-1/rat.ron create mode 100644 assets/common/entity/dungeon/tier-2/boss.ron create mode 100644 assets/common/entity/dungeon/tier-2/hakulaq.ron create mode 100644 assets/common/entity/dungeon/tier-3/bonerattler.ron create mode 100644 assets/common/entity/dungeon/tier-3/boss.ron create mode 100644 assets/common/entity/dungeon/tier-3/sentry.ron create mode 100644 assets/common/entity/dungeon/tier-4/boss.ron create mode 100644 assets/common/entity/dungeon/tier-4/miniboss.ron create mode 100644 assets/common/entity/dungeon/tier-5/hound.ron create mode 100644 assets/common/entity/dungeon/tier-5/turret.ron diff --git a/assets/common/entity/dungeon/fallback/boss.ron b/assets/common/entity/dungeon/fallback/boss.ron new file mode 100644 index 0000000000..caf7fc2256 --- /dev/null +++ b/assets/common/entity/dungeon/fallback/boss.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Crazy Sheep"), + body: Some(RandomWith("sheep")), + + loot: Some(LootTable("common.loot_tables.fallback")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/fallback/enemy.ron b/assets/common/entity/dungeon/fallback/enemy.ron new file mode 100644 index 0000000000..63e9172578 --- /dev/null +++ b/assets/common/entity/dungeon/fallback/enemy.ron @@ -0,0 +1,20 @@ +EntityConfig ( + name: Some("Yan Hus"), + body: Some(RandomWith("humanoid")), + + loot: Some(LootTable("common.loot_tables.fallback")), + + main_tool: Some(Choice([ + (1.0, Some(Item("common.items.weapons.tool.broom"))), + (1.0, Some(Item("common.items.weapons.tool.hoe"))), + (1.0, Some(Item("common.items.weapons.tool.pickaxe"))), + (1.0, Some(Item("common.items.weapons.tool.rake"))), + (1.0, Some(Item("common.items.weapons.tool.shovel-0"))), + (1.0, Some(Item("common.items.weapons.tool.shovel-1"))), + (1.0, Some(Item("common.items.weapons.bow.bone-1"))), + ])), + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/fallback/miniboss.ron b/assets/common/entity/dungeon/fallback/miniboss.ron new file mode 100644 index 0000000000..6f7ba8171e --- /dev/null +++ b/assets/common/entity/dungeon/fallback/miniboss.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Big Goose"), + body: Some(RandomWith("goose")), + + loot: Some(LootTable("common.loot_tables.fallback")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-0/boss.ron b/assets/common/entity/dungeon/tier-0/boss.ron new file mode 100644 index 0000000000..98483efc4d --- /dev/null +++ b/assets/common/entity/dungeon/tier-0/boss.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Harvester"), + body: Some(RandomWith("harvester")), + + loot: Some(LootTable("common.loot_tables.dungeon.tier-0.boss")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-0/bow.ron b/assets/common/entity/dungeon/tier-0/bow.ron index 1c3f4539f2..be2948eb55 100644 --- a/assets/common/entity/dungeon/tier-0/bow.ron +++ b/assets/common/entity/dungeon/tier-0/bow.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Gnarling Stalker"), body: Some(RandomWith("gnarling")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-0.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.adlet_bow")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-0/miniboss.ron b/assets/common/entity/dungeon/tier-0/miniboss.ron new file mode 100644 index 0000000000..411ef1a7b1 --- /dev/null +++ b/assets/common/entity/dungeon/tier-0/miniboss.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Deadwood"), + body: Some(RandomWith("deadwood")), + + loot: Some(LootTable("common.loot_tables.dungeon.tier-0.miniboss")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-0/spear.ron b/assets/common/entity/dungeon/tier-0/spear.ron index 05cd042552..e5a4d9e2ad 100644 --- a/assets/common/entity/dungeon/tier-0/spear.ron +++ b/assets/common/entity/dungeon/tier-0/spear.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Gnarling Mugger"), body: Some(RandomWith("gnarling")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-0.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.wooden_spear")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-0/staff.ron b/assets/common/entity/dungeon/tier-0/staff.ron index 43b1948197..7202904324 100644 --- a/assets/common/entity/dungeon/tier-0/staff.ron +++ b/assets/common/entity/dungeon/tier-0/staff.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Gnarling Shaman"), body: Some(RandomWith("gnarling")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-0.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.gnarling.gnoll_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-1/boss.ron b/assets/common/entity/dungeon/tier-1/boss.ron new file mode 100644 index 0000000000..30035d7c3f --- /dev/null +++ b/assets/common/entity/dungeon/tier-1/boss.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Yeti"), + body: Some(RandomWith("yeti")), + + loot: Some(LootTable("common.loot_tables.dungeon.tier-1.boss")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-1/bow.ron b/assets/common/entity/dungeon/tier-1/bow.ron index 51c93c9929..013fd7c04c 100644 --- a/assets/common/entity/dungeon/tier-1/bow.ron +++ b/assets/common/entity/dungeon/tier-1/bow.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Adlet Tracker"), body: Some(RandomWith("adlet")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-1.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.adlet_bow")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-1/rat.ron b/assets/common/entity/dungeon/tier-1/rat.ron new file mode 100644 index 0000000000..565057282d --- /dev/null +++ b/assets/common/entity/dungeon/tier-1/rat.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Rat"), + body: Some(RandomWith("rat")), + + loot: Some(LootTable("common.loot_tables.creature.quad_small.generic")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-1/spear.ron b/assets/common/entity/dungeon/tier-1/spear.ron index 9943b5b3c7..0ce2a1b28c 100644 --- a/assets/common/entity/dungeon/tier-1/spear.ron +++ b/assets/common/entity/dungeon/tier-1/spear.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Adlet Hunter"), body: Some(RandomWith("adlet")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-1.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.wooden_spear")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-1/staff.ron b/assets/common/entity/dungeon/tier-1/staff.ron index d196d2654f..d9dc6d2a3e 100644 --- a/assets/common/entity/dungeon/tier-1/staff.ron +++ b/assets/common/entity/dungeon/tier-1/staff.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Adlet Shaman"), body: Some(RandomWith("adlet")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-1.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.adlet.gnoll_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-2/boss.ron b/assets/common/entity/dungeon/tier-2/boss.ron new file mode 100644 index 0000000000..4db8d8d5d2 --- /dev/null +++ b/assets/common/entity/dungeon/tier-2/boss.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Tidal Warrior"), + body: Some(RandomWith("tidalwarrior")), + + loot: Some(LootTable("common.loot_tables.dungeon.tier-2.boss")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-2/bow.ron b/assets/common/entity/dungeon/tier-2/bow.ron index 435199764c..abd5927b14 100644 --- a/assets/common/entity/dungeon/tier-2/bow.ron +++ b/assets/common/entity/dungeon/tier-2/bow.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Sahagin Sniper"), body: Some(RandomWith("sahagin")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-2.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.adlet_bow")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-2/hakulaq.ron b/assets/common/entity/dungeon/tier-2/hakulaq.ron new file mode 100644 index 0000000000..8f279e3943 --- /dev/null +++ b/assets/common/entity/dungeon/tier-2/hakulaq.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Hakulaq"), + body: Some(RandomWith("hakulaq")), + + loot: Some(LootTable("common.loot_tables.creature.quad_low.fanged")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-2/spear.ron b/assets/common/entity/dungeon/tier-2/spear.ron index b7270579ff..bce3699599 100644 --- a/assets/common/entity/dungeon/tier-2/spear.ron +++ b/assets/common/entity/dungeon/tier-2/spear.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Sahagin Spearman"), body: Some(RandomWith("sahagin")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-2.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.wooden_spear")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-2/staff.ron b/assets/common/entity/dungeon/tier-2/staff.ron index 1bf74fc93e..e41cddb070 100644 --- a/assets/common/entity/dungeon/tier-2/staff.ron +++ b/assets/common/entity/dungeon/tier-2/staff.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Sahagin Sorcerer"), body: Some(RandomWith("sahagin")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-2.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.sahagin.gnoll_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-3/bonerattler.ron b/assets/common/entity/dungeon/tier-3/bonerattler.ron new file mode 100644 index 0000000000..1fbf9329eb --- /dev/null +++ b/assets/common/entity/dungeon/tier-3/bonerattler.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Bonerattler"), + body: Some(RandomWith("bonerattler")), + + loot: Some(LootTable("common.loot_tables.creature.quad_medium.carapace")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-3/boss.ron b/assets/common/entity/dungeon/tier-3/boss.ron new file mode 100644 index 0000000000..cf6ddc11ed --- /dev/null +++ b/assets/common/entity/dungeon/tier-3/boss.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Clay Golem"), + body: Some(RandomWith("claygolem")), + + loot: Some(LootTable("common.loot_tables.dungeon.tier-3.boss")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-3/bow.ron b/assets/common/entity/dungeon/tier-3/bow.ron index a667ca255b..f693a38d03 100644 --- a/assets/common/entity/dungeon/tier-3/bow.ron +++ b/assets/common/entity/dungeon/tier-3/bow.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Haniwa Archer"), body: Some(RandomWith("haniwa")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-3.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.adlet_bow")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-3/sentry.ron b/assets/common/entity/dungeon/tier-3/sentry.ron new file mode 100644 index 0000000000..724f259ee4 --- /dev/null +++ b/assets/common/entity/dungeon/tier-3/sentry.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Haniwa Sentry"), + body: None, + + loot: Some(Item("common.items.crafting_ing.stones")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-3/spear.ron b/assets/common/entity/dungeon/tier-3/spear.ron index 9d74bd0ba6..376d2d67e6 100644 --- a/assets/common/entity/dungeon/tier-3/spear.ron +++ b/assets/common/entity/dungeon/tier-3/spear.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Haniwa Guard"), body: Some(RandomWith("haniwa")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-3.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.wooden_spear")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-3/staff.ron b/assets/common/entity/dungeon/tier-3/staff.ron index 649c669272..f8d051f2b7 100644 --- a/assets/common/entity/dungeon/tier-3/staff.ron +++ b/assets/common/entity/dungeon/tier-3/staff.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Haniwa Sorcerer"), body: Some(RandomWith("haniwa")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-3.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.haniwa.gnoll_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-4/boss.ron b/assets/common/entity/dungeon/tier-4/boss.ron new file mode 100644 index 0000000000..5f8883c1dd --- /dev/null +++ b/assets/common/entity/dungeon/tier-4/boss.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Minotaur"), + body: Some(RandomWith("minotaur")), + + loot: Some(LootTable("common.loot_tables.dungeon.tier-4.boss")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-4/bow.ron b/assets/common/entity/dungeon/tier-4/bow.ron index 205cca7248..04ec56dc59 100644 --- a/assets/common/entity/dungeon/tier-4/bow.ron +++ b/assets/common/entity/dungeon/tier-4/bow.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Myrmidon Marksman"), body: Some(RandomWith("myrmidon")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-4.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.adlet_bow")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-4/miniboss.ron b/assets/common/entity/dungeon/tier-4/miniboss.ron new file mode 100644 index 0000000000..46ba728ead --- /dev/null +++ b/assets/common/entity/dungeon/tier-4/miniboss.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Dullahan"), + body: Some(RandomWith("dullahan")), + + loot: Some(LootTable("common.loot_tables.dungeon.tier-4.miniboss")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-4/spear.ron b/assets/common/entity/dungeon/tier-4/spear.ron index c5688f6fe9..dffc6440dd 100644 --- a/assets/common/entity/dungeon/tier-4/spear.ron +++ b/assets/common/entity/dungeon/tier-4/spear.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Myrmidon Hoplite"), body: Some(RandomWith("myrmidon")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-4.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.wooden_spear")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-4/staff.ron b/assets/common/entity/dungeon/tier-4/staff.ron index 2b4926db59..8d432875d2 100644 --- a/assets/common/entity/dungeon/tier-4/staff.ron +++ b/assets/common/entity/dungeon/tier-4/staff.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Myrmidon Wizard"), body: Some(RandomWith("myrmidon")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-4.enemy")), + main_tool: Some(Item("common.items.npc_weapons.biped_small.myrmidon.gnoll_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-5/beastmaster.ron b/assets/common/entity/dungeon/tier-5/beastmaster.ron index 3bf5cac4f2..29987a13e2 100644 --- a/assets/common/entity/dungeon/tier-5/beastmaster.ron +++ b/assets/common/entity/dungeon/tier-5/beastmaster.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Beastmaster"), body: Some(RandomWith("humanoid")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-5.miniboss")), + main_tool: Some(Choice([ (1.0, Some(Item("common.items.weapons.axe.malachite_axe-0"))), (1.0, Some(Item("common.items.weapons.sword.bloodsteel-1"))), @@ -10,6 +12,6 @@ EntityConfig ( second_tool: None, loadout_asset: Some("common.loadout.dungeon.tier-5.beastmaster"), - // TODO: make skillset for him, I'm just too lazy + // TODO: make own skillset for him? skillset_asset: Some("common.skillset.dungeon.tier-5.enemy"), ) diff --git a/assets/common/entity/dungeon/tier-5/boss.ron b/assets/common/entity/dungeon/tier-5/boss.ron index 9f7c6d376a..9957ec5482 100644 --- a/assets/common/entity/dungeon/tier-5/boss.ron +++ b/assets/common/entity/dungeon/tier-5/boss.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Mindflayer"), body: Some(RandomWith("mindflayer")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-5.boss")), + main_tool: None, second_tool: None, diff --git a/assets/common/entity/dungeon/tier-5/hound.ron b/assets/common/entity/dungeon/tier-5/hound.ron new file mode 100644 index 0000000000..0b78c67746 --- /dev/null +++ b/assets/common/entity/dungeon/tier-5/hound.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Tamed Darkhound"), + body: Some(RandomWith("darkhound")), + + loot: Some(LootTable("common.loot_tables.dungeon.tier-5.minion")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-5/husk.ron b/assets/common/entity/dungeon/tier-5/husk.ron index 0409d98436..a28e46db0e 100644 --- a/assets/common/entity/dungeon/tier-5/husk.ron +++ b/assets/common/entity/dungeon/tier-5/husk.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Cultist Husk"), body: Some(RandomWith("husk")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-5.minion")), + main_tool: None, second_tool: None, diff --git a/assets/common/entity/dungeon/tier-5/turret.ron b/assets/common/entity/dungeon/tier-5/turret.ron new file mode 100644 index 0000000000..bb84413d6a --- /dev/null +++ b/assets/common/entity/dungeon/tier-5/turret.ron @@ -0,0 +1,12 @@ +EntityConfig ( + name: Some("Possessed Turret"), + body: None, + + loot: Some(Item("common.items.crafting_ing.twigs")), + + main_tool: None, + second_tool: None, + + loadout_asset: None, + skillset_asset: None, +) diff --git a/assets/common/entity/dungeon/tier-5/warlock.ron b/assets/common/entity/dungeon/tier-5/warlock.ron index 4553ea5ff9..0932bd714c 100644 --- a/assets/common/entity/dungeon/tier-5/warlock.ron +++ b/assets/common/entity/dungeon/tier-5/warlock.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Cultist Warlock"), body: Some(RandomWith("humanoid")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-5.enemy")), + main_tool: Some(Item("common.items.weapons.staff.cultist_staff")), second_tool: None, diff --git a/assets/common/entity/dungeon/tier-5/warlord.ron b/assets/common/entity/dungeon/tier-5/warlord.ron index 53d3605fb0..b85cb504de 100644 --- a/assets/common/entity/dungeon/tier-5/warlord.ron +++ b/assets/common/entity/dungeon/tier-5/warlord.ron @@ -2,6 +2,8 @@ EntityConfig ( name: Some("Cultist Warlord"), body: Some(RandomWith("humanoid")), + loot: Some(LootTable("common.loot_tables.dungeon.tier-5.enemy")), + main_tool: Some(Choice([ (1.0, Some(Item("common.items.weapons.axe_1h.orichalcum-0"))), (2.0, Some(Item("common.items.weapons.sword.cultist"))), diff --git a/assets/common/entity/test.ron b/assets/common/entity/test.ron index e967f36b6c..20c8ff8fb6 100644 --- a/assets/common/entity/test.ron +++ b/assets/common/entity/test.ron @@ -4,8 +4,7 @@ EntityConfig ( /// Body /// Can be Exact (Body with all fields e.g BodyType, Species, Hair color and such) - /// or Random (will use random if available for this Body) - /// or RandomWith (will use random_with if available for this Body) + /// or RandomWith (will generate random body or species) body: Some(RandomWith("humanoid")), /// Main and second tools @@ -24,7 +23,7 @@ EntityConfig ( /// Loot /// Can be Item (with asset_specifier for item) /// or LootTable (with asset_specifier for loot table) - // loot: LootTable("common.loot_tables.humanoids"), + loot: Some(LootTable("common.loot_tables.humanoids")), /// Meta Info (level, alignment, agency, etc) // meta: {}, diff --git a/assets/common/entity/village/guard.ron b/assets/common/entity/village/guard.ron index 6332487f7a..a80314380c 100644 --- a/assets/common/entity/village/guard.ron +++ b/assets/common/entity/village/guard.ron @@ -3,6 +3,8 @@ EntityConfig ( // body is specified outsite body: None, + loot: None, + main_tool: Some(Item("common.items.weapons.sword.iron-4")), second_tool: None, diff --git a/assets/common/entity/village/merchant.ron b/assets/common/entity/village/merchant.ron index c1b39be174..7fb1de003d 100644 --- a/assets/common/entity/village/merchant.ron +++ b/assets/common/entity/village/merchant.ron @@ -3,6 +3,9 @@ EntityConfig ( // body is specified outsite body: None, + // considering giving some gold/gems/materials? + loot: None, + main_tool: Some(Item("common.items.weapons.bow.eldwood-0")), second_tool: None, diff --git a/assets/common/entity/village/villager.ron b/assets/common/entity/village/villager.ron index bef5f5bbad..37d6528807 100644 --- a/assets/common/entity/village/villager.ron +++ b/assets/common/entity/village/villager.ron @@ -3,6 +3,8 @@ EntityConfig ( // body is specified outsite body: None, + loot: None, + main_tool: Some(Choice([ (1.0, Some(Item("common.items.weapons.tool.broom"))), (1.0, Some(Item("common.items.weapons.tool.hoe"))), diff --git a/common/src/generation.rs b/common/src/generation.rs index 131815bbe2..4994a93702 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -5,6 +5,7 @@ use crate::{ inventory::loadout_builder::{ItemSpec, LoadoutBuilder}, Alignment, Body, Item, }, + lottery::{LootSpec, Lottery}, npc::{self, NPC_NAMES}, trade, trade::SiteInformation, @@ -17,10 +18,17 @@ enum BodyKind { RandomWith(String), } +#[derive(Debug, Deserialize, Clone)] +enum LootKind { + Item(String), + LootTable(String), +} + #[derive(Debug, Deserialize, Clone)] struct EntityConfig { name: Option, body: Option, + loot: Option, main_tool: Option, second_tool: Option, loadout_asset: Option, @@ -93,6 +101,7 @@ impl EntityInfo { let EntityConfig { name, body, + loot, main_tool, second_tool, loadout_asset, @@ -116,6 +125,19 @@ impl EntityInfo { } } + if let Some(loot) = loot { + match loot { + LootKind::Item(asset) => { + self = self.with_loot_drop(Item::new_from_asset_expect(&asset)); + }, + LootKind::LootTable(asset) => { + let table = Lottery::::load_expect(&asset); + let drop = table.read().choose().to_item(); + self = self.with_loot_drop(drop); + }, + } + } + let rng = &mut rand::thread_rng(); if let Some(main_tool) = main_tool.and_then(|i| i.try_to_item(asset_specifier.unwrap_or("??"), rng)) @@ -318,6 +340,7 @@ mod tests { skillset_asset, name: _name, body, + loot, } = config; if let Some(main_tool) = main_tool { @@ -335,7 +358,20 @@ mod tests { string.parse::().unwrap_or_else(|err| { panic!("failed to parse body {:?}. Err: {:?}", &string, err) }); - std::mem::drop(body_creator()); + let _ = body_creator(); + }, + } + } + + if let Some(loot) = loot { + match loot { + LootKind::Item(asset) => { + std::mem::drop(Item::new_from_asset_expect(&asset)); + }, + LootKind::LootTable(asset) => { + // we need to just load it check if it exists, + // because all loot tables are tested in Lottery module + let _ = Lottery::::load_expect(&asset); }, } } diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 7fc3cd0410..aa6b6bda4a 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -9,11 +9,10 @@ use crate::{ }; use common::{ - assets::{AssetExt, AssetHandle}, + assets::AssetHandle, astar::Astar, comp::{self}, generation::{ChunkSupplement, EntityInfo}, - lottery::{LootSpec, Lottery}, store::{Id, Store}, terrain::{Block, BlockKind, SpriteKind, Structure, StructuresGroup, TerrainChunkSize}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol}, @@ -567,7 +566,7 @@ impl Floor { 3 => enemy_3(dynamic_rng, raw_entity), 4 => enemy_4(dynamic_rng, raw_entity), 5 => enemy_5(dynamic_rng, raw_entity), - _ => enemy_fallback(dynamic_rng, raw_entity), + _ => enemy_fallback(raw_entity), }; supplement.add_entity( entity.with_alignment(comp::Alignment::Enemy).with_level( @@ -597,13 +596,13 @@ impl Floor { if tile_pos == boss_spawn_tile && tile_wcenter.xy() == wpos2d { let entities = match room.difficulty { - 0 => boss_0(dynamic_rng, tile_wcenter), - 1 => boss_1(dynamic_rng, tile_wcenter), - 2 => boss_2(dynamic_rng, tile_wcenter), - 3 => boss_3(dynamic_rng, tile_wcenter), - 4 => boss_4(dynamic_rng, tile_wcenter), - 5 => boss_5(dynamic_rng, tile_wcenter), - _ => boss_fallback(dynamic_rng, tile_wcenter), + 0 => boss_0(tile_wcenter), + 1 => boss_1(tile_wcenter), + 2 => boss_2(tile_wcenter), + 3 => boss_3(tile_wcenter), + 4 => boss_4(tile_wcenter), + 5 => boss_5(tile_wcenter), + _ => boss_fallback(tile_wcenter), }; for entity in entities { @@ -640,13 +639,13 @@ impl Floor { if tile_pos == miniboss_spawn_tile && tile_wcenter.xy() == wpos2d { let entities = match room.difficulty { - 0 => mini_boss_0(dynamic_rng, tile_wcenter), - 1 => mini_boss_1(dynamic_rng, tile_wcenter), - 2 => mini_boss_2(dynamic_rng, tile_wcenter), - 3 => mini_boss_3(dynamic_rng, tile_wcenter), - 4 => mini_boss_4(dynamic_rng, tile_wcenter), + 0 => mini_boss_0(tile_wcenter), + 1 => mini_boss_1(tile_wcenter), + 2 => mini_boss_2(tile_wcenter), + 3 => mini_boss_3(tile_wcenter), + 4 => mini_boss_4(tile_wcenter), 5 => mini_boss_5(dynamic_rng, tile_wcenter), - _ => mini_boss_fallback(dynamic_rng, tile_wcenter), + _ => mini_boss_fallback(tile_wcenter), }; for entity in entities { @@ -923,292 +922,150 @@ impl Floor { } fn enemy_0(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-0.enemy"); - - let gnarling = entity - .with_loot_drop(chosen.read().choose().to_item()); - match dynamic_rng.gen_range(0..5) { - 0 => gnarling.with_asset_expect("common.entity.dungeon.tier-0.bow"), - 1 => gnarling.with_asset_expect("common.entity.dungeon.tier-0.staff"), - _ => gnarling.with_asset_expect("common.entity.dungeon.tier-0.spear"), + 0 => entity.with_asset_expect("common.entity.dungeon.tier-0.bow"), + 1 => entity.with_asset_expect("common.entity.dungeon.tier-0.staff"), + _ => entity.with_asset_expect("common.entity.dungeon.tier-0.spear"), } } fn enemy_1(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-1.enemy"); - - let adlet = entity - .with_loot_drop(chosen.read().choose().to_item()); - match dynamic_rng.gen_range(0..5) { - 0 => adlet.with_asset_expect("common.entity.dungeon.tier-1.bow"), - 1 => adlet.with_asset_expect("common.entity.dungeon.tier-1.staff"), - _ => adlet.with_asset_expect("common.entity.dungeon.tier-1.spear"), + 0 => entity.with_asset_expect("common.entity.dungeon.tier-1.bow"), + 1 => entity.with_asset_expect("common.entity.dungeon.tier-1.staff"), + _ => entity.with_asset_expect("common.entity.dungeon.tier-1.spear"), } } fn enemy_2(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-2.enemy"); - - let sahagin = entity - .with_loot_drop(chosen.read().choose().to_item()); - match dynamic_rng.gen_range(0..5) { - 0 => sahagin.with_asset_expect("common.entity.dungeon.tier-2.bow"), - 1 => sahagin.with_asset_expect("common.entity.dungeon.tier-2.staff"), - _ => sahagin.with_asset_expect("common.entity.dungeon.tier-2.spear"), + 0 => entity.with_asset_expect("common.entity.dungeon.tier-2.bow"), + 1 => entity.with_asset_expect("common.entity.dungeon.tier-2.staff"), + _ => entity.with_asset_expect("common.entity.dungeon.tier-2.spear"), } } fn enemy_3(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-3.enemy"); - - match dynamic_rng.gen_range(0..4) { + match dynamic_rng.gen_range(0..5) { 0 => entity .with_body(comp::Body::Object(comp::object::Body::HaniwaSentry)) - .with_name("Haniwa Sentry".to_string()) - .with_loot_drop(comp::Item::new_from_asset_expect( - "common.items.crafting_ing.stones", - )), - _ => { - let haniwa = entity - .with_loot_drop(chosen.read().choose().to_item()); - - match dynamic_rng.gen_range(0..5) { - 0 => haniwa.with_asset_expect("common.entity.dungeon.tier-3.bow"), - 1 => haniwa.with_asset_expect("common.entity.dungeon.tier-3.staff"), - _ => haniwa.with_asset_expect("common.entity.dungeon.tier-3.spear"), - } - }, + .with_asset_expect("common.entity.dungeon.tier-3.sentry"), + 1 => entity.with_asset_expect("common.entity.dungeon.tier-3.bow"), + 2 => entity.with_asset_expect("common.entity.dungeon.tier-3.staff"), + _ => entity.with_asset_expect("common.entity.dungeon.tier-3.spear"), } } fn enemy_4(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-4.enemy"); - - let myrmidon = entity - .with_loot_drop(chosen.read().choose().to_item()); - match dynamic_rng.gen_range(0..5) { - 0 => myrmidon.with_asset_expect("common.entity.dungeon.tier-4.bow"), - 1 => myrmidon.with_asset_expect("common.entity.dungeon.tier-4.staff"), - _ => myrmidon.with_asset_expect("common.entity.dungeon.tier-4.spear"), + 0 => entity.with_asset_expect("common.entity.dungeon.tier-4.bow"), + 1 => entity.with_asset_expect("common.entity.dungeon.tier-4.staff"), + _ => entity.with_asset_expect("common.entity.dungeon.tier-4.spear"), } } fn enemy_5(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-5.enemy"); - match dynamic_rng.gen_range(0..6) { 0 => entity .with_body(comp::Body::Object(comp::object::Body::Crossbow)) - .with_name("Possessed Turret".to_string()) - .with_loot_drop(comp::Item::new_from_asset_expect( - "common.items.crafting_ing.twigs", - )), - 1 => entity - .with_loot_drop(chosen.read().choose().to_item()) - .with_asset_expect("common.entity.dungeon.tier-5.warlock"), - _ => entity - .with_loot_drop(chosen.read().choose().to_item()) - .with_asset_expect("common.entity.dungeon.tier-5.warlord"), + .with_asset_expect("common.entity.dungeon.tier-5.turret"), + 1 => entity.with_asset_expect("common.entity.dungeon.tier-5.warlock"), + _ => entity.with_asset_expect("common.entity.dungeon.tier-5.warlord"), } } -fn enemy_fallback(_dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { - let chosen = Lottery::::load_expect("common.loot_tables.fallback"); - - entity - .with_name("Humanoid") - .with_loot_drop(chosen.read().choose().to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - "common.items.weapons.bow.bone-1", - )) +fn enemy_fallback(entity: EntityInfo) -> EntityInfo { + entity.with_asset_expect("common.entity.dungeon.fallback.enemy") } -fn boss_0(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-0.boss"); - +fn boss_0(tile_wcenter: Vec3) -> Vec { vec![ EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Harvester, - ), - )) - .with_name("Harvester".to_string()) - .with_loot_drop(chosen.read().choose().to_item()), + .with_asset_expect("common.entity.dungeon.tier-0.boss"), ] } -fn boss_1(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-1.boss"); +fn boss_1(tile_wcenter: Vec3) -> Vec { vec![ EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Yeti, - ), - )) - .with_name("Yeti".to_string()) - .with_loot_drop(chosen.read().choose().to_item()), + .with_asset_expect("common.entity.dungeon.tier-1.boss"), ] } -fn boss_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-2.boss"); +fn boss_2(tile_wcenter: Vec3) -> Vec { vec![ EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Tidalwarrior, - ), - )) - .with_name("Tidal Warrior".to_string()) - .with_loot_drop(chosen.read().choose().to_item()), + .with_asset_expect("common.entity.dungeon.tier-2.boss"), ] } -fn boss_3(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-3.boss"); - +fn boss_3(tile_wcenter: Vec3) -> Vec { let mut entities = Vec::new(); entities.resize_with(2, || { EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::Golem(comp::golem::Body::random_with( - dynamic_rng, - &comp::golem::Species::ClayGolem, - ))) - .with_name("Clay Golem".to_string()) - .with_loot_drop(chosen.read().choose().to_item()) + .with_asset_expect("common.entity.dungeon.tier-3.boss") }); + entities } -fn boss_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-4.boss"); - +fn boss_4(tile_wcenter: Vec3) -> Vec { vec![ EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Minotaur, - ), - )) - .with_name("Minotaur".to_string()) - .with_loot_drop(chosen.read().choose().to_item()), + .with_asset_expect("common.entity.dungeon.tier-4.boss"), ] } -fn boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-5.boss"); - +fn boss_5(tile_wcenter: Vec3) -> Vec { vec![ EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Mindflayer, - ), - )) - .with_loot_drop(chosen.read().choose().to_item()) .with_asset_expect("common.entity.dungeon.tier-5.boss"), ] } -fn boss_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_body(comp::Body::QuadrupedSmall( - comp::quadruped_small::Body::random_with( - dynamic_rng, - &comp::quadruped_small::Species::Sheep, - ), - )), - ] -} - -fn mini_boss_0(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-0.miniboss"); +fn boss_fallback(tile_wcenter: Vec3) -> Vec { vec![ EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::QuadrupedLow( - comp::quadruped_low::Body::random_with( - dynamic_rng, - &comp::quadruped_low::Species::Deadwood, - ), - )) - .with_name("Deadwood".to_string()) - .with_loot_drop(chosen.read().choose().to_item()), + .with_asset_expect("common.entity.dungeon.fallback.boss"), ] } -fn mini_boss_1(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = Lottery::::load_expect("common.loot_tables.creature.quad_small.generic"); +fn mini_boss_0(tile_wcenter: Vec3) -> Vec { + vec![ + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_asset_expect("common.entity.dungeon.tier-0.miniboss"), + ] +} + +fn mini_boss_1(tile_wcenter: Vec3) -> Vec { let mut entities = Vec::new(); entities.resize_with(8, || { EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::QuadrupedSmall( - comp::quadruped_small::Body::random_with( - dynamic_rng, - &comp::quadruped_small::Species::Rat, - ), - )) - .with_name("Rat".to_string()) - .with_loot_drop(chosen.read().choose().to_item()) + .with_asset_expect("common.entity.dungeon.tier-1.rat") }); entities } -fn mini_boss_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = Lottery::::load_expect("common.loot_tables.creature.quad_low.fanged"); +fn mini_boss_2(tile_wcenter: Vec3) -> Vec { let mut entities = Vec::new(); entities.resize_with(6, || { EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::QuadrupedLow( - comp::quadruped_low::Body::random_with( - dynamic_rng, - &comp::quadruped_low::Species::Hakulaq, - ), - )) - .with_name("Hakulaq".to_string()) - .with_loot_drop(chosen.read().choose().to_item()) + .with_asset_expect("common.entity.dungeon.tier-2.hakulaq") }); entities } -fn mini_boss_3(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = - Lottery::::load_expect("common.loot_tables.creature.quad_medium.carapace"); +fn mini_boss_3(tile_wcenter: Vec3) -> Vec { let mut entities = Vec::new(); entities.resize_with(3, || { EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::QuadrupedMedium( - comp::quadruped_medium::Body::random_with( - dynamic_rng, - &comp::quadruped_medium::Species::Bonerattler, - ), - )) - .with_name("Bonerattler".to_string()) - .with_loot_drop(chosen.read().choose().to_item()) + .with_asset_expect("common.entity.dungeon.tier-3.bonerattler") }); entities } -fn mini_boss_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let chosen = Lottery::::load_expect("common.loot_tables.dungeon.tier-4.miniboss"); +fn mini_boss_4(tile_wcenter: Vec3) -> Vec { vec![ EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Dullahan, - ), - )) - .with_name("Dullahan Guard".to_string()) - .with_loot_drop(chosen.read().choose().to_item()), + .with_asset_expect("common.entity.dungeon.tier-4.miniboss"), ] } @@ -1216,40 +1073,18 @@ fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec { - let trainer_loot = - Lottery::::load_expect("common.loot_tables.dungeon.tier-5.miniboss"); - let hound_loot = - Lottery::::load_expect("common.loot_tables.dungeon.tier-5.minion"); entities.push( EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) - .with_loot_drop(trainer_loot.read().choose().to_item()) .with_asset_expect("common.entity.dungeon.tier-5.beastmaster"), ); entities.resize_with(entities.len() + 2, || { EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::QuadrupedMedium( - comp::quadruped_medium::Body::random_with( - dynamic_rng, - &comp::quadruped_medium::Species::Darkhound, - ), - )) - .with_name("Tamed Darkhound".to_string()) - .with_loot_drop(hound_loot.read().choose().to_item()) + .with_asset_expect("common.entity.dungeon.tier-5.hound") }); }, _ => { - let chosen = - Lottery::::load_expect("common.loot_tables.dungeon.tier-5.minion"); entities.resize_with(10, || { EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedSmall( - comp::biped_small::Body::random_with( - dynamic_rng, - &comp::biped_small::Species::Husk, - ), - )) - .with_loot_drop(chosen.read().choose().to_item()) .with_asset_expect("common.entity.dungeon.tier-5.husk") }); }, @@ -1257,11 +1092,10 @@ fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3) -> Vec) -> Vec { +fn mini_boss_fallback(tile_wcenter: Vec3) -> Vec { vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_body(comp::Body::BirdMedium( - comp::bird_medium::Body::random_with(dynamic_rng, &comp::bird_medium::Species::Goose), - )), + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_asset_expect("common.entity.dungeon.fallback.miniboss"), ] } @@ -1271,18 +1105,18 @@ mod tests { #[test] fn test_creating_bosses() { - let mut dynamic_rng = rand::thread_rng(); let tile_wcenter = Vec3::new(0, 0, 0); - boss_0(&mut dynamic_rng, tile_wcenter); - boss_1(&mut dynamic_rng, tile_wcenter); - boss_2(&mut dynamic_rng, tile_wcenter); - boss_3(&mut dynamic_rng, tile_wcenter); - boss_4(&mut dynamic_rng, tile_wcenter); - boss_5(&mut dynamic_rng, tile_wcenter); - boss_fallback(&mut dynamic_rng, tile_wcenter); + boss_0(tile_wcenter); + boss_1(tile_wcenter); + boss_2(tile_wcenter); + boss_3(tile_wcenter); + boss_4(tile_wcenter); + boss_5(tile_wcenter); + boss_fallback(tile_wcenter); } #[test] + // FIXME: Uses random, test may be not great fn test_creating_enemies() { let mut dynamic_rng = rand::thread_rng(); let raw_entity = EntityInfo::at(Vec3::new(0.0, 0.0, 0.0)); @@ -1292,19 +1126,20 @@ mod tests { enemy_3(&mut dynamic_rng, raw_entity.clone()); enemy_4(&mut dynamic_rng, raw_entity.clone()); enemy_5(&mut dynamic_rng, raw_entity.clone()); - enemy_fallback(&mut dynamic_rng, raw_entity); + enemy_fallback(raw_entity); } #[test] + // FIXME: Uses random, test may be not great fn test_creating_minibosses() { let mut dynamic_rng = rand::thread_rng(); let tile_wcenter = Vec3::new(0, 0, 0); - mini_boss_0(&mut dynamic_rng, tile_wcenter); - mini_boss_1(&mut dynamic_rng, tile_wcenter); - mini_boss_2(&mut dynamic_rng, tile_wcenter); - mini_boss_3(&mut dynamic_rng, tile_wcenter); - mini_boss_4(&mut dynamic_rng, tile_wcenter); + mini_boss_0(tile_wcenter); + mini_boss_1(tile_wcenter); + mini_boss_2(tile_wcenter); + mini_boss_3(tile_wcenter); + mini_boss_4(tile_wcenter); mini_boss_5(&mut dynamic_rng, tile_wcenter); - mini_boss_fallback(&mut dynamic_rng, tile_wcenter); + mini_boss_fallback(tile_wcenter); } } From d02ff2db200647c37138604ce29c340e21510883 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Sat, 12 Jun 2021 11:10:06 -0400 Subject: [PATCH 15/37] Don't apply e2e pushback during a forced movement character state. --- CHANGELOG.md | 1 + assets/voxygen/i18n/en/_manifest.ron | 1 + common/src/comp/character_state.rs | 11 ++++++++++- common/systems/src/phys.rs | 15 +++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4466ae1851..fb77d098c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed +- Entity-entity pushback is no longer applied in forced movement states like rolling and leaping. ### Removed diff --git a/assets/voxygen/i18n/en/_manifest.ron b/assets/voxygen/i18n/en/_manifest.ron index 737713c4dc..2be067818b 100644 --- a/assets/voxygen/i18n/en/_manifest.ron +++ b/assets/voxygen/i18n/en/_manifest.ron @@ -58,6 +58,7 @@ "You can toggle showing your amount of health on the healthbar in the settings.", "Sit near a campfire (with the 'K' key) to slowly recover from your injuries.", "Need more bags or better armor to continue your journey? Press 'C' to open the crafting menu!", + "Try jumping when rolling through creatures.", ], "npc.speech.villager": [ "Isn't it such a lovely day?", diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 4e40b2ac6c..b21d90008f 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -2,7 +2,7 @@ use crate::{ combat::Attack, comp::{tool::ToolKind, Density, Energy, InputAttr, InputKind, Ori, Pos, Vel}, event::{LocalEvent, ServerEvent}, - states::{behavior::JoinData, *}, + states::{behavior::JoinData, utils::StageSection, *}, }; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage, VecStorage}; @@ -196,6 +196,15 @@ impl CharacterState { pub fn is_stunned(&self) -> bool { matches!(self, CharacterState::Stunned(_)) } + pub fn is_forced_movement(&self) -> bool { + matches!(self, + CharacterState::ComboMelee(s) if s.stage_section == StageSection::Swing) + || matches!(self, CharacterState::DashMelee(s) if s.stage_section == StageSection::Charge) + || matches!(self, CharacterState::LeapMelee(s) if s.stage_section == StageSection::Movement) + || matches!(self, CharacterState::SpinMelee(s) if s.stage_section == StageSection::Swing) + || matches!(self, CharacterState::Roll(s) if s.stage_section == StageSection::Movement) + } + /// Compares for shallow equality (does not check internal struct equality) pub fn same_variant(&self, other: &Self) -> bool { // Check if state is the same without looking at the inner data diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 48c36e506d..1f4521496c 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -333,6 +333,21 @@ impl<'a> PhysicsData<'a> { }; } + // Don't apply e2e pushback to entities that are in a forced movement state + // (e.g. roll, leapmelee). This allows leaps to work properly (since you won't + // get pushed away before delivering the hit), and allows rolling through an + // enemy when trapped (e.g. with minotaur). This allows using e2e pushback to + // gain speed by jumping out of a roll while in the middle of a collider, this + // is an intentional combat mechanic. + if let Some(cs) = char_state_maybe { + if cs.is_forced_movement() { + return PhysicsMetrics { + entity_entity_collision_checks, + entity_entity_collisions, + }; + } + } + let z_limits = calc_z_limit(char_state_maybe, collider); // Resets touch_entities in physics From c17e3ad9969802e10d6a81595a46a40980870103 Mon Sep 17 00:00:00 2001 From: Monty Date: Sun, 13 Jun 2021 13:52:56 +0200 Subject: [PATCH 16/37] Cactus Colada Made cacti lootable Cactus colada recipe and item price balance fmt "make it 8 and drop it to 20 or 25? Not really sure tbh" --- assets/common/items/crafting_ing/cactus.ron | 9 ++++++++ assets/common/items/food/cactus_colada.ron | 22 ++++++++++++++++++++ assets/common/loot_tables/food/prepared.ron | 1 + assets/common/recipe_book.ron | 8 +++++++ assets/voxygen/item_image_manifest.ron | 8 +++++++ assets/voxygen/voxel/object/cactus_drink.vox | 3 +++ common/src/comp/inventory/item/mod.rs | 3 +++ common/src/terrain/sprite.rs | 3 +++ 8 files changed, 57 insertions(+) create mode 100644 assets/common/items/crafting_ing/cactus.ron create mode 100644 assets/common/items/food/cactus_colada.ron create mode 100644 assets/voxygen/voxel/object/cactus_drink.vox diff --git a/assets/common/items/crafting_ing/cactus.ron b/assets/common/items/crafting_ing/cactus.ron new file mode 100644 index 0000000000..33c34e742b --- /dev/null +++ b/assets/common/items/crafting_ing/cactus.ron @@ -0,0 +1,9 @@ +ItemDef( + name: "Cactus", + description: "Grows in warm and dry places.", + kind: Ingredient( + kind: "Cactus", + ), + quality: Common, + tags: [], +) \ No newline at end of file diff --git a/assets/common/items/food/cactus_colada.ron b/assets/common/items/food/cactus_colada.ron new file mode 100644 index 0000000000..cef46eee0d --- /dev/null +++ b/assets/common/items/food/cactus_colada.ron @@ -0,0 +1,22 @@ +ItemDef( + name: "Cactus Colada", + description: "Giving you that special prickle.", + kind: Consumable( + kind: "CactusColada", + effect: [ + Buff(( + kind: Saturation, + data: ( + strength: 25.0, + duration: Some(( + secs: 15, + nanos: 0, + )), + ), + cat_ids: [Natural], + )), + ] + ), + quality: Moderate, + tags: [Food], +) diff --git a/assets/common/loot_tables/food/prepared.ron b/assets/common/loot_tables/food/prepared.ron index 89d78c10bf..a738723739 100644 --- a/assets/common/loot_tables/food/prepared.ron +++ b/assets/common/loot_tables/food/prepared.ron @@ -6,4 +6,5 @@ (1.2, Item("common.items.food.plainsalad")), (0.5, Item("common.items.food.sunflower_icetea")), (1.0, Item("common.items.food.tomatosalad")), + (1.4, Item("common.items.food.cactus_colada")), ] diff --git a/assets/common/recipe_book.ron b/assets/common/recipe_book.ron index fa306b04d3..85f6d48076 100644 --- a/assets/common/recipe_book.ron +++ b/assets/common/recipe_book.ron @@ -49,6 +49,14 @@ ], craft_sprite: Some(Cauldron), ), + "cactus_colada": ( + output: ("common.items.food.cactus_colada", 1), + inputs: [ + (Item("common.items.crafting_ing.empty_vial"), 1), + (Item("common.items.crafting_ing.cactus"), 8), + ], + craft_sprite: Some(Cauldron), + ), "collar_basic": ( output: ("common.items.utility.collar", 1), inputs: [ diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 5bb8796f1c..0e93d0282e 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -2272,6 +2272,10 @@ Consumable("Coconut"): Png( "element.items.item_coconut", ), + Consumable("CactusColada"): VoxTrans( + "voxel.object.cactus_drink", + (-1.0, 1.0, 0.0), (-50.0, 30.0, 20.0), 0.8, + ), Consumable("PotionMed"): VoxTrans( "voxel.object.potion_red", (0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.7, @@ -2448,6 +2452,10 @@ "voxel.sprite.rocks.rock-0", (0.0, 0.0, 0.0), (-50.0, 40.0, 20.0), 0.8, ), + Ingredient("Cactus"): VoxTrans( + "voxel.sprite.cacti.flat_cactus_med", + (0.0, 0.0, 0.0), (-50.0, 40.0, 20.0), 0.9, + ), Ingredient("Seashells"): VoxTrans( "voxel.sprite.seashells.shell-0", (0.0, 0.0, 0.0), (-50.0, 40.0, 20.0), 0.8, diff --git a/assets/voxygen/voxel/object/cactus_drink.vox b/assets/voxygen/voxel/object/cactus_drink.vox new file mode 100644 index 0000000000..bb9815be42 --- /dev/null +++ b/assets/voxygen/voxel/object/cactus_drink.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b36bfc8f2a1eecd3563e4686efa4834a8ae891ce8ad2013571ad8ee6f204a4b +size 2656 diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 64583dfa60..d246a16039 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -851,6 +851,9 @@ impl Item { SpriteKind::Pyrebloom => "common.items.flowers.pyrebloom", SpriteKind::WildFlax => "common.items.flowers.wild_flax", SpriteKind::Seashells => "common.items.crafting_ing.seashells", + SpriteKind::RoundCactus => "common.items.crafting_ing.cactus", + SpriteKind::ShortFlatCactus => "common.items.crafting_ing.cactus", + SpriteKind::MedFlatCactus => "common.items.crafting_ing.cactus", // Containers // IMPORTANT: Add any new container to `SpriteKind::is_container` container diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index d859ac0495..e35a99035f 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -298,6 +298,9 @@ impl SpriteKind { SpriteKind::Moonbell => true, SpriteKind::Pyrebloom => true, SpriteKind::WildFlax => true, + SpriteKind::RoundCactus => true, + SpriteKind::ShortFlatCactus => true, + SpriteKind::MedFlatCactus => true, _ => false, } } From 2e305744fec42dc7367bc67f3e27ad8c5e13c373 Mon Sep 17 00:00:00 2001 From: "K. Kisa" Date: Sun, 13 Jun 2021 13:08:52 +0000 Subject: [PATCH 17/37] =?UTF-8?q?=D0=9D=D0=B5=20=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B2=D1=8B=D0=BA=D0=BE=D0=B2,=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=B3=D0=B8=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B8=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/voxygen/i18n/ru_RU/skills.ron | 246 +++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 assets/voxygen/i18n/ru_RU/skills.ron diff --git a/assets/voxygen/i18n/ru_RU/skills.ron b/assets/voxygen/i18n/ru_RU/skills.ron new file mode 100644 index 0000000000..5b898545cf --- /dev/null +++ b/assets/voxygen/i18n/ru_RU/skills.ron @@ -0,0 +1,246 @@ +/// WARNING: Localization files shall be saved in UTF-8 format without BOM + +/// Localization for "global" English +( + string_map: { + "hud.rank_up": "Новый скиллпоинт", + "hud.skill.sp_available": "{number} SP доступны", + "hud.skill.not_unlocked": "Еще не разблокирован", + "hud.skill.req_sp": "\n\nНеобходимо {number} SP", + // Skills + // General + "hud.skill.inc_health_title": "Повышение здоровья", + "hud.skill.inc_health": "Увеличивает максимальное здоровье на 5{SP}", + "hud.skill.inc_stam_title": "Повышение Выносливости", + "hud.skill.inc_stam": "Увеличивает максимальную выносливость на 5{SP}", + "hud.skill.unlck_sword_title": "Разблокировать меч", + "hud.skill.unlck_sword": "Разблокировать древо навыков владения мечом{SP}", + "hud.skill.unlck_axe_title": "Разблокировать топор", + "hud.skill.unlck_axe": "Разблокировать древо навыков владения топором{SP}", + "hud.skill.unlck_hammer_title": "Разблокировать молот", + "hud.skill.unlck_hammer": "Разблокировать древо навыков владения молотом{SP}", + "hud.skill.unlck_bow_title": "Разблокировать лук", + "hud.skill.unlck_bow": "Разблокировать древо навыков владения луком{SP}", + "hud.skill.unlck_staff_title": "Разблокировать посох", + "hud.skill.unlck_staff": "Разблокировать древо навыков владения посохом{SP}", + "hud.skill.unlck_sceptre_title": "Разблокировать скипетр", + "hud.skill.unlck_sceptre": "Разблокировать древо навыков владения скипетром{SP}", + "hud.skill.dodge_title": "Перекат", + "hud.skill.dodge": "Перекат активируется средним щелчком мыши. Во время переката вы игнорируете урон ближнего боя.", + "hud.skill.roll_stamina_title": "Стоимость активации переката", + "hud.skill.roll_stamina": "Перекат использует на 10% меньше выносливости{SP}", + "hud.skill.roll_speed_title": "Скорость переката", + "hud.skill.roll_speed": "Перекат на 10% быстрее{SP}", + "hud.skill.roll_dur_title": "Продолжительность переката", + "hud.skill.roll_dur": "Перекат на 10% дольше{SP}", + "hud.skill.climbing_title": "Скалолазание", + "hud.skill.climbing": "Высота прыжка на вершине подъема", + "hud.skill.climbing_cost_title": "Затраты выносливости на подъем", + "hud.skill.climbing_cost": "Скалолазание использует на 20% меньше выносливости{SP}", + "hud.skill.climbing_speed_title": "Скорость подъема", + "hud.skill.climbing_speed": "Вы поднимаетесь на 20% быстрее{SP}", + "hud.skill.swim_title": "Плавание", + "hud.skill.swim": "Перемещение в воде", + "hud.skill.swim_speed_title": "Скорость плавания", + "hud.skill.swim_speed": "Плавание на 40% быстрее{SP}", + // Sceptre + "hud.skill.sc_lifesteal_title": "Луч вампиризма", + "hud.skill.sc_lifesteal": "Высосите жизнь из ваших врагов", + "hud.skill.sc_lifesteal_damage_title": "Урон", + "hud.skill.sc_lifesteal_damage": "Наносит на 20% больше урона{SP}", + "hud.skill.sc_lifesteal_range_title": "Дистанция", + "hud.skill.sc_lifesteal_range": "Ваш луч на 20% дальше{SP}", + "hud.skill.sc_lifesteal_lifesteal_title": "Вампиризм", + "hud.skill.sc_lifesteal_lifesteal": "Преобразуйте дополнительные 15% урона в здоровье{SP}", + "hud.skill.sc_lifesteal_regen_title": "Восстановление выносливости", + "hud.skill.sc_lifesteal_regen": "Востановите свою выносливость на 20%{SP}", + "hud.skill.sc_heal_title": "Исцеляющий луч", + "hud.skill.sc_heal": "Исцеляйте своих союзников, используя здоровье своих врагов", + "hud.skill.sc_heal_heal_title": "Лечение", + "hud.skill.sc_heal_heal": "Увеличивает эффективность исцеления союзников на 20%{SP}", + "hud.skill.sc_heal_cost_title": "Затраты выносливости", + "hud.skill.sc_heal_cost": "Исцеление требует на 20% меньше выносливости{SP}", + "hud.skill.sc_heal_range_title": "Дистанция", + "hud.skill.sc_heal_range": "Ваш луч на 20% дальше{SP}", + "hud.skill.sc_wardaura_unlock_title": "Разблокировать ауру защиты", + "hud.skill.sc_wardaura_unlock": "Позволяет вам повысить защиту своих союзников (и себя){SP}", + "hud.skill.sc_wardaura_strength_title": "Сила", + "hud.skill.sc_wardaura_strength": "Сила вашей защиты увеличивается на 15%{SP}", + "hud.skill.sc_wardaura_duration_title": "Продолжительность", + "hud.skill.sc_wardaura_duration": "Эффект длится на 20% дольше{SP}", + "hud.skill.sc_wardaura_range_title": "Радиус", + "hud.skill.sc_wardaura_range": "Радиус на 25% больше{SP}", + "hud.skill.sc_wardaura_cost_title": "Затраты выносливости", + "hud.skill.sc_wardaura_cost": "Создание ауры потребует на 15% меньше выносливости{SP}", + // Staff + "hud.skill.st_shockwave_range_title" : "Диапазон волны", + "hud.skill.st_shockwave_range" : "Радиус больше на 20%{SP}", + "hud.skill.st_shockwave_cost_title" : "Затраты выносливости", + "hud.skill.st_shockwave_cost" : "Затраты выносливости ниже на 20%{SP}", + "hud.skill.st_shockwave_knockback_title" : "Отбрасывание волны", + "hud.skill.st_shockwave_knockback" : "Увеличивает потенциал отбрасывания на 30%{SP}", + "hud.skill.st_shockwave_damage_title" : "Урон кольца огня", + "hud.skill.st_shockwave_damage" : "Увеличивает наносимый урон на 30%{SP}", + "hud.skill.st_shockwave_unlock_title" : "Разблокировать кольцо огня", + "hud.skill.st_shockwave_unlock" : "Открывает возможность отбрасывать врагов с помощью огня{SP}", + "hud.skill.st_flamethrower_title" : "Поток пламени", + "hud.skill.st_flamethrower" : "Плотный поток пламени из вашего посоха", + "hud.skill.st_flame_velocity_title" : "Скорость пламени", + "hud.skill.st_flame_velocity" : "Скорость создания огня выше на 25%{SP}", + "hud.skill.st_flamethrower_range_title" : "Дальность действия потока пламени", + "hud.skill.st_flamethrower_range" : "Дальность действия на 25% выше{SP}", + "hud.skill.st_energy_drain_title" : "Экономия выносливости", + "hud.skill.st_energy_drain" : "Уменьшает скорость траты выносливости на 20%{SP}", + "hud.skill.st_flamethrower_damage_title" : "Урон потока пламени", + "hud.skill.st_flamethrower_damage" : "Урон больше на 30%{SP}", + "hud.skill.st_explosion_radius_title" : "Радиус взрыва", + "hud.skill.st_explosion_radius" : "Радиус взрыва больше на 15%{SP}", + "hud.skill.st_stamina_regen_title" : "Востановление выносливости", + "hud.skill.st_stamina_regen" : "Увеличивает прирост выносливости на 20%{SP}", + "hud.skill.st_fireball_title" : "Огненый шар", + "hud.skill.st_fireball" : "Стреляет огненным шаром, который взрывается при ударе", + "hud.skill.st_damage_title" : "Урон", + "hud.skill.st_damage" : "Увеличивает урон на 20%{SP}", + // Bow + "hud.skill.bow_projectile_speed_title" : "Скорость снаряда", + "hud.skill.bow_projectile_speed" : "Стрелы летят дальше и быстрее на 30%{SP}", + "hud.skill.bow_charged_title" : "Прицельный выстрел", + "hud.skill.bow_charged" : "Более сильный выстрел за счет натяжения титевы", + "hud.skill.bow_charged_damage_title" : "Наносимый урон", + "hud.skill.bow_charged_damage" : "Урон увеличен на 20%{SP}", + "hud.skill.bow_charged_energy_regen_title" : "Востановление выносливости", + "hud.skill.bow_charged_energy_regen" : "Повышает востановление выносливости на 20%{SP}", + "hud.skill.bow_charged_knockback_title" : "Отбрасывание выстрела", + "hud.skill.bow_charged_knockback" : "Отбрасывание врагов больше на 20%{SP}", + "hud.skill.bow_charged_speed_title" : "Скорость атаки", + "hud.skill.bow_charged_speed" : "Увеличивает скорость атаки на 10%{SP}", + "hud.skill.bow_charged_move_title" : "Скорость перемещения", + "hud.skill.bow_charged_move" : "Увеличивает скорость перемешивания при прицеливании на 10%{SP}", + "hud.skill.bow_repeater_title" : "Repeater", + "hud.skill.bow_repeater" : "Shoots faster the longer you fire for", + "hud.skill.bow_repeater_damage_title" : "Repeater Урон", + "hud.skill.bow_repeater_damage" : "Увеличивает наносимый урон на 20%{SP}", + "hud.skill.bow_repeater_cost_title" : "Repeater расход выносливости", + "hud.skill.bow_repeater_cost" : "Снижает затраты выносливости на 20%{SP}", + "hud.skill.bow_repeater_speed_title" : "Repeater скорость", + "hud.skill.bow_repeater_speed" : "Увеличивает скорость стрельбы стрелами на 20%{SP}", + "hud.skill.bow_shotgun_unlock_title" : "Разблокировать Shotgun", + "hud.skill.bow_shotgun_unlock" : "Разблокирует возможность стрелять несколькими стрелами одновременно{SP}", + "hud.skill.bow_shotgun_damage_title" : "Shotgun Урон", + "hud.skill.bow_shotgun_damage" : "Увеличивает наносимый урон на 20%{SP}", + "hud.skill.bow_shotgun_cost_title" : "Shotgun затраты выносливости", + "hud.skill.bow_shotgun_cost" : "Снижает расход выносливости на 20%{SP}", + "hud.skill.bow_shotgun_arrow_count_title" : "Shotgun стрелы", + "hud.skill.bow_shotgun_arrow_count" : "Увеличивает количество стрел в серии ударов на 1{SP}", + "hud.skill.bow_shotgun_spread_title" : "Shotgun разброс", + "hud.skill.bow_shotgun_spread" : "Уменьшает разброс стрел на 20%{SP}", + // Hammer + "hud.skill.hmr_leap_radius_title" : "Радиус", + "hud.skill.hmr_leap_radius" : "Увеличивает радиус атаки при ударе об землю на 1 метр{SP}", + "hud.skill.hmr_leap_distance_title" : "Высота прыжка", + "hud.skill.hmr_leap_distance" : "Увеличивает расстояние прыжка на 25%{SP}", + "hud.skill.hmr_leap_cost_title" : "Затраты выносливости", + "hud.skill.hmr_leap_cost" : "Снижает затраты выносливости на 25%{SP}", + "hud.skill.hmr_leap_knockback_title" : "Leap Knockback", + "hud.skill.hmr_leap_knockback" : "Увеличивает отбрасивание от удара на 50%{SP}", + "hud.skill.hmr_leap_damage_title" : "Урон", + "hud.skill.hmr_leap_damage" : "Увеличивает урон на 40%{SP}", + "hud.skill.hmr_unlock_leap_title" : "Разблокировать Leap", + "hud.skill.hmr_unlock_leap" : "Разблокировать a leap{SP}", + "hud.skill.hmr_charged_melee_title" : "Силовая атака", + "hud.skill.hmr_charged_melee" : "ПКМ совершает оглушающий удар. Зажатие ПКМ накапливает силу и оглушает врага отбрасывая его", + "hud.skill.hmr_charged_rate_title" : "Скорость атаки", + "hud.skill.hmr_charged_rate" : "Увеличивает скорость с которой вы накапливаете силу удара на 25%{SP}", + "hud.skill.hmr_charged_melee_nrg_drain_title" : "Расход выносливости", + "hud.skill.hmr_charged_melee_nrg_drain" : "Уменьшает расход выносливости на 25%{SP}", + "hud.skill.hmr_charged_melee_damage_title" : "Урон атаки", + "hud.skill.hmr_charged_melee_damage" : "Увеличивает урон на 25%{SP}", + "hud.skill.hmr_charged_melee_knockback_title" : "Оглушение", + "hud.skill.hmr_charged_melee_knockback" : "Увеличивает отбрасывание 50%{SP}", + "hud.skill.hmr_single_strike_title" : "Одиночный удар", + "hud.skill.hmr_single_strike" : "Обычный удар", + "hud.skill.hmr_single_strike_regen_title" : "Востановление выносливости", + "hud.skill.hmr_single_strike_regen" : "Увеличивает прирост выносливости с каждым последующим ударом{SP}", + "hud.skill.hmr_single_strike_speed_title" : "Скорость атаки", + "hud.skill.hmr_single_strike_speed" : "Увеличивает скорость атаки с каждым последующим ударом{SP}", + "hud.skill.hmr_single_strike_damage_title" : "Урон", + "hud.skill.hmr_single_strike_damage" : "Увеличивает урон с каждым последующим ударом{SP}", + "hud.skill.hmr_single_strike_knockback_title" : "Отбрасывание", + "hud.skill.hmr_single_strike_knockback" : "Увеличьте потенциал отбрасывания на 50%{SP}", + "hud.skill." : "", + // Sword + "hud.skill.sw_trip_str_title": "Тройное вращение", + "hud.skill.sw_trip_str": "Вращение на 3 оборота", + "hud.skill.sw_trip_str_combo_title": "Тройной удар Комбо", + "hud.skill.sw_trip_str_combo": "Разблокирует комбо при тройном вращении{SP}", + "hud.skill.sw_trip_str_dmg_title": "Урон тройного вращения", + "hud.skill.sw_trip_str_dmg": "Увеличивает урон, наносимый каждым последующим ударом{SP}", + "hud.skill.sw_trip_str_sp_title": "Скорость атаки", + "hud.skill.sw_trip_str_sp": "Увеличивает скорость атаки, получаемую при каждом последующем ударе{SP}", + "hud.skill.sw_trip_str_reg_title": "Triple Strike Regen", + "hud.skill.sw_trip_str_reg": "Increases stamina gain on each successive strike{SP}", + "hud.skill.sw_dash_title": "Dash", + "hud.skill.sw_dash": "Pin through your enemies", + "hud.skill.sw_dash_dmg_title": "Dash Damage", + "hud.skill.sw_dash_dmg": "Increases initial damage of the dash by 20%{SP}", + "hud.skill.sw_dash_drain_title": "Dash Drain", + "hud.skill.sw_dash_drain": "Decreases the rate energy is drained while dashing by 25%{SP}", + "hud.skill.sw_dash_cost_title": "Dash Cost", + "hud.skill.sw_dash_cost": "Decreases the initial cost of the dash by 25%{SP}", + "hud.skill.sw_dash_speed_title": "Dash Speed", + "hud.skill.sw_dash_speed": "Increases how fast you go while dashing by 30%{SP}", + "hud.skill.sw_dash_charge_through_title": "Charge Through", + "hud.skill.sw_dash_charge_through": "Allows you to charge through the first enemies you hit{SP}", + "hud.skill.sw_dash_scale_title": "Dash Scaling Damage", + "hud.skill.sw_dash_scale": "Increases the damage scaling from the dash by 20%{SP}", + "hud.skill.sw_spin_title": "Spin Unlock", + "hud.skill.sw_spin": "Unlocks the sword spin{SP}", + "hud.skill.sw_spin_dmg_title": "Spin Damage", + "hud.skill.sw_spin_dmg": "Increases the damage done by 40%{SP}", + "hud.skill.sw_spin_spd_title": "Spin Speed", + "hud.skill.sw_spin_spd": "Increase the speed at which you spin by 25%{SP}", + "hud.skill.sw_spin_cost_title": "Spin Cost", + "hud.skill.sw_spin_cost": "Decreases the energy cost of each spin by 25%{SP}", + "hud.skill.sw_spin_spins_title": "Spin Spins", + "hud.skill.sw_spin_spins": "Increases the number of times you can spin{SP}", + "hud.skill.sw_interrupt_title": "Interrupting Attacks", + "hud.skill.sw_interrupt": "Allows you to immediately cancel an attack with another attack{SP}", + // Axe + "hud.skill.axe_double_strike_title": "Double Strike", + "hud.skill.axe_double_strike": "Chop down those villains", + "hud.skill.axe_double_strike_combo_title": "Double Strike Combo", + "hud.skill.axe_double_strike_combo": "Unlocks a second strike{SP}", + "hud.skill.axe_double_strike_damage_title": "Double Strike Damage", + "hud.skill.axe_double_strike_damage": "Increases the damage dealt in each successive strike{SP}", + "hud.skill.axe_double_strike_speed_title": "Double Strike Speed", + "hud.skill.axe_double_strike_speed": "Increases the attack speed with each successive strike{SP}", + "hud.skill.axe_double_strike_regen_title": "Double Strike Regen", + "hud.skill.axe_double_strike_regen": "Increases stamina gain with each successive strike{SP}", + "hud.skill.axe_spin_title": "Axe Spin", + "hud.skill.axe_spin": "You spin it right round ...", + "hud.skill.axe_infinite_axe_spin_title": "Infinite Axe Spin", + "hud.skill.axe_infinite_axe_spin": "Spin for as long as you have energy{SP}", + "hud.skill.axe_spin_damage_title": "Spin Damage", + "hud.skill.axe_spin_damage": "Increases the damage each spin does by 30%{SP}", + "hud.skill.axe_spin_helicopter_title": "Spin Helicopter", + "hud.skill.axe_spin_helicopter": "You fall a little slower while spinning{SP}", + "hud.skill.axe_spin_speed_title": "Spin Speed", + "hud.skill.axe_spin_speed": "Increases your spin speed by 25%{SP}", + "hud.skill.axe_spin_cost_title": "Spin Cost", + "hud.skill.axe_spin_cost": "Decreases stamina cost of spinning by 25%{SP}", + "hud.skill.axe_unlock_leap_title": "Unlock Leap", + "hud.skill.axe_unlock_leap": "Unlocks a leap spin{SP}", + "hud.skill.axe_leap_damage_title": "Leap Damage", + "hud.skill.axe_leap_damage": "Increases damage of leap by 35%{SP}", + "hud.skill.axe_leap_knockback_title": "Leap Knockback", + "hud.skill.axe_leap_knockback": "Increases knockback from leap by 40%{SP}", + "hud.skill.axe_leap_cost_title": "Leap Cost", + "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": "Increases distance of leap by 20%{SP}", + }, + + + vector_map: { + } +) From acb05892779dcb44be1ce4e337a6fed5300c8e5c Mon Sep 17 00:00:00 2001 From: "K. Kisa" Date: Sun, 13 Jun 2021 13:24:57 +0000 Subject: [PATCH 18/37] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=80=D1=83=D1=81=D1=81=D0=BA=D0=B8=D0=B9=20=D1=8F=D0=B7=D1=8B?= =?UTF-8?q?=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/voxygen/i18n/ru_RU/main.ron | 69 ++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 assets/voxygen/i18n/ru_RU/main.ron diff --git a/assets/voxygen/i18n/ru_RU/main.ron b/assets/voxygen/i18n/ru_RU/main.ron new file mode 100644 index 0000000000..26a42b40d1 --- /dev/null +++ b/assets/voxygen/i18n/ru_RU/main.ron @@ -0,0 +1,69 @@ +/// WARNING: Localization files shall be saved in UTF-8 format without BOM + +/// Localization for RU +( + string_map: { + /// Start Main screen section + "main.username": "Никнейм", + "main.server": "Сервер", + "main.password": "Пароль", + "main.connecting": "Соединение", + "main.creating_world": "Создание мира", + "main.tip": "Совет:", + + // Welcome notice that appears the first time Veloren is started + "main.notice": r#"Добро пожаловать в альфа версию Veloren! + +Прежде чем погрузиться в веселье, пожалуйста, имейте в виду несколько вещей: + +- Это очень ранняя альфа. Ожидайте ошибок, крайне незавершенного геймплея, неполированной механики и отсутствующих функций. + +- Если у вас есть конструктивные отзывы или сообщения об ошибках, вы можете связаться с нами через Reddit, GitLab или наш сервер разногласий сообщества. + +- Veloren лицензирован по лицензии GPL 3 с открытым исходным кодом. Это означает, что вы можете играть, изменять и распространять игру +по своему усмотрению (при условии, что производная работа также находится в рамках GPL 3). + +- Велорен-это некоммерческий общественный проект, и все, кто работает над ним, являются добровольцами. +Если вам нравится то, что вы видите, вы можете присоединиться к командам разработчиков или художников! + +Спасибо, что нашли время прочитать это уведомление, мы надеемся, что вам понравится игра! + +~ Разработчики Велорена"#, + + // Login process description + "main.login_process": r#"Информация о процессе входа в систему: + +Обратите внимание, что вам нужна учетная +запись для игры на серверах с поддержкой аутентификации. + +Вы можете создать учетную запись по адресу +https://veloren.net/account/."#, + "main.login.server_not_found": "Сервер не найден", + "main.login.authentication_error": "Ошибка аутентификации на сервере", + "main.login.internal_error": "Внутренняя ошибка на клиенте (скорее всего, персонаж игрока был удален)", + "main.login.failed_auth_server_url_invalid": "Не удалось подключиться к серверу аутентификации", + "main.login.insecure_auth_scheme": "Схема аутентификации HTTP не поддерживается. Это небезопасно! В целях разработки HTTP разрешен для 'локального хоста' или отладочных сборок", + "main.login.server_full": "Сервер заполнен", + "main.login.untrusted_auth_server": "Сервер аутентификации не является доверенным", + "main.login.outdated_client_or_server": "ServerWentMad: Возможно, версии несовместимы, проверьте наличие обновлений.", + "main.login.timeout": "Тайм-аут: Сервер не ответил вовремя. (Перегрузка или проблемы с сетью).", + "main.login.server_shut_down": "Сервер выключен", + "main.login.network_error": "Ошибка сети", + "main.login.network_wrong_version": "Несоответствие версии сервера и клиента, пожалуйста, обновите свой игровой клиент.", + "main.login.failed_sending_request": "Не удалось выполнить запрос на сервер аутентификации", + "main.login.invalid_character": "Выбранный символ недопустим", + "main.login.client_crashed": "Клиент разбился", + "main.login.not_on_whitelist": "Вам нужна запись в Белом списке от администратора, чтобы присоединиться", + "main.login.banned": "Вы были забанены по следующей причине", + "main.login.kicked": "Вас выгнали по следующей причине", + "main.login.select_language": "Выберите язык", + "main.login.client_version": "Версия клиента", + "main.login.server_version": "Server Version", + "main.servers.select_server": "Версия сервера", + /// End Main screen section + }, + + + vector_map: { + } +) From 883297d4cc00f502fbca516362f487fc2ba460a7 Mon Sep 17 00:00:00 2001 From: "K. Kisa" Date: Sun, 13 Jun 2021 13:30:42 +0000 Subject: [PATCH 19/37] Upload New File --- assets/voxygen/i18n/ru_RU/esc_menu.ron | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 assets/voxygen/i18n/ru_RU/esc_menu.ron diff --git a/assets/voxygen/i18n/ru_RU/esc_menu.ron b/assets/voxygen/i18n/ru_RU/esc_menu.ron new file mode 100644 index 0000000000..48066287f6 --- /dev/null +++ b/assets/voxygen/i18n/ru_RU/esc_menu.ron @@ -0,0 +1,13 @@ +/// WARNING: Localization files shall be saved in UTF-8 format without BOM + +/// Localization for RUS +( + string_map: { + "esc_menu.logout": "Выйти в меню", + "esc_menu.quit_game": "Выйти из игры", + }, + + + vector_map: { + } +) From 5f2b44002e863332ebb3b896e77dd13442f612f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Mon, 5 Apr 2021 15:50:44 +0200 Subject: [PATCH 20/37] make test less flanky, try to avoid absolute comparisions and compare jobs relative. --- common/src/slowjob.rs | 75 +++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/common/src/slowjob.rs b/common/src/slowjob.rs index fac0c98846..16186ddbd2 100644 --- a/common/src/slowjob.rs +++ b/common/src/slowjob.rs @@ -73,7 +73,7 @@ impl InternalSlowJobPool { fn maintain(&self) { let jobs_available = - self.global_limit as i64 - self.global_running_jobs.load(Ordering::Relaxed); + self.global_limit as i64 - self.global_running_jobs.load(Ordering::SeqCst); if jobs_available < 0 { tracing::warn!(?jobs_available, "Some math is wrong in slowjob code"); } @@ -104,7 +104,7 @@ impl InternalSlowJobPool { let c = lock.get(&name).unwrap(); ( name, - c.spawned_total.load(Ordering::Relaxed) / c.max_local, + c.spawned_total.load(Ordering::SeqCst) / c.max_local, c.max_local, ) }) @@ -122,7 +122,7 @@ impl InternalSlowJobPool { }; if let Some(queue) = map.remove(&firstkey) { - if queue.local_running_jobs.load(Ordering::Relaxed) < *max as i64 { + if queue.local_running_jobs.load(Ordering::SeqCst) < *max as i64 { self.fire(queue); } else { map.insert(firstkey, queue); @@ -134,9 +134,9 @@ impl InternalSlowJobPool { } fn fire(&self, queue: Queue) { - queue.spawned_total.fetch_add(1, Ordering::Relaxed); - queue.local_running_jobs.fetch_add(1, Ordering::Relaxed); - self.global_running_jobs.fetch_add(1, Ordering::Relaxed); + queue.spawned_total.fetch_add(1, Ordering::SeqCst); + queue.local_running_jobs.fetch_add(1, Ordering::SeqCst); + self.global_running_jobs.fetch_add(1, Ordering::SeqCst); self.threadpool.spawn(queue.task); } } @@ -167,7 +167,7 @@ impl SlowJobPool { where F: FnOnce() + Send + Sync + 'static, { - let id = self.internal.next_id.fetch_add(1, Ordering::Relaxed); + let id = self.internal.next_id.fetch_add(1, Ordering::SeqCst); self.internal .queue .write() @@ -208,8 +208,8 @@ impl SlowJobPool { task: Box::new(move || { common_base::prof_span!(_guard, &_name_clones); f(); - local_running_jobs_clone.fetch_sub(1, Ordering::Relaxed); - global_running_jobs_clone.fetch_sub(1, Ordering::Relaxed); + local_running_jobs_clone.fetch_sub(1, Ordering::SeqCst); + global_running_jobs_clone.fetch_sub(1, Ordering::SeqCst); // directly maintain the next task afterwards internal.maintain(); }), @@ -291,13 +291,22 @@ mod tests { let f6 = measure(f6, start); let f7 = measure(f7, start); assert_eq!(done.load(Ordering::Relaxed), 7); - assert!(f1 < 500); - assert!(f2 < 500); - assert!(f3 < 500); - assert!(f4 < 1000); - assert!(f5 < 1000); - assert!(f6 < 1000); - assert!(f7 < 1500); + // Just test relative times, not absolute + assert!(f1 < f4); + assert!(f1 < f5); + assert!(f1 < f6); + assert!(f1 < f7); + assert!(f2 < f4); + assert!(f2 < f5); + assert!(f2 < f6); + assert!(f2 < f7); + assert!(f3 < f4); + assert!(f3 < f5); + assert!(f3 < f6); + assert!(f3 < f7); + assert!(f4 < f7); + assert!(f5 < f7); + assert!(f6 < f7); } #[test] @@ -338,13 +347,21 @@ mod tests { let f6 = measure(f6, start); let f7 = measure(f7, start); assert_eq!(done.load(Ordering::Relaxed), 7); - assert!(f1 < 500); - assert!(f2 < 500); - assert!(f3 < 500); - assert!(f4 < 1000); - assert!(f5 < 1000); - assert!(f6 < 1000); - assert!(f7 < 1500); + assert!(f1 < f4); + assert!(f1 < f5); + assert!(f1 < f6); + assert!(f1 < f7); + assert!(f2 < f4); + assert!(f2 < f5); + assert!(f2 < f6); + assert!(f2 < f7); + assert!(f3 < f4); + assert!(f3 < f5); + assert!(f3 < f6); + assert!(f3 < f7); + assert!(f4 < f7); + assert!(f5 < f7); + assert!(f6 < f7); } #[test] @@ -356,12 +373,12 @@ mod tests { let pool = SlowJobPool::new(2, Arc::new(threadpool)); pool.configure("FOO", |n| n); pool.configure("BAR", |n| n / 2); - let start = Instant::now(); let f1 = Arc::new(Mutex::new(None)); let f2 = Arc::new(Mutex::new(None)); let b1 = Arc::new(Mutex::new(None)); let b2 = Arc::new(Mutex::new(None)); let done = Arc::new(AtomicI64::new(0)); + let start = Instant::now(); pool.spawn("FOO", mock_fn("foo1", &f1, &done)); pool.spawn("FOO", mock_fn("foo2", &f2, &done)); std::thread::sleep(Duration::from_millis(1000)); @@ -380,11 +397,13 @@ mod tests { // [B1] // [B2] assert_eq!(done.load(Ordering::Relaxed), 4); - assert!(f1 < 500); - assert!(f2 < 500); + assert!(f1 < 600); + assert!(f2 < 600); println!("b1 {}", b1); println!("b2 {}", b2); - assert!((1000..1500).contains(&b1)); - assert!((1500..2000).contains(&b2)); + // would be to flanky: + //assert!((1000..1500).contains(&b1)); + //assert!((1500..2000).contains(&b2)); + assert!(b1 < b2); } } From e2a91289767c103035108fbda7a4612e3099b04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Mon, 12 Apr 2021 12:24:24 +0200 Subject: [PATCH 21/37] redo slowjobs in order to have a `try_run` fn --- common/src/slowjob.rs | 831 ++++++++++++++++++++++++++---------------- 1 file changed, 524 insertions(+), 307 deletions(-) diff --git a/common/src/slowjob.rs b/common/src/slowjob.rs index 16186ddbd2..5175e4b069 100644 --- a/common/src/slowjob.rs +++ b/common/src/slowjob.rs @@ -1,9 +1,14 @@ use hashbrown::HashMap; use rayon::ThreadPool; -use std::sync::{ - atomic::{AtomicI64, AtomicU64, Ordering}, - Arc, RwLock, +use std::{ + collections::VecDeque, + sync::{Arc, Mutex}, + time::{Duration, Instant} }; +use tracing::{error, warn}; + +const LAST_JOBS_METRICS: usize = 32; +const FAIR_TIME_INTERVAL: Duration = Duration::from_millis(1000); /// Provides a Wrapper around rayon threadpool to execute slow-jobs. /// slow means, the job doesn't need to not complete within the same tick. @@ -11,11 +16,26 @@ use std::sync::{ /// Jobs run here, will reduce the ammount of threads rayon can use during the /// main tick. /// +/// ## Configuration /// This Pool allows you to configure certain names of jobs and assign them a /// maximum number of threads # Example /// Your system has 16 cores, you assign 12 cores for slow-jobs. /// Then you can configure all jobs with the name `CHUNK_GENERATOR` to spawn on -/// max 50% (6 = cores) ```rust +/// max 50% (6 = cores) +/// +/// ## Spawn Order +/// - At least 1 job of a configuration is allowed to run if global limit isn't +/// hit. +/// - remaining capacities are spread in relation to their limit. e.g. a +/// configuration with double the limit will be sheduled to spawn double the +/// tasks. +/// +/// ## States +/// - queued +/// - spawned +/// - started +/// - finished +/// ``` /// # use veloren_common::slowjob::SlowJobPool; /// # use std::sync::Arc; /// @@ -25,126 +45,248 @@ use std::sync::{ /// .unwrap(); /// let pool = SlowJobPool::new(3, Arc::new(threadpool)); /// pool.configure("CHUNK_GENERATOR", |n| n / 2); -/// pool.spawn("CHUNK_GENERATOR", move || println("this is a job")); +/// pool.spawn("CHUNK_GENERATOR", move || println!("this is a job")); /// ``` #[derive(Clone)] pub struct SlowJobPool { - internal: Arc, + internal: Arc>, } +#[derive(Debug)] pub struct SlowJob { name: String, id: u64, } struct InternalSlowJobPool { - next_id: Arc, - queue: RwLock>>, - running_jobs: RwLock>>, - configs: RwLock>, - global_running_jobs: Arc, + next_id: u64, + queue: HashMap>, + configs: HashMap, + global_spawned_and_running: u64, global_limit: u64, threadpool: Arc, + internal: Option>>, } +#[derive(Debug)] struct Config { - max_local: u64, - spawned_total: Arc, + local_limit: u64, + local_spawned_and_running: u64, + /// hold the start and time of the last LAST_JOBS_METRICS jobs + last_jobs: VecDeque<(std::time::Instant, Option)>, } struct Queue { + id: u64, + name: String, task: Box, - spawned_total: Arc, - local_running_jobs: Arc, +} + +impl Queue { + fn new(name: &str, id: u64, internal: &Arc>, f: F) -> Self + where + F: FnOnce() + Send + Sync + 'static, + { + let internal = Arc::clone(&internal); + let name_cloned = name.to_owned(); + Self { + id, + name: name.to_owned(), + task: Box::new(move || { + common_base::prof_span!(_guard, &name_cloned); + f(); + // directly maintain the next task afterwards + { + let mut lock = internal.lock().expect("slowjob lock poisoned"); + lock.finish(&name_cloned); + lock.spawn_queued(); + } + }), + } + } } impl InternalSlowJobPool { - pub fn new(global_limit: u64, threadpool: Arc) -> Self { - Self { - next_id: Arc::new(AtomicU64::new(0)), - queue: RwLock::new(HashMap::new()), - running_jobs: RwLock::new(HashMap::new()), - configs: RwLock::new(HashMap::new()), - global_running_jobs: Arc::new(AtomicI64::new(0)), + pub fn new(global_limit: u64, threadpool: Arc) -> Arc> { + let link = Arc::new(Mutex::new(Self { + next_id: 0, + queue: HashMap::new(), + configs: HashMap::new(), + global_spawned_and_running: 0, global_limit: global_limit.max(1), threadpool, - } + internal: None, + })); + + let link_clone = Arc::clone(&link); + link.lock() + .expect("poisoned on InternalSlowJobPool::new") + .internal = Some(link_clone); + link } - fn maintain(&self) { - let jobs_available = - self.global_limit as i64 - self.global_running_jobs.load(Ordering::SeqCst); - if jobs_available < 0 { - tracing::warn!(?jobs_available, "Some math is wrong in slowjob code"); - } - if jobs_available <= 0 { - // we run at limit, can't spawn - return; - } - let possible = { - let lock = self.queue.read().unwrap(); - lock.iter() - .map(|(name, queues)| { - if !queues.is_empty() { - Some(name.clone()) - } else { - None - } - }) - .flatten() - .collect::>() - }; - - let mut possible_total = { - let mut possible = possible; - let lock = self.configs.read().unwrap(); - possible - .drain(..) - .map(|name| { - let c = lock.get(&name).unwrap(); - ( - name, - c.spawned_total.load(Ordering::SeqCst) / c.max_local, - c.max_local, - ) - }) - .collect::>() - }; - possible_total.sort_by_key(|(_, i, _)| *i); - - let mut lock = self.queue.write().unwrap(); - for i in 0..jobs_available as usize { - if let Some((name, _, max)) = possible_total.get(i) { - if let Some(map) = lock.get_mut(name) { - let firstkey = match map.keys().next() { - Some(k) => *k, - None => continue, - }; - - if let Some(queue) = map.remove(&firstkey) { - if queue.local_running_jobs.load(Ordering::SeqCst) < *max as i64 { - self.fire(queue); - } else { - map.insert(firstkey, queue); - } - } + /// returns order of configuration which are queued next + fn calc_queued_order( + &self, + mut queued: HashMap<&String, u64>, + mut limit: usize, + ) -> Vec { + let mut result = vec![]; + let spawned = self + .configs + .iter() + .map(|(n, c)| (n, c.local_spawned_and_running)) + .collect::>(); + let mut queried_caped = self + .configs + .iter() + .map(|(n, c)| { + ( + n, + queued + .get(&n) + .cloned() + .unwrap_or(0) + .min(c.local_limit - c.local_spawned_and_running), + ) + }) + .collect::>(); + // grab all configs that are queued and not running + for (&n, c) in queued.iter_mut() { + if *c > 0 && spawned.get(n).cloned().unwrap_or(0) == 0 { + result.push(n.clone()); + *c -= 1; + limit -= 1; + queried_caped.get_mut(&n).map(|v| *v -= 1); + if limit == 0 { + return result; } } } + //schedule rest + let total_limit = queried_caped.values().sum::() as f32; + if total_limit < f32::EPSILON { + return result; + } + let mut spawn_rates = queried_caped + .iter() + .map(|(&n, l)| (n, ((*l as f32 * limit as f32) / total_limit).min(*l as f32))) + .collect::>(); + while limit > 0 { + spawn_rates.sort_by(|(_, a), (_, b)| { + if b < a { + core::cmp::Ordering::Less + } else if (b - a).abs() < f32::EPSILON { + core::cmp::Ordering::Equal + } else { + core::cmp::Ordering::Greater + } + }); + match spawn_rates.first_mut() { + Some((n, r)) => { + if *r > f32::EPSILON { + result.push(n.clone()); + limit -= 1; + *r -= 1.0; + } else { + break; + } + }, + None => break, + } + } + result } - fn fire(&self, queue: Queue) { - queue.spawned_total.fetch_add(1, Ordering::SeqCst); - queue.local_running_jobs.fetch_add(1, Ordering::SeqCst); - self.global_running_jobs.fetch_add(1, Ordering::SeqCst); - self.threadpool.spawn(queue.task); + fn can_spawn(&self, name: &str) -> bool { + let queued = self + .queue + .iter() + .map(|(n, m)| (n, m.len() as u64)) + .collect::>(); + let mut to_be_queued = queued.clone(); + let name = name.to_owned(); + *to_be_queued.entry(&name).or_default() += 1; + let limit = (self.global_limit - self.global_spawned_and_running) as usize; + // calculate to_be_queued first + let to_be_queued_order = self.calc_queued_order(to_be_queued, limit); + let queued_order = self.calc_queued_order(queued, limit); + // if its queued one time more then its okay to spawn + let to_be_queued_cnt = to_be_queued_order + .into_iter() + .filter(|n| n == &name) + .count(); + let queued_cnt = queued_order.into_iter().filter(|n| n == &name).count(); + to_be_queued_cnt > queued_cnt + } + + pub fn spawn(&mut self, name: &str, f: F) -> SlowJob + where + F: FnOnce() + Send + Sync + 'static, + { + let id = self.next_id; + self.next_id += 1; + let queue = Queue::new(name, id, self.internal.as_ref().expect("internal empty"), f); + self.queue + .entry(name.to_string()) + .or_default() + .push_back(queue); + //spawn already queued + self.spawn_queued(); + SlowJob { + name: name.to_string(), + id, + } + } + + fn finish(&mut self, name: &str) { + self.global_spawned_and_running -= 1; + if let Some(c) = self.configs.get_mut(name) { + c.local_spawned_and_running -= 1; + c.last_jobs. + } else { + warn!(?name, "sync_maintain on a no longer existing config"); + } + } + + fn spawn_queued(&mut self) { + let queued = self + .queue + .iter() + .map(|(n, m)| (n, m.len() as u64)) + .collect::>(); + let limit = self.global_limit as usize; + let queued_order = self.calc_queued_order(queued, limit); + for name in queued_order.into_iter() { + match self.queue.get_mut(&name) { + Some(deque) => match deque.pop_front() { + Some(queue) => { + //fire + self.global_spawned_and_running += 1; + self.configs + .get_mut(&queue.name) + .expect("cannot fire a unconfigured job") + .local_spawned_and_running += 1; + self.threadpool.spawn(queue.task); + }, + None => error!( + "internal calculation is wrong, we extected a schedulable job to be \ + present in the queue" + ), + }, + None => error!( + "internal calculation is wrong, we marked a queue as schedulable which \ + doesn't exist" + ), + } + } } } impl SlowJobPool { pub fn new(global_limit: u64, threadpool: Arc) -> Self { Self { - internal: Arc::new(InternalSlowJobPool::new(global_limit, threadpool)), + internal: InternalSlowJobPool::new(global_limit, threadpool), } } @@ -154,256 +296,331 @@ impl SlowJobPool { where F: Fn(u64) -> u64, { + let mut lock = self.internal.lock().expect("lock poisoned while configure"); let cnf = Config { - max_local: f(self.internal.global_limit).max(1), - spawned_total: Arc::new(AtomicU64::new(0)), + local_limit: f(lock.global_limit).max(1), + local_spawned_and_running: 0, + last_jobs: VecDeque::with_capacity(LAST_JOBS_METRICS), }; - let mut lock = self.internal.configs.write().unwrap(); - lock.insert(name.to_string(), cnf); + lock.configs.insert(name.to_owned(), cnf); + } + + /// spawn a new slow job on a certain NAME IF it can run immediately + #[allow(clippy::result_unit_err)] + pub fn try_run(&self, name: &str, f: F) -> Result + where + F: FnOnce() + Send + Sync + 'static, + { + let mut lock = self.internal.lock().expect("lock poisoned while try_run"); + //spawn already queued + lock.spawn_queued(); + if lock.can_spawn(name) { + Ok(lock.spawn(name, f)) + } else { + Err(()) + } } - /// spawn a new slow job on a certain NAME pub fn spawn(&self, name: &str, f: F) -> SlowJob where F: FnOnce() + Send + Sync + 'static, { - let id = self.internal.next_id.fetch_add(1, Ordering::SeqCst); self.internal - .queue - .write() - .unwrap() - .entry(name.to_string()) - .or_default() - .insert(id, self.queue(name, f)); - self.maintain(); - SlowJob { - name: name.to_string(), - id, - } + .lock() + .expect("lock poisoned while spawn") + .spawn(name, f) } - fn queue(&self, name: &str, f: F) -> Queue - where - F: FnOnce() + Send + Sync + 'static, - { - let internal = Arc::clone(&self.internal); - let spawned_total = Arc::clone( - &self - .internal - .configs - .read() - .unwrap() - .get(name) - .expect("can't spawn a non-configued slowjob") - .spawned_total, - ); - let local_running_jobs_clone = { - let mut lock = self.internal.running_jobs.write().unwrap(); - Arc::clone(&lock.entry(name.to_string()).or_default()) - }; - let local_running_jobs = Arc::clone(&local_running_jobs_clone); - let global_running_jobs_clone = Arc::clone(&self.internal.global_running_jobs); - let _name_clones = name.to_string(); - Queue { - task: Box::new(move || { - common_base::prof_span!(_guard, &_name_clones); - f(); - local_running_jobs_clone.fetch_sub(1, Ordering::SeqCst); - global_running_jobs_clone.fetch_sub(1, Ordering::SeqCst); - // directly maintain the next task afterwards - internal.maintain(); - }), - spawned_total, - local_running_jobs, + pub fn cancel(&self, job: SlowJob) -> Result<(), SlowJob> { + let mut lock = self.internal.lock().expect("lock poisoned while cancel"); + if let Some(m) = lock.queue.get_mut(&job.name) { + let p = match m.iter().position(|p| p.id == job.id) { + Some(p) => p, + None => return Err(job), + }; + if m.remove(p).is_some() { + return Ok(()); + } } + Err(job) } - - pub fn cancel(&self, job: SlowJob) { - let mut lock = self.internal.queue.write().unwrap(); - if let Some(map) = lock.get_mut(&job.name) { - map.remove(&job.id); - } - } - - fn maintain(&self) { self.internal.maintain() } } #[cfg(test)] mod tests { use super::*; - use std::{ - sync::Mutex, - time::{Duration, Instant}, - }; - fn mock_fn( - name: &str, - start_time: &Arc>>, - done: &Arc, - ) -> impl FnOnce() { - let name = name.to_string(); - let start_time = Arc::clone(start_time); - let done = Arc::clone(done); - move || { - println!("Start {}", name); - *start_time.lock().unwrap() = Some(Instant::now()); - std::thread::sleep(Duration::from_millis(500)); - done.fetch_add(1, Ordering::Relaxed); - println!("Finished {}", name); + #[allow(clippy::blacklisted_name)] + fn mock_pool( + pool_threads: usize, + global_threads: u64, + foo: u64, + bar: u64, + baz: u64, + ) -> SlowJobPool { + let threadpool = rayon::ThreadPoolBuilder::new() + .num_threads(pool_threads) + .build() + .unwrap(); + let pool = SlowJobPool::new(global_threads, Arc::new(threadpool)); + if foo != 0 { + pool.configure("FOO", |x| x / foo); } + if bar != 0 { + pool.configure("BAR", |x| x / bar); + } + if baz != 0 { + pool.configure("BAZ", |x| x / baz); + } + pool } #[test] - fn global_limit() { - let threadpool = rayon::ThreadPoolBuilder::new() - .num_threads(4) - .build() - .unwrap(); - let pool = SlowJobPool::new(3, Arc::new(threadpool)); - pool.configure("FOO", |_| 1000); - let start = Instant::now(); - let f1 = Arc::new(Mutex::new(None)); - let f2 = Arc::new(Mutex::new(None)); - let f3 = Arc::new(Mutex::new(None)); - let f4 = Arc::new(Mutex::new(None)); - let f5 = Arc::new(Mutex::new(None)); - let f6 = Arc::new(Mutex::new(None)); - let f7 = Arc::new(Mutex::new(None)); - let done = Arc::new(AtomicI64::new(0)); - pool.spawn("FOO", mock_fn("foo1", &f1, &done)); - pool.spawn("FOO", mock_fn("foo2", &f2, &done)); - pool.spawn("FOO", mock_fn("foo3", &f3, &done)); - std::thread::sleep(Duration::from_millis(300)); - pool.spawn("FOO", mock_fn("foo4", &f4, &done)); - pool.spawn("FOO", mock_fn("foo5", &f5, &done)); - pool.spawn("FOO", mock_fn("foo6", &f6, &done)); - std::thread::sleep(Duration::from_millis(300)); - pool.spawn("FOO", mock_fn("foo7", &f7, &done)); - std::thread::sleep(Duration::from_secs(1)); - let measure = |a: Arc>>, s: Instant| { - a.lock().unwrap().unwrap().duration_since(s).as_millis() - }; - let f1 = measure(f1, start); - let f2 = measure(f2, start); - let f3 = measure(f3, start); - let f4 = measure(f4, start); - let f5 = measure(f5, start); - let f6 = measure(f6, start); - let f7 = measure(f7, start); - assert_eq!(done.load(Ordering::Relaxed), 7); - // Just test relative times, not absolute - assert!(f1 < f4); - assert!(f1 < f5); - assert!(f1 < f6); - assert!(f1 < f7); - assert!(f2 < f4); - assert!(f2 < f5); - assert!(f2 < f6); - assert!(f2 < f7); - assert!(f3 < f4); - assert!(f3 < f5); - assert!(f3 < f6); - assert!(f3 < f7); - assert!(f4 < f7); - assert!(f5 < f7); - assert!(f6 < f7); + fn simple_queue() { + let pool = mock_pool(4, 4, 1, 0, 0); + let internal = pool.internal.lock().unwrap(); + let queue_data = [("FOO", 1u64)] + .iter() + .map(|(n, c)| ((*n).to_owned(), *c)) + .collect::>(); + let queued = queue_data + .iter() + .map(|(s, c)| (s, *c)) + .collect::>(); + let result = internal.calc_queued_order(queued, 4); + assert_eq!(result.len(), 1); + assert_eq!(result[0], "FOO"); } #[test] - fn local_limit() { - let threadpool = rayon::ThreadPoolBuilder::new() - .num_threads(4) - .build() - .unwrap(); - let pool = SlowJobPool::new(100, Arc::new(threadpool)); - pool.configure("FOO", |_| 3); - let start = Instant::now(); - let f1 = Arc::new(Mutex::new(None)); - let f2 = Arc::new(Mutex::new(None)); - let f3 = Arc::new(Mutex::new(None)); - let f4 = Arc::new(Mutex::new(None)); - let f5 = Arc::new(Mutex::new(None)); - let f6 = Arc::new(Mutex::new(None)); - let f7 = Arc::new(Mutex::new(None)); - let done = Arc::new(AtomicI64::new(0)); - pool.spawn("FOO", mock_fn("foo1", &f1, &done)); - pool.spawn("FOO", mock_fn("foo2", &f2, &done)); - pool.spawn("FOO", mock_fn("foo3", &f3, &done)); - std::thread::sleep(Duration::from_millis(300)); - pool.spawn("FOO", mock_fn("foo4", &f4, &done)); - pool.spawn("FOO", mock_fn("foo5", &f5, &done)); - pool.spawn("FOO", mock_fn("foo6", &f6, &done)); - std::thread::sleep(Duration::from_millis(300)); - pool.spawn("FOO", mock_fn("foo7", &f7, &done)); - std::thread::sleep(Duration::from_secs(1)); - let measure = |a: Arc>>, s: Instant| { - a.lock().unwrap().unwrap().duration_since(s).as_millis() - }; - let f1 = measure(f1, start); - let f2 = measure(f2, start); - let f3 = measure(f3, start); - let f4 = measure(f4, start); - let f5 = measure(f5, start); - let f6 = measure(f6, start); - let f7 = measure(f7, start); - assert_eq!(done.load(Ordering::Relaxed), 7); - assert!(f1 < f4); - assert!(f1 < f5); - assert!(f1 < f6); - assert!(f1 < f7); - assert!(f2 < f4); - assert!(f2 < f5); - assert!(f2 < f6); - assert!(f2 < f7); - assert!(f3 < f4); - assert!(f3 < f5); - assert!(f3 < f6); - assert!(f3 < f7); - assert!(f4 < f7); - assert!(f5 < f7); - assert!(f6 < f7); + fn multiple_queue() { + let pool = mock_pool(4, 4, 1, 0, 0); + let internal = pool.internal.lock().unwrap(); + let queue_data = [("FOO", 2u64)] + .iter() + .map(|(n, c)| ((*n).to_owned(), *c)) + .collect::>(); + let queued = queue_data + .iter() + .map(|(s, c)| (s, *c)) + .collect::>(); + let result = internal.calc_queued_order(queued, 4); + assert_eq!(result.len(), 2); + assert_eq!(result[0], "FOO"); + assert_eq!(result[1], "FOO"); } #[test] - fn pool() { - let threadpool = rayon::ThreadPoolBuilder::new() - .num_threads(2) - .build() - .unwrap(); - let pool = SlowJobPool::new(2, Arc::new(threadpool)); - pool.configure("FOO", |n| n); - pool.configure("BAR", |n| n / 2); - let f1 = Arc::new(Mutex::new(None)); - let f2 = Arc::new(Mutex::new(None)); - let b1 = Arc::new(Mutex::new(None)); - let b2 = Arc::new(Mutex::new(None)); - let done = Arc::new(AtomicI64::new(0)); - let start = Instant::now(); - pool.spawn("FOO", mock_fn("foo1", &f1, &done)); - pool.spawn("FOO", mock_fn("foo2", &f2, &done)); - std::thread::sleep(Duration::from_millis(1000)); - pool.spawn("BAR", mock_fn("bar1", &b1, &done)); - pool.spawn("BAR", mock_fn("bar2", &b2, &done)); - std::thread::sleep(Duration::from_secs(2)); - let measure = |a: Arc>>, s: Instant| { - a.lock().unwrap().unwrap().duration_since(s).as_millis() + fn limit_queue() { + let pool = mock_pool(5, 5, 1, 0, 0); + let internal = pool.internal.lock().unwrap(); + let queue_data = [("FOO", 80u64)] + .iter() + .map(|(n, c)| ((*n).to_owned(), *c)) + .collect::>(); + let queued = queue_data + .iter() + .map(|(s, c)| (s, *c)) + .collect::>(); + let result = internal.calc_queued_order(queued, 4); + assert_eq!(result.len(), 4); + assert_eq!(result[0], "FOO"); + assert_eq!(result[1], "FOO"); + assert_eq!(result[2], "FOO"); + assert_eq!(result[3], "FOO"); + } + + #[test] + fn simple_queue_2() { + let pool = mock_pool(4, 4, 1, 1, 0); + let internal = pool.internal.lock().unwrap(); + let queue_data = [("FOO", 1u64), ("BAR", 1u64)] + .iter() + .map(|(n, c)| ((*n).to_owned(), *c)) + .collect::>(); + let queued = queue_data + .iter() + .map(|(s, c)| (s, *c)) + .collect::>(); + let result = internal.calc_queued_order(queued, 4); + assert_eq!(result.len(), 2); + assert_eq!(result.iter().filter(|&x| x == "FOO").count(), 1); + assert_eq!(result.iter().filter(|&x| x == "BAR").count(), 1); + } + + #[test] + fn multiple_queue_3() { + let pool = mock_pool(4, 4, 1, 1, 0); + let internal = pool.internal.lock().unwrap(); + let queue_data = [("FOO", 2u64), ("BAR", 2u64)] + .iter() + .map(|(n, c)| ((*n).to_owned(), *c)) + .collect::>(); + let queued = queue_data + .iter() + .map(|(s, c)| (s, *c)) + .collect::>(); + let result = internal.calc_queued_order(queued, 4); + assert_eq!(result.len(), 4); + assert_eq!(result.iter().filter(|&x| x == "FOO").count(), 2); + assert_eq!(result.iter().filter(|&x| x == "BAR").count(), 2); + } + + #[test] + fn multiple_queue_4() { + let pool = mock_pool(4, 4, 2, 1, 0); + let internal = pool.internal.lock().unwrap(); + let queue_data = [("FOO", 3u64), ("BAR", 3u64)] + .iter() + .map(|(n, c)| ((*n).to_owned(), *c)) + .collect::>(); + let queued = queue_data + .iter() + .map(|(s, c)| (s, *c)) + .collect::>(); + let result = internal.calc_queued_order(queued, 4); + assert_eq!(result.len(), 4); + assert_eq!(result.iter().filter(|&x| x == "FOO").count(), 2); + assert_eq!(result.iter().filter(|&x| x == "BAR").count(), 2); + } + + #[test] + fn multiple_queue_5() { + let pool = mock_pool(4, 4, 2, 1, 0); + let internal = pool.internal.lock().unwrap(); + let queue_data = [("FOO", 5u64), ("BAR", 5u64)] + .iter() + .map(|(n, c)| ((*n).to_owned(), *c)) + .collect::>(); + let queued = queue_data + .iter() + .map(|(s, c)| (s, *c)) + .collect::>(); + let result = internal.calc_queued_order(queued, 5); + assert_eq!(result.len(), 5); + assert_eq!(result.iter().filter(|&x| x == "FOO").count(), 2); + assert_eq!(result.iter().filter(|&x| x == "BAR").count(), 3); + } + + #[test] + fn multiple_queue_6() { + let pool = mock_pool(40, 40, 2, 1, 0); + let internal = pool.internal.lock().unwrap(); + let queue_data = [("FOO", 5u64), ("BAR", 5u64)] + .iter() + .map(|(n, c)| ((*n).to_owned(), *c)) + .collect::>(); + let queued = queue_data + .iter() + .map(|(s, c)| (s, *c)) + .collect::>(); + let result = internal.calc_queued_order(queued, 11); + assert_eq!(result.len(), 10); + assert_eq!(result.iter().filter(|&x| x == "FOO").count(), 5); + assert_eq!(result.iter().filter(|&x| x == "BAR").count(), 5); + } + + #[test] + #[should_panic] + fn unconfigured() { + let pool = mock_pool(4, 4, 2, 1, 0); + let mut internal = pool.internal.lock().unwrap(); + internal.spawn("UNCONFIGURED", || println!()); + } + + #[test] + fn correct_spawn_doesnt_panic() { + let pool = mock_pool(4, 4, 2, 1, 0); + let mut internal = pool.internal.lock().unwrap(); + internal.spawn("FOO", || println!("foo")); + internal.spawn("BAR", || println!("bar")); + } + + #[test] + fn can_spawn() { + let pool = mock_pool(4, 4, 2, 1, 0); + let internal = pool.internal.lock().unwrap(); + assert!(internal.can_spawn("FOO")); + assert!(internal.can_spawn("BAR")); + } + + #[test] + fn try_run_works() { + let pool = mock_pool(4, 4, 2, 1, 0); + pool.try_run("FOO", || println!("foo")).unwrap(); + pool.try_run("BAR", || println!("bar")).unwrap(); + } + + #[test] + fn try_run_exhausted() { + use std::{thread::sleep, time::Duration}; + let pool = mock_pool(8, 8, 4, 2, 0); + let func = || loop { + sleep(Duration::from_secs(1)) }; - let f1 = measure(f1, start); - let f2 = measure(f2, start); - let b1 = measure(b1, start); - let b2 = measure(b2, start); - // Expect: - // [F1, F2] - // [B1] - // [B2] - assert_eq!(done.load(Ordering::Relaxed), 4); - assert!(f1 < 600); - assert!(f2 < 600); - println!("b1 {}", b1); - println!("b2 {}", b2); - // would be to flanky: - //assert!((1000..1500).contains(&b1)); - //assert!((1500..2000).contains(&b2)); - assert!(b1 < b2); + pool.try_run("FOO", func).unwrap(); + pool.try_run("BAR", func).unwrap(); + pool.try_run("FOO", func).unwrap(); + pool.try_run("BAR", func).unwrap(); + pool.try_run("FOO", func).unwrap_err(); + pool.try_run("BAR", func).unwrap(); + pool.try_run("FOO", func).unwrap_err(); + pool.try_run("BAR", func).unwrap(); + pool.try_run("FOO", func).unwrap_err(); + pool.try_run("BAR", func).unwrap_err(); + pool.try_run("FOO", func).unwrap_err(); + } + + #[test] + fn actually_runs_1() { + let pool = mock_pool(4, 4, 0, 0, 1); + let barrier = Arc::new(std::sync::Barrier::new(2)); + let barrier_clone = Arc::clone(&barrier); + pool.try_run("BAZ", move || { + barrier_clone.wait(); + }) + .unwrap(); + barrier.wait(); + } + + #[test] + fn actually_runs_2() { + let pool = mock_pool(4, 4, 0, 0, 1); + let barrier = Arc::new(std::sync::Barrier::new(2)); + let barrier_clone = Arc::clone(&barrier); + pool.spawn("BAZ", move || { + barrier_clone.wait(); + }); + barrier.wait(); + } + + #[test] + fn actually_waits() { + use std::sync::{ + atomic::{AtomicBool, Ordering}, + Barrier, + }; + let pool = mock_pool(4, 4, 4, 0, 1); + let ops_i_ran = Arc::new(AtomicBool::new(false)); + let ops_i_ran_clone = Arc::clone(&ops_i_ran); + let barrier = Arc::new(Barrier::new(2)); + let barrier_clone = Arc::clone(&barrier); + let barrier2 = Arc::new(Barrier::new(2)); + let barrier2_clone = Arc::clone(&barrier2); + pool.try_run("FOO", move || { + barrier_clone.wait(); + }) + .unwrap(); + pool.spawn("FOO", move || { + ops_i_ran_clone.store(true, Ordering::SeqCst); + barrier2_clone.wait(); + }); + // in this case we have to sleep + std::thread::sleep(std::time::Duration::from_secs(1)); + assert_eq!(ops_i_ran.load(Ordering::SeqCst), false); + // now finish the first job + barrier.wait(); + // now wait on the second job to be actually finished + barrier2.wait(); } } From 34f5ff62d471c5f03f61e13836c47cb24570c157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=A4rtens?= Date: Fri, 21 May 2021 09:51:00 +0200 Subject: [PATCH 22/37] implement a simple roundrobin to assure if multiple are spawned the older one has prio, spelling --- common/src/slowjob.rs | 106 +++++++++++++++++++++++++++-------- voxygen/src/ui/keyed_jobs.rs | 5 +- 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/common/src/slowjob.rs b/common/src/slowjob.rs index 5175e4b069..fa77b03ba6 100644 --- a/common/src/slowjob.rs +++ b/common/src/slowjob.rs @@ -3,13 +3,9 @@ use rayon::ThreadPool; use std::{ collections::VecDeque, sync::{Arc, Mutex}, - time::{Duration, Instant} }; use tracing::{error, warn}; -const LAST_JOBS_METRICS: usize = 32; -const FAIR_TIME_INTERVAL: Duration = Duration::from_millis(1000); - /// Provides a Wrapper around rayon threadpool to execute slow-jobs. /// slow means, the job doesn't need to not complete within the same tick. /// DO NOT USE I/O blocking jobs, but only CPU heavy jobs. @@ -28,7 +24,7 @@ const FAIR_TIME_INTERVAL: Duration = Duration::from_millis(1000); /// hit. /// - remaining capacities are spread in relation to their limit. e.g. a /// configuration with double the limit will be sheduled to spawn double the -/// tasks. +/// tasks, starting by a round robin. /// /// ## States /// - queued @@ -62,6 +58,7 @@ struct InternalSlowJobPool { next_id: u64, queue: HashMap>, configs: HashMap, + last_spawned_configs: Vec, global_spawned_and_running: u64, global_limit: u64, threadpool: Arc, @@ -72,8 +69,6 @@ struct InternalSlowJobPool { struct Config { local_limit: u64, local_spawned_and_running: u64, - /// hold the start and time of the last LAST_JOBS_METRICS jobs - last_jobs: VecDeque<(std::time::Instant, Option)>, } struct Queue { @@ -112,6 +107,7 @@ impl InternalSlowJobPool { next_id: 0, queue: HashMap::new(), configs: HashMap::new(), + last_spawned_configs: Vec::new(), global_spawned_and_running: 0, global_limit: global_limit.max(1), threadpool, @@ -131,13 +127,14 @@ impl InternalSlowJobPool { mut queued: HashMap<&String, u64>, mut limit: usize, ) -> Vec { + let mut roundrobin = self.last_spawned_configs.clone(); let mut result = vec![]; let spawned = self .configs .iter() .map(|(n, c)| (n, c.local_spawned_and_running)) .collect::>(); - let mut queried_caped = self + let mut queried_capped = self .configs .iter() .map(|(n, c)| { @@ -151,24 +148,31 @@ impl InternalSlowJobPool { ) }) .collect::>(); - // grab all configs that are queued and not running - for (&n, c) in queued.iter_mut() { - if *c > 0 && spawned.get(n).cloned().unwrap_or(0) == 0 { - result.push(n.clone()); - *c -= 1; - limit -= 1; - queried_caped.get_mut(&n).map(|v| *v -= 1); - if limit == 0 { - return result; + // grab all configs that are queued and not running. in roundrobin order + for n in roundrobin.clone().into_iter() { + if let Some(c) = queued.get_mut(&n) { + if *c > 0 && spawned.get(&n).cloned().unwrap_or(0) == 0 { + result.push(n.clone()); + *c -= 1; + limit -= 1; + queried_capped.get_mut(&n).map(|v| *v -= 1); + roundrobin + .iter() + .position(|e| e == &n) + .map(|i| roundrobin.remove(i)); + roundrobin.push(n); + if limit == 0 { + return result; + } } } } - //schedule rest - let total_limit = queried_caped.values().sum::() as f32; + //schedule rest based on their possible limites, don't use round robin here + let total_limit = queried_capped.values().sum::() as f32; if total_limit < f32::EPSILON { return result; } - let mut spawn_rates = queried_caped + let mut spawn_rates = queried_capped .iter() .map(|(&n, l)| (n, ((*l as f32 * limit as f32) / total_limit).min(*l as f32))) .collect::>(); @@ -231,6 +235,10 @@ impl InternalSlowJobPool { .entry(name.to_string()) .or_default() .push_back(queue); + debug_assert!( + self.configs.contains_key(name), + "Can't spawn unconfigured task!" + ); //spawn already queued self.spawn_queued(); SlowJob { @@ -243,7 +251,6 @@ impl InternalSlowJobPool { self.global_spawned_and_running -= 1; if let Some(c) = self.configs.get_mut(name) { c.local_spawned_and_running -= 1; - c.last_jobs. } else { warn!(?name, "sync_maintain on a no longer existing config"); } @@ -267,6 +274,11 @@ impl InternalSlowJobPool { .get_mut(&queue.name) .expect("cannot fire a unconfigured job") .local_spawned_and_running += 1; + self.last_spawned_configs + .iter() + .position(|e| e == &queue.name) + .map(|i| self.last_spawned_configs.remove(i)); + self.last_spawned_configs.push(queue.name.to_owned()); self.threadpool.spawn(queue.task); }, None => error!( @@ -300,9 +312,9 @@ impl SlowJobPool { let cnf = Config { local_limit: f(lock.global_limit).max(1), local_spawned_and_running: 0, - last_jobs: VecDeque::with_capacity(LAST_JOBS_METRICS), }; lock.configs.insert(name.to_owned(), cnf); + lock.last_spawned_configs.push(name.to_owned()); } /// spawn a new slow job on a certain NAME IF it can run immediately @@ -520,6 +532,54 @@ mod tests { assert_eq!(result.iter().filter(|&x| x == "BAR").count(), 5); } + #[test] + fn roundrobin() { + let pool = mock_pool(4, 4, 2, 2, 0); + let queue_data = [("FOO", 5u64), ("BAR", 5u64)] + .iter() + .map(|(n, c)| ((*n).to_owned(), *c)) + .collect::>(); + let queued = queue_data + .iter() + .map(|(s, c)| (s, *c)) + .collect::>(); + // Spawn a FOO task. + pool.internal + .lock() + .unwrap() + .spawn("FOO", || println!("foo")); + // a barrier in f doesnt work as we need to wait for the cleanup + while pool.internal.lock().unwrap().global_spawned_and_running != 0 { + std::thread::yield_now(); + } + let result = pool + .internal + .lock() + .unwrap() + .calc_queued_order(queued.clone(), 1); + assert_eq!(result.len(), 1); + assert_eq!(result[0], "BAR"); + // keep order if no new is spawned + let result = pool + .internal + .lock() + .unwrap() + .calc_queued_order(queued.clone(), 1); + assert_eq!(result.len(), 1); + assert_eq!(result[0], "BAR"); + // spawn a BAR task + pool.internal + .lock() + .unwrap() + .spawn("BAR", || println!("bar")); + while pool.internal.lock().unwrap().global_spawned_and_running != 0 { + std::thread::yield_now(); + } + let result = pool.internal.lock().unwrap().calc_queued_order(queued, 1); + assert_eq!(result.len(), 1); + assert_eq!(result[0], "FOO"); + } + #[test] #[should_panic] fn unconfigured() { @@ -617,7 +677,7 @@ mod tests { }); // in this case we have to sleep std::thread::sleep(std::time::Duration::from_secs(1)); - assert_eq!(ops_i_ran.load(Ordering::SeqCst), false); + assert!(!ops_i_ran.load(Ordering::SeqCst)); // now finish the first job barrier.wait(); // now wait on the second job to be actually finished diff --git a/voxygen/src/ui/keyed_jobs.rs b/voxygen/src/ui/keyed_jobs.rs index 72f61b33b7..cacd5195f3 100644 --- a/voxygen/src/ui/keyed_jobs.rs +++ b/voxygen/src/ui/keyed_jobs.rs @@ -4,6 +4,7 @@ use std::{ hash::Hash, time::{Duration, Instant}, }; +use tracing::warn; enum KeyedJobTask { Pending(Instant, Option), @@ -60,7 +61,9 @@ impl Key let fresh = now - *at < KEYEDJOBS_GC_INTERVAL; if !fresh { if let Some(job) = job.take() { - pool.cancel(job) + if let Err(e) = pool.cancel(job) { + warn!(?e, "failed to cancel job"); + } } } fresh From faf5b3fc9c0f68db7009cc9cf0c2f7dd03501b15 Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Sun, 13 Jun 2021 17:55:08 +0300 Subject: [PATCH 23/37] chore(deps): update nix flake deps --- flake.lock | 88 +++++++----------------------------------------------- flake.nix | 2 +- 2 files changed, 11 insertions(+), 79 deletions(-) diff --git a/flake.lock b/flake.lock index cbb32d0c2a..47a42b63e2 100644 --- a/flake.lock +++ b/flake.lock @@ -1,22 +1,5 @@ { "nodes": { - "crate2nix": { - "flake": false, - "locked": { - "lastModified": 1619462726, - "narHash": "sha256-bQuUBOGzPnL3S+aweK/P9WRfNGk/tuoLDPfzIiX7XXY=", - "owner": "yusdacra", - "repo": "crate2nix", - "rev": "e10f71834d1464cd4b07d1bf7965c65abbff3fab", - "type": "github" - }, - "original": { - "owner": "yusdacra", - "ref": "feat/builtinfetchgit", - "repo": "crate2nix", - "type": "github" - } - }, "devshell": { "locked": { "lastModified": 1622711433, @@ -32,71 +15,36 @@ "type": "github" } }, - "flakeUtils": { - "locked": { - "lastModified": 1622445595, - "narHash": "sha256-m+JRe6Wc5OZ/mKw2bB3+Tl0ZbtyxxxfnAWln8Q5qs+Y=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "7d706970d94bc5559077eb1a6600afddcd25a7c8", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "naersk": { - "flake": false, - "locked": { - "lastModified": 1619312121, - "narHash": "sha256-Zx1rTlonsp54lVlnIg38HV3bYx6GdIoKS1SgDnV+YBY=", - "owner": "yusdacra", - "repo": "naersk", - "rev": "07d0b56bdbd353a705f26b799e3a125c7be0f8c3", - "type": "github" - }, - "original": { - "owner": "yusdacra", - "ref": "feat/cargolock-git-deps", - "repo": "naersk", - "type": "github" - } - }, "nixCargoIntegration": { "inputs": { - "crate2nix": "crate2nix", "devshell": "devshell", - "flakeUtils": "flakeUtils", - "naersk": "naersk", "nixpkgs": [ "nixpkgs" ], - "preCommitHooks": "preCommitHooks", "rustOverlay": "rustOverlay" }, "locked": { - "lastModified": 1622713739, - "narHash": "sha256-+X+MJno3JKwrtJxLbl37/0YihznIfjowRyxeEwoFEk0=", + "lastModified": 1623594891, + "narHash": "sha256-JmjOTdIW0QIsMoqU6oIQx9fqH0Ims83g2jK8jmokHsI=", "owner": "yusdacra", "repo": "nix-cargo-integration", - "rev": "f383dd1d1915ec1fc38d855fe9351d8f5778211e", + "rev": "5394e8fa179346854e44e05edced0b9d7dc818b7", "type": "github" }, "original": { "owner": "yusdacra", + "ref": "fix/crate2nix", "repo": "nix-cargo-integration", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1622368643, - "narHash": "sha256-/BEAmTreS2JjRvnuNNr65tA20FWcRsonURKfYdJecJA=", + "lastModified": 1623176226, + "narHash": "sha256-54a9uvHlIlK3i0b36HfGMc4zqM0BpMOOiFYBxEhQFK8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "774fe1878b045411e6bdd0dd90d8581e82b10993", + "rev": "51bb9f3e9ab6161a3bf7746e20b955712cef618b", "type": "github" }, "original": { @@ -106,22 +54,6 @@ "type": "github" } }, - "preCommitHooks": { - "flake": false, - "locked": { - "lastModified": 1622650193, - "narHash": "sha256-qSzUpJDv04ajS9FXoCq6NjVF3qOt9IiGIiGh0P8amyw=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "0398f0649e0a741660ac5e8216760bae5cc78579", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, "root": { "inputs": { "nixCargoIntegration": "nixCargoIntegration", @@ -131,11 +63,11 @@ "rustOverlay": { "flake": false, "locked": { - "lastModified": 1622689331, - "narHash": "sha256-b8c0t2Uo4uk7DNhSfhFDpqzY/w/tIUgBut5Ug1Pl6DU=", + "lastModified": 1623550815, + "narHash": "sha256-RumRrkE6OTJDndHV4qZNZv8kUGnzwRHZQSyzx29r6/g=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "7ad7640ad12ff6b0af4f96bffd95175ad0898d49", + "rev": "9824f142cbd7bc3e2a92eefbb79addfff8704cd3", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 54b4bd0c43..a34463137d 100644 --- a/flake.nix +++ b/flake.nix @@ -4,7 +4,7 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixCargoIntegration = { - url = "github:yusdacra/nix-cargo-integration"; + url = "github:yusdacra/nix-cargo-integration/fix/crate2nix"; inputs.nixpkgs.follows = "nixpkgs"; }; }; From fd480fdfbc02d58785ceda64483219b2cd951c21 Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Sun, 13 Jun 2021 19:09:10 +0300 Subject: [PATCH 24/37] build(nix): fix shaderc lib --- Cargo.toml | 4 ---- flake.nix | 6 ++++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 88c76c276e..bd0e04061c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,10 +109,6 @@ key = "veloren-nix.cachix.org-1:zokfKJqVsNV6kI/oJdLF6TYBdNPYGSb+diMVQPn/5Rc=" buildInputs = ["openssl"] nativeBuildInputs = ["pkg-config"] -[workspace.metadata.nix.crateOverride.shaderc-sys] -buildInputs = ["shaderc"] -nativeBuildInputs = ["cmake", "python3", "gnumake"] - [patch.crates-io] # macos CI fix isn't released yet winit = { git = "https://gitlab.com/veloren/winit.git", branch = "macos-test-spiffed" } diff --git a/flake.nix b/flake.nix index a34463137d..9a7353e892 100644 --- a/flake.nix +++ b/flake.nix @@ -60,6 +60,12 @@ # veloren-world = oldAttrs: { # crateBin = lib.filter (bin: bin.name != "chunk_compression_benchmarks") oldAttrs.crateBin; # }; + shaderc-sys = _: + let SHADERC_LIB_DIR = "${common.pkgs.shaderc.lib}/lib"; in + { + inherit SHADERC_LIB_DIR; + propagatedEnv = { inherit SHADERC_LIB_DIR; }; + }; veloren-client = oldAttrs: { crateBin = lib.filter (bin: bin.name != "bot") oldAttrs.crateBin; }; From ffad3a5f662f852278d9053a21d4feff9c107f08 Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Sun, 13 Jun 2021 19:17:37 +0300 Subject: [PATCH 25/37] build(nix): dont try to compile i18n-check binary --- flake.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flake.nix b/flake.nix index 9a7353e892..199b8be1b5 100644 --- a/flake.nix +++ b/flake.nix @@ -69,6 +69,9 @@ veloren-client = oldAttrs: { crateBin = lib.filter (bin: bin.name != "bot") oldAttrs.crateBin; }; + veloren-i18n = oldAttrs: { + crateBin = lib.filter (bin: bin.name != "i18n-check") oldAttrs.crateBin; + }; veloren-common = oldAttrs: { # Disable `git-lfs` check here since we check it ourselves # We have to include the command output here, otherwise Nix won't run it From 27216d9a5e9cc7c8da9dff8a51b3dd5148666909 Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Sun, 13 Jun 2021 19:23:00 +0300 Subject: [PATCH 26/37] build(nix): dont try to compile the recipe_graphviz binary --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 199b8be1b5..26bebdf085 100644 --- a/flake.nix +++ b/flake.nix @@ -79,7 +79,7 @@ # Declare env values here so that `common/build.rs` sees them NIX_GIT_HASH = prettyRev; NIX_GIT_TAG = tag; - crateBin = lib.filter (bin: bin.name != "csv_export" && bin.name != "csv_import") oldAttrs.crateBin; + crateBin = lib.filter (bin: bin.name != "csv_export" && bin.name != "csv_import" && bin.name != "recipe_graphviz") oldAttrs.crateBin; }; veloren-voxygen = oldAttrs: { VELOREN_USERDATA_STRATEGY = "system"; From e27a6fcaa1a714b6d6c923ac5bf1883a26f36d82 Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Sun, 13 Jun 2021 19:33:38 +0300 Subject: [PATCH 27/37] build(nix): switch back to master branch nix-cargo-integration, use defaultOutputs --- flake.lock | 7 +- flake.nix | 196 ++++++++++++++++++++++++++--------------------------- 2 files changed, 100 insertions(+), 103 deletions(-) diff --git a/flake.lock b/flake.lock index 47a42b63e2..dace752322 100644 --- a/flake.lock +++ b/flake.lock @@ -24,16 +24,15 @@ "rustOverlay": "rustOverlay" }, "locked": { - "lastModified": 1623594891, - "narHash": "sha256-JmjOTdIW0QIsMoqU6oIQx9fqH0Ims83g2jK8jmokHsI=", + "lastModified": 1623601923, + "narHash": "sha256-zQDRV3OMYKlewfp3k0ilmFNfcFDxBCCk0tbcde8HMYM=", "owner": "yusdacra", "repo": "nix-cargo-integration", - "rev": "5394e8fa179346854e44e05edced0b9d7dc818b7", + "rev": "ab922adefdeb387ce105b1bde75f62ffb971cec4", "type": "github" }, "original": { "owner": "yusdacra", - "ref": "fix/crate2nix", "repo": "nix-cargo-integration", "type": "github" } diff --git a/flake.nix b/flake.nix index 26bebdf085..e31ceb2257 100644 --- a/flake.nix +++ b/flake.nix @@ -4,110 +4,108 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixCargoIntegration = { - url = "github:yusdacra/nix-cargo-integration/fix/crate2nix"; + url = "github:yusdacra/nix-cargo-integration"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = inputs: - let - output = inputs.nixCargoIntegration.lib.makeOutputs { - root = ./.; - buildPlatform = "crate2nix"; - overrides = { - build = common: prev: { - runTests = !prev.release && prev.runTests; - }; - crateOverrides = common: prev: - let - pkgs = common.pkgs; - lib = common.lib; - - gitLfsCheckFile = ./assets/voxygen/background/bg_main.png; - utils = import ./nix/utils.nix { inherit pkgs; }; - - sourceInfo = - if inputs.self.sourceInfo ? rev - then inputs.self.sourceInfo // { - # Tag would have to be set manually for stable releases flake - # because there's currently no way to get the tag via the interface. - # tag = v0.9.0; - } - else (throw "Can't get revision because the git tree is dirty"); - - prettyRev = with sourceInfo; builtins.substring 0 8 rev + "/" + utils.dateTimeFormat lastModified; - - tag = with sourceInfo; - if sourceInfo ? tag - then sourceInfo.tag - else ""; - - # If gitTag has a tag (meaning the commit we are on is a *release*), use - # it as version, else: just use the prettified hash we have, if we don't - # have it the build fails. - # Must be in format f4987672/2020-12-10-12:00 - version = - if tag != "" then tag - else if prettyRev != "" then prettyRev - else throw "Need a tag or pretty revision in order to determine version"; - - veloren-assets = pkgs.runCommand "makeAssetsDir" { } '' - mkdir $out - ln -sf ${./assets} $out/assets - ''; - in - { - # veloren-world = oldAttrs: { - # crateBin = lib.filter (bin: bin.name != "chunk_compression_benchmarks") oldAttrs.crateBin; - # }; - shaderc-sys = _: - let SHADERC_LIB_DIR = "${common.pkgs.shaderc.lib}/lib"; in - { - inherit SHADERC_LIB_DIR; - propagatedEnv = { inherit SHADERC_LIB_DIR; }; - }; - veloren-client = oldAttrs: { - crateBin = lib.filter (bin: bin.name != "bot") oldAttrs.crateBin; - }; - veloren-i18n = oldAttrs: { - crateBin = lib.filter (bin: bin.name != "i18n-check") oldAttrs.crateBin; - }; - veloren-common = oldAttrs: { - # Disable `git-lfs` check here since we check it ourselves - # We have to include the command output here, otherwise Nix won't run it - DISABLE_GIT_LFS_CHECK = utils.isGitLfsSetup gitLfsCheckFile; - # Declare env values here so that `common/build.rs` sees them - NIX_GIT_HASH = prettyRev; - NIX_GIT_TAG = tag; - crateBin = lib.filter (bin: bin.name != "csv_export" && bin.name != "csv_import" && bin.name != "recipe_graphviz") oldAttrs.crateBin; - }; - veloren-voxygen = oldAttrs: { - VELOREN_USERDATA_STRATEGY = "system"; - postInstall = '' - if [ -f $out/bin/veloren-voxygen ]; then - wrapProgram $out/bin/veloren-voxygen \ - --set VELOREN_ASSETS ${veloren-assets} \ - --set LD_LIBRARY_PATH ${lib.makeLibraryPath common.runtimeLibs} - fi - ''; - patches = [ - (import ./nix/nullOggPatch.nix { nullOgg = ./assets/voxygen/audio/null.ogg; inherit pkgs; }) - ]; - }; - veloren-server-cli = oldAttrs: { - VELOREN_USERDATA_STRATEGY = "system"; - postInstall = '' - if [ -f $out/bin/veloren-server-cli ]; then - wrapProgram $out/bin/veloren-server-cli --set VELOREN_ASSETS ${veloren-assets} - fi - ''; - }; - }; - }; + inputs.nixCargoIntegration.lib.makeOutputs { + root = ./.; + buildPlatform = "crate2nix"; + defaultOutputs = { + package = "veloren-voxygen"; + app = "veloren-voxygen"; + }; + overrides = { + build = common: prev: { + runTests = !prev.release && prev.runTests; + }; + crateOverrides = common: prev: + let + pkgs = common.pkgs; + lib = common.lib; + + gitLfsCheckFile = ./assets/voxygen/background/bg_main.png; + utils = import ./nix/utils.nix { inherit pkgs; }; + + sourceInfo = + if inputs.self.sourceInfo ? rev + then inputs.self.sourceInfo // { + # Tag would have to be set manually for stable releases flake + # because there's currently no way to get the tag via the interface. + # tag = v0.9.0; + } + else (throw "Can't get revision because the git tree is dirty"); + + prettyRev = with sourceInfo; builtins.substring 0 8 rev + "/" + utils.dateTimeFormat lastModified; + + tag = with sourceInfo; + if sourceInfo ? tag + then sourceInfo.tag + else ""; + + # If gitTag has a tag (meaning the commit we are on is a *release*), use + # it as version, else: just use the prettified hash we have, if we don't + # have it the build fails. + # Must be in format f4987672/2020-12-10-12:00 + version = + if tag != "" then tag + else if prettyRev != "" then prettyRev + else throw "Need a tag or pretty revision in order to determine version"; + + veloren-assets = pkgs.runCommand "makeAssetsDir" { } '' + mkdir $out + ln -sf ${./assets} $out/assets + ''; + in + { + # veloren-world = oldAttrs: { + # crateBin = lib.filter (bin: bin.name != "chunk_compression_benchmarks") oldAttrs.crateBin; + # }; + shaderc-sys = _: + let SHADERC_LIB_DIR = "${common.pkgs.shaderc.lib}/lib"; in + { + inherit SHADERC_LIB_DIR; + propagatedEnv = { inherit SHADERC_LIB_DIR; }; + }; + veloren-client = oldAttrs: { + crateBin = lib.filter (bin: bin.name != "bot") oldAttrs.crateBin; + }; + veloren-i18n = oldAttrs: { + crateBin = lib.filter (bin: bin.name != "i18n-check") oldAttrs.crateBin; + }; + veloren-common = oldAttrs: { + # Disable `git-lfs` check here since we check it ourselves + # We have to include the command output here, otherwise Nix won't run it + DISABLE_GIT_LFS_CHECK = utils.isGitLfsSetup gitLfsCheckFile; + # Declare env values here so that `common/build.rs` sees them + NIX_GIT_HASH = prettyRev; + NIX_GIT_TAG = tag; + crateBin = lib.filter (bin: bin.name != "csv_export" && bin.name != "csv_import" && bin.name != "recipe_graphviz") oldAttrs.crateBin; + }; + veloren-voxygen = oldAttrs: { + VELOREN_USERDATA_STRATEGY = "system"; + postInstall = '' + if [ -f $out/bin/veloren-voxygen ]; then + wrapProgram $out/bin/veloren-voxygen \ + --set VELOREN_ASSETS ${veloren-assets} \ + --set LD_LIBRARY_PATH ${lib.makeLibraryPath common.runtimeLibs} + fi + ''; + patches = [ + (import ./nix/nullOggPatch.nix { nullOgg = ./assets/voxygen/audio/null.ogg; inherit pkgs; }) + ]; + }; + veloren-server-cli = oldAttrs: { + VELOREN_USERDATA_STRATEGY = "system"; + postInstall = '' + if [ -f $out/bin/veloren-server-cli ]; then + wrapProgram $out/bin/veloren-server-cli --set VELOREN_ASSETS ${veloren-assets} + fi + ''; + }; + }; }; - in - output // { - defaultApp = builtins.mapAttrs (_: apps: apps.veloren-voxygen) output.apps; - defaultPackage = builtins.mapAttrs (_: packages: packages.veloren-voxygen) output.packages; }; } From 0e394029de70651045419cb758f15e1bd4330c1d Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Wed, 9 Jun 2021 01:14:20 -0400 Subject: [PATCH 28/37] Mining skill tree. --- CHANGELOG.md | 1 + .../common/skill_trees/skill_max_levels.ron | 3 + .../skills_skill-groups_manifest.ron | 5 ++ .../resource_experience_manifest.ron | 19 +++++ .../element/skills/pickaxe_gemgain.png | 3 + .../element/skills/pickaxe_oregain.png | 3 + .../voxygen/element/skills/pickaxe_speed.png | 3 + assets/voxygen/element/weapons/pickaxe.png | 3 + assets/voxygen/i18n/en/skills.ron | 9 +++ assets/voxygen/item_image_manifest.ron | 4 + common/src/cmd.rs | 2 +- common/src/comp/ability.rs | 17 ++++ common/src/comp/inventory/item/tool.rs | 15 ++++ common/src/comp/skills.rs | 29 ++++--- common/src/event.rs | 1 + common/systems/src/melee.rs | 1 + server/src/cmd.rs | 1 + server/src/events/entity_manipulation.rs | 13 ++-- server/src/events/interaction.rs | 78 ++++++++++++++++++- server/src/events/mod.rs | 4 +- server/src/migrations/V41__mining_tree.sql | 7 ++ server/src/persistence/json_models.rs | 17 ++-- voxygen/src/hud/diary.rs | 73 ++++++++++++++++- voxygen/src/hud/img_ids.rs | 5 ++ voxygen/src/hud/mod.rs | 1 + 25 files changed, 290 insertions(+), 27 deletions(-) create mode 100644 assets/server/manifests/resource_experience_manifest.ron create mode 100644 assets/voxygen/element/skills/pickaxe_gemgain.png create mode 100644 assets/voxygen/element/skills/pickaxe_oregain.png create mode 100644 assets/voxygen/element/skills/pickaxe_speed.png create mode 100644 assets/voxygen/element/weapons/pickaxe.png create mode 100644 server/src/migrations/V41__mining_tree.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 4466ae1851..99fa93a4cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Meat drops from animals - New ores, plants and hides to be looted from the world and processed into craft ingredients - Added more crafting stations, loom, spinning wheel, tanning rack, forge +- Added a skill tree for mining, which gains xp from mining ores and gems. ### Changed diff --git a/assets/common/skill_trees/skill_max_levels.ron b/assets/common/skill_trees/skill_max_levels.ron index 275c23b15b..a319299903 100644 --- a/assets/common/skill_trees/skill_max_levels.ron +++ b/assets/common/skill_trees/skill_max_levels.ron @@ -76,4 +76,7 @@ Climb(Cost): Some(2), Climb(Speed): Some(2), Swim(Speed): Some(2), + Pick(Speed): Some(3), + Pick(OreGain): Some(3), + Pick(GemGain): Some(3), }) diff --git a/assets/common/skill_trees/skills_skill-groups_manifest.ron b/assets/common/skill_trees/skills_skill-groups_manifest.ron index 220f2b1e97..641ba74520 100644 --- a/assets/common/skill_trees/skills_skill-groups_manifest.ron +++ b/assets/common/skill_trees/skills_skill-groups_manifest.ron @@ -109,4 +109,9 @@ Sceptre(ARange), Sceptre(ACost), ], + Weapon(Pick): [ + Pick(Speed), + Pick(OreGain), + Pick(GemGain), + ], }) diff --git a/assets/server/manifests/resource_experience_manifest.ron b/assets/server/manifests/resource_experience_manifest.ron new file mode 100644 index 0000000000..6345e0d392 --- /dev/null +++ b/assets/server/manifests/resource_experience_manifest.ron @@ -0,0 +1,19 @@ +ResourceExperienceManifest({ + "common.items.mineral.gem.amethyst": 20, + "common.items.mineral.gem.sapphire": 50, + "common.items.mineral.gem.topaz": 20, + "common.items.mineral.gem.diamond": 100, + "common.items.mineral.gem.emerald": 50, + "common.items.mineral.gem.ruby": 75, + + "common.items.mineral.ore.coal": 25, + "common.items.mineral.ore.gold": 100, + "common.items.mineral.ore.iron": 20, + "common.items.mineral.ore.silver": 75, + "common.items.mineral.ore.velorite": 30, + "common.items.mineral.ore.veloritefrag": 20, + "common.items.mineral.ore.bloodstone": 100, + "common.items.mineral.ore.cobalt": 75, + "common.items.mineral.ore.copper": 10, + "common.items.mineral.ore.tin": 10, +}) diff --git a/assets/voxygen/element/skills/pickaxe_gemgain.png b/assets/voxygen/element/skills/pickaxe_gemgain.png new file mode 100644 index 0000000000..3615cd1d7d --- /dev/null +++ b/assets/voxygen/element/skills/pickaxe_gemgain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87cb3de9b1db989b909b5db2b4d4f0f91773f881df5caeac5e7b92644c52b955 +size 1780 diff --git a/assets/voxygen/element/skills/pickaxe_oregain.png b/assets/voxygen/element/skills/pickaxe_oregain.png new file mode 100644 index 0000000000..9d42b64f7d --- /dev/null +++ b/assets/voxygen/element/skills/pickaxe_oregain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b3de05de2d4ca7a76769aa93369c92bbd18fe98c2b337a412c380d726808e4e +size 1790 diff --git a/assets/voxygen/element/skills/pickaxe_speed.png b/assets/voxygen/element/skills/pickaxe_speed.png new file mode 100644 index 0000000000..7a462f3940 --- /dev/null +++ b/assets/voxygen/element/skills/pickaxe_speed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27f66e968c4f475123d726c096e80bb34c2ebea856c4c0bf1d20969ac4d01dbc +size 1734 diff --git a/assets/voxygen/element/weapons/pickaxe.png b/assets/voxygen/element/weapons/pickaxe.png new file mode 100644 index 0000000000..0f4ec67ac9 --- /dev/null +++ b/assets/voxygen/element/weapons/pickaxe.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff8c3173b5d87c4bf00233d222d2b47045bbb3cf602afea5814d85167ce9f9da +size 1720 diff --git a/assets/voxygen/i18n/en/skills.ron b/assets/voxygen/i18n/en/skills.ron index 93ae9ffb7e..66250941ff 100644 --- a/assets/voxygen/i18n/en/skills.ron +++ b/assets/voxygen/i18n/en/skills.ron @@ -238,6 +238,15 @@ "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": "Increases distance of leap by 20%{SP}", + // Pick + "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}", }, diff --git a/assets/voxygen/item_image_manifest.ron b/assets/voxygen/item_image_manifest.ron index 5bb8796f1c..e0d711ad6a 100644 --- a/assets/voxygen/item_image_manifest.ron +++ b/assets/voxygen/item_image_manifest.ron @@ -65,6 +65,10 @@ "voxel.weapon.sceptre.wood-nature", (-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( "voxel.weapon.dagger.dagger_basic-0", (0.0, 0.0, 0.0), (90.0, 90.0, 0.0), 1.0, diff --git a/common/src/cmd.rs b/common/src/cmd.rs index e8f9054605..d8752cffd5 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -127,7 +127,7 @@ lazy_static! { .iter() .map(|s| s.to_string()) .collect(); - static ref SKILL_TREES: Vec = vec!["general", "sword", "axe", "hammer", "bow", "staff", "sceptre"] + static ref SKILL_TREES: Vec = vec!["general", "sword", "axe", "hammer", "bow", "staff", "sceptre", "pick"] .iter() .map(|s| s.to_string()) .collect(); diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 8ab75b8928..bab3f56117 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -1174,6 +1174,23 @@ impl CharacterAbility { _ => {}, } }, + Some(ToolKind::Pick) => { + use skills::PickSkill::*; + 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 => { if let CharacterAbility::Roll { ref mut energy_cost, diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index e91b52c0de..c0f3ee2d03 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -50,6 +50,21 @@ impl ToolKind { 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)] diff --git a/common/src/comp/skills.rs b/common/src/comp/skills.rs index e1c113d8f2..c130b067c9 100644 --- a/common/src/comp/skills.rs +++ b/common/src/comp/skills.rs @@ -107,6 +107,7 @@ pub enum Skill { Roll(RollSkill), Climb(ClimbSkill), Swim(SwimSkill), + Pick(PickSkill), } pub enum SkillError { @@ -263,6 +264,13 @@ pub enum SwimSkill { Speed, } +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum PickSkill { + Speed, + OreGain, + GemGain, +} + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum SkillGroupKind { General, @@ -344,7 +352,10 @@ impl Default for SkillSet { /// player fn default() -> 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(), modify_health: false, modify_energy: false, @@ -365,7 +376,7 @@ impl SkillSet { /// let mut skillset = SkillSet::default(); /// 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) { if !self.contains_skill_group(skill_group_kind) { @@ -668,13 +679,13 @@ mod tests { skillset.add_skill_points(SkillGroupKind::Weapon(ToolKind::Axe), 1); 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!(skillset.has_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); } @@ -683,9 +694,9 @@ mod tests { let mut skillset = SkillSet::default(); skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)); - assert_eq!(skillset.skill_groups.len(), 2); + assert_eq!(skillset.skill_groups.len(), 3); assert_eq!( - skillset.skill_groups[1], + skillset.skill_groups[2], SkillGroup::new(SkillGroupKind::Weapon(ToolKind::Axe)) ); } @@ -697,13 +708,13 @@ mod tests { skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)); 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); // Try unlocking a skill with enough skill points 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!(skillset.has_skill(Skill::Axe(AxeSkill::UnlockLeap))); @@ -720,6 +731,6 @@ mod tests { skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)); 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); } } diff --git a/common/src/event.rs b/common/src/event.rs index 1c507d0876..d21eee05c5 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -164,6 +164,7 @@ pub enum ServerEvent { }, // Attempt to mine a block, turning it into an item MineBlock { + entity: EcsEntity, pos: Vec3, tool: Option, }, diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index 561b9a4b5b..46a3b004fd 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -86,6 +86,7 @@ impl<'a> System<'a> for Sys { < (rad + scale * melee_attack.range).powi(2) { server_emitter.emit(ServerEvent::MineBlock { + entity: attacker, pos: block_pos, tool, }); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 926b439588..eb6af36349 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -2610,6 +2610,7 @@ fn parse_skill_tree(skill_tree: &str) -> CmdResult "bow" => Ok(SkillGroupKind::Weapon(ToolKind::Bow)), "staff" => Ok(SkillGroupKind::Weapon(ToolKind::Staff)), "sceptre" => Ok(SkillGroupKind::Weapon(ToolKind::Sceptre)), + "pick" => Ok(SkillGroupKind::Weapon(ToolKind::Pick)), _ => Err(format!("{} is not a skill group!", skill_tree)), } } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 78b14d3517..c559ba19b6 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -992,13 +992,12 @@ fn handle_exp_gain( // Closure to add xp pool corresponding to weapon type equipped in a particular // EquipSlot let mut add_tool_from_slot = |equip_slot| { - let tool_kind = inventory.equipped(equip_slot).and_then(|i| { - if let ItemKind::Tool(tool) = &i.kind() { - Some(tool.kind) - } else { - None - } - }); + let tool_kind = inventory + .equipped(equip_slot) + .and_then(|i| match &i.kind() { + ItemKind::Tool(tool) if tool.kind.gains_combat_xp() => Some(tool.kind), + _ => None, + }); if let Some(weapon) = tool_kind { // Only adds to xp pools if entity has that skill group available if skill_set.contains_skill_group(SkillGroupKind::Weapon(weapon)) { diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index e8c4a5e0e9..3220a8a25b 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -3,6 +3,7 @@ use tracing::error; use vek::*; use common::{ + assets, comp::{ self, agent::{AgentEvent, Sound, MAX_LISTEN_DIST}, @@ -11,7 +12,7 @@ use common::{ item, slot::Slot, tool::ToolKind, - Inventory, Pos, + Inventory, Pos, SkillGroupKind, }, consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME}, outcome::Outcome, @@ -27,6 +28,10 @@ use crate::{ Server, }; +use hashbrown::HashMap; +use lazy_static::lazy_static; +use serde::Deserialize; + pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) { let ecs = server.state_mut().ecs(); @@ -281,13 +286,80 @@ fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<& } } -pub fn handle_mine_block(server: &mut Server, pos: Vec3, tool: Option) { +#[derive(Deserialize)] +struct ResourceExperienceManifest(HashMap); + +impl assets::Asset for ResourceExperienceManifest { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} + +lazy_static! { + static ref RESOURCE_EXPERIENCE_MANIFEST: assets::AssetHandle = + assets::AssetExt::load_expect("server.manifests.resource_experience_manifest"); +} + +pub fn handle_mine_block( + server: &mut Server, + entity: EcsEntity, + pos: Vec3, + tool: Option, +) { let state = server.state_mut(); if state.can_set_block(pos) { 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)) { // 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::() + .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::>() + .push(Outcome::ExpChange { + uid, + exp: *exp_reward, + }); + } + use common::comp::skills::{PickSkill, 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(PickSkill::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(PickSkill::GemGain)) + .ok() + .flatten() + .unwrap_or(0) as f64, + ) + { + let _ = item.increase_amount(1); + } + } state .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))) diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index c808e8f28a..7079b3bda3 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -207,7 +207,9 @@ impl Server { handle_combo_change(&self, entity, change) }, 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 { entity, target, diff --git a/server/src/migrations/V41__mining_tree.sql b/server/src/migrations/V41__mining_tree.sql new file mode 100644 index 0000000000..e4cd339df7 --- /dev/null +++ b/server/src/migrations/V41__mining_tree.sql @@ -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; diff --git a/server/src/persistence/json_models.rs b/server/src/persistence/json_models.rs index 81d9531022..b74955ae46 100644 --- a/server/src/persistence/json_models.rs +++ b/server/src/persistence/json_models.rs @@ -40,8 +40,8 @@ pub fn skill_to_db_string(skill: comp::skills::Skill) -> String { use comp::{ item::tool::ToolKind, skills::{ - AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, RollSkill, SceptreSkill, - Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, + AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, PickSkill, RollSkill, + SceptreSkill, Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, }, }; 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::Speed) => "Climb Speed", Swim(SwimSkill::Speed) => "Swim Speed", + Pick(PickSkill::Speed) => "Pick Speed", + Pick(PickSkill::OreGain) => "Pick OreGain", + Pick(PickSkill::GemGain) => "Pick GemGain", UnlockGroup(SkillGroupKind::Weapon(ToolKind::Sword)) => "Unlock Weapon Sword", UnlockGroup(SkillGroupKind::Weapon(ToolKind::Axe)) => "Unlock Weapon Axe", 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::{ item::tool::ToolKind, skills::{ - AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, RollSkill, SceptreSkill, - Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, + AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, PickSkill, RollSkill, + SceptreSkill, Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, }, }; 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 Speed" => Climb(ClimbSkill::Speed), "Swim Speed" => Swim(SwimSkill::Speed), + "Pick Speed" => Pick(PickSkill::Speed), + "Pick GemGain" => Pick(PickSkill::GemGain), + "Pick OreGain" => Pick(PickSkill::OreGain), "Unlock Weapon Sword" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Sword)), "Unlock Weapon Axe" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Axe)), "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::Staff) => "Weapon Staff", Weapon(ToolKind::Sceptre) => "Weapon Sceptre", + Weapon(ToolKind::Pick) => "Weapon Pick", Weapon(ToolKind::Dagger) | Weapon(ToolKind::Shield) | Weapon(ToolKind::Spear) | Weapon(ToolKind::Debug) | Weapon(ToolKind::Farming) - | Weapon(ToolKind::Pick) | Weapon(ToolKind::Empty) | Weapon(ToolKind::Natural) => panic!( "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 Staff" => Weapon(ToolKind::Staff), "Weapon Sceptre" => Weapon(ToolKind::Sceptre), + "Weapon Pick" => Weapon(ToolKind::Pick), _ => panic!( "Tried to convert an unsupported string from the database: {}", skill_group_string diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index 2e679c6362..48182bda38 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -149,6 +149,11 @@ widget_ids! { skill_sceptre_aura_2, skill_sceptre_aura_3, 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_1, skill_general_stat_0, @@ -227,7 +232,7 @@ impl<'a> Diary<'a> { pub type SelectedSkillTree = skills::SkillGroupKind; -const TREES: [&str; 7] = [ +const TREES: [&str; 8] = [ "General Combat", "Sword", "Hammer", @@ -235,6 +240,7 @@ const TREES: [&str; 7] = [ "Sceptre", "Bow", "Fire Staff", + "Pickaxe", ]; pub enum Event { @@ -353,6 +359,7 @@ impl<'a> Widget for Diary<'a> { "Sceptre" => self.imgs.sceptre, "Bow" => self.imgs.bow, "Fire Staff" => self.imgs.staff, + "Pickaxe" => self.imgs.pickaxe, _ => self.imgs.nothing, }); @@ -500,6 +507,9 @@ impl<'a> Widget for Diary<'a> { SelectedSkillTree::Weapon(ToolKind::Staff) => { self.localized_strings.get("common.weapons.staff") }, + SelectedSkillTree::Weapon(ToolKind::Pick) => { + self.localized_strings.get("common.tool.pick") + }, _ => "Unknown", }; 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::Staff) => 4, SelectedSkillTree::Weapon(ToolKind::Sceptre) => 5, + SelectedSkillTree::Weapon(ToolKind::Pick) => 4, _ => 0, }; let skills_top_r = match sel_tab { @@ -1976,6 +1987,65 @@ impl<'a> Widget for Diary<'a> { &diary_tooltip, ); }, + SelectedSkillTree::Weapon(ToolKind::Pick) => { + use skills::PickSkill::*; + // Pick + 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 { "Sceptre" => Some(SelectedSkillTree::Weapon(ToolKind::Sceptre)), "Bow" => Some(SelectedSkillTree::Weapon(ToolKind::Bow)), "Fire Staff" => Some(SelectedSkillTree::Weapon(ToolKind::Staff)), + "Pickaxe" => Some(SelectedSkillTree::Weapon(ToolKind::Pick)), _ => None, } } diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index f96bd8b610..e451dd3409 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -78,6 +78,7 @@ image_ids! { hammer: "voxygen.element.weapons.hammer", bow: "voxygen.element.weapons.bow", staff: "voxygen.element.weapons.staff", + pickaxe: "voxygen.element.weapons.pickaxe", lock: "voxygen.element.ui.diary.buttons.lock", wpn_icon_border_skills: "voxygen.element.ui.diary.buttons.border_skills", wpn_icon_border: "voxygen.element.ui.generic.buttons.border", @@ -303,6 +304,10 @@ image_ids! { utility_speed_skill: "voxygen.element.skills.skilltree.utility_speed", 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 level_up: "voxygen.element.ui.skillbar.level_up", bar_content: "voxygen.element.ui.skillbar.bar_content", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 585361849d..90aef9563f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1353,6 +1353,7 @@ impl Hud { Weapon(ToolKind::Sceptre) => &i18n.get("common.weapons.sceptre"), Weapon(ToolKind::Bow) => &i18n.get("common.weapons.bow"), Weapon(ToolKind::Staff) => &i18n.get("common.weapons.staff"), + Weapon(ToolKind::Pick) => &i18n.get("common.tool.pick"), _ => "Unknown", }; Text::new(skill) From 43b6780c98d39edc944e17d3fd09b4b1ce7f0a64 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Wed, 9 Jun 2021 01:35:30 -0400 Subject: [PATCH 29/37] Address MR 2406 comments. - Tweak ore xp values per Slipped's advice. --- assets/server/manifests/resource_experience_manifest.ron | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/server/manifests/resource_experience_manifest.ron b/assets/server/manifests/resource_experience_manifest.ron index 6345e0d392..c597adef8f 100644 --- a/assets/server/manifests/resource_experience_manifest.ron +++ b/assets/server/manifests/resource_experience_manifest.ron @@ -9,11 +9,11 @@ ResourceExperienceManifest({ "common.items.mineral.ore.coal": 25, "common.items.mineral.ore.gold": 100, "common.items.mineral.ore.iron": 20, - "common.items.mineral.ore.silver": 75, + "common.items.mineral.ore.silver": 90, "common.items.mineral.ore.velorite": 30, "common.items.mineral.ore.veloritefrag": 20, - "common.items.mineral.ore.bloodstone": 100, - "common.items.mineral.ore.cobalt": 75, + "common.items.mineral.ore.bloodstone": 80, + "common.items.mineral.ore.cobalt": 60, "common.items.mineral.ore.copper": 10, "common.items.mineral.ore.tin": 10, }) From 01a04a80fa2699961f2831d48b8213766fbaabbe Mon Sep 17 00:00:00 2001 From: Monty Marz Date: Wed, 9 Jun 2021 21:07:34 +0200 Subject: [PATCH 30/37] icons and naming --- .../element/{weapons => skills}/pickaxe.png | 0 assets/voxygen/element/weapons/axe.png | 4 ++-- assets/voxygen/element/weapons/bow.png | 4 ++-- assets/voxygen/element/weapons/daggers.png | 4 ++-- assets/voxygen/element/weapons/hammer.png | 4 ++-- assets/voxygen/element/weapons/mining.png | 3 +++ assets/voxygen/element/weapons/staff.png | 4 ++-- assets/voxygen/element/weapons/sword.png | 4 ++-- assets/voxygen/i18n/en/common.ron | 1 + assets/voxygen/i18n/en/skills.ron | 3 ++- common/src/comp/ability.rs | 2 +- common/src/comp/skills.rs | 4 ++-- server/src/events/interaction.rs | 6 +++--- server/src/persistence/json_models.rs | 16 ++++++++-------- voxygen/src/hud/diary.rs | 12 ++++++------ voxygen/src/hud/img_ids.rs | 3 ++- voxygen/src/hud/mod.rs | 3 ++- 17 files changed, 42 insertions(+), 35 deletions(-) rename assets/voxygen/element/{weapons => skills}/pickaxe.png (100%) create mode 100644 assets/voxygen/element/weapons/mining.png diff --git a/assets/voxygen/element/weapons/pickaxe.png b/assets/voxygen/element/skills/pickaxe.png similarity index 100% rename from assets/voxygen/element/weapons/pickaxe.png rename to assets/voxygen/element/skills/pickaxe.png diff --git a/assets/voxygen/element/weapons/axe.png b/assets/voxygen/element/weapons/axe.png index ee44e81671..79274d2f20 100644 --- a/assets/voxygen/element/weapons/axe.png +++ b/assets/voxygen/element/weapons/axe.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37844f6d3ad21e036df2e8dd43bbb05b96728e00295084179a4841585ecabfef -size 168 +oid sha256:ad4f860670fef14577606c9b39f393484ecf6568cdf79d7d36b384fbf439eb90 +size 285 diff --git a/assets/voxygen/element/weapons/bow.png b/assets/voxygen/element/weapons/bow.png index d147b3e33f..136bfa3d86 100644 --- a/assets/voxygen/element/weapons/bow.png +++ b/assets/voxygen/element/weapons/bow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a1380273d271639ff7d3016fe10331f5847e7e2339f6bbcb2eb7f9de1f714d1 -size 169 +oid sha256:8fb101f42ea28b599fdfab86ca06c59c46edd03fa4c854230465bd04f93a9d11 +size 294 diff --git a/assets/voxygen/element/weapons/daggers.png b/assets/voxygen/element/weapons/daggers.png index 2a5671b5c7..e2b5dd7c19 100644 --- a/assets/voxygen/element/weapons/daggers.png +++ b/assets/voxygen/element/weapons/daggers.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7018ea3f92db46e0e2fde2e28b8c023156d52dd7445611bb189c996a067844dd -size 183 +oid sha256:ee65025e8edaafaf00d43fe4f0af74e15bd9263bd9f399f60ca8b87ba807aa9b +size 319 diff --git a/assets/voxygen/element/weapons/hammer.png b/assets/voxygen/element/weapons/hammer.png index c96bcd6a1a..16990f4088 100644 --- a/assets/voxygen/element/weapons/hammer.png +++ b/assets/voxygen/element/weapons/hammer.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6244d3f8f071eff3a3ac3413bd4b20f4c0c555dc8e656574bc0de25d166fb741 -size 157 +oid sha256:c23362300e7b0cddf081fc664ec2cc2a8accf0a6af87129cf853020c238946de +size 276 diff --git a/assets/voxygen/element/weapons/mining.png b/assets/voxygen/element/weapons/mining.png new file mode 100644 index 0000000000..e6b4f1a2b2 --- /dev/null +++ b/assets/voxygen/element/weapons/mining.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d40df9df229492a46afaa7236177d6f0ba0536e9e1bae4c4a46063ee588187a +size 435 diff --git a/assets/voxygen/element/weapons/staff.png b/assets/voxygen/element/weapons/staff.png index 3455ac846a..4ca0d49763 100644 --- a/assets/voxygen/element/weapons/staff.png +++ b/assets/voxygen/element/weapons/staff.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b7ee9205baca5c27bfa681ef0fe64ca49b2f326265777522df15f503a4b0b3d -size 149 +oid sha256:bda8b960270c230a49bb4e29f9f65598f08359672c8c5256b1efea8015811e8b +size 279 diff --git a/assets/voxygen/element/weapons/sword.png b/assets/voxygen/element/weapons/sword.png index d76d41b7e2..2ebd2b3293 100644 --- a/assets/voxygen/element/weapons/sword.png +++ b/assets/voxygen/element/weapons/sword.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46b2aaa0d65ad5e645c460ec76029cdc42aedf7ceb5b1840e2a9e8885b57c75d -size 158 +oid sha256:1c293dfb161fd9c99670913ac96c55103907fb0dd9e8812f81406a8acda51b59 +size 288 diff --git a/assets/voxygen/i18n/en/common.ron b/assets/voxygen/i18n/en/common.ron index 6694770192..1dd6bf2c10 100644 --- a/assets/voxygen/i18n/en/common.ron +++ b/assets/voxygen/i18n/en/common.ron @@ -78,6 +78,7 @@ Is the client up to date?"#, "common.tool.debug": "Debug", "common.tool.faming": "Farming Tool", "common.tool.pick": "Pickaxe", + "common.tool.mining": "Mining", "common.kind.modular_component": "Modular Component", "common.kind.glider": "Glider", "common.kind.consumable": "Consumable", diff --git a/assets/voxygen/i18n/en/skills.ron b/assets/voxygen/i18n/en/skills.ron index 66250941ff..1a4d6ab3b8 100644 --- a/assets/voxygen/i18n/en/skills.ron +++ b/assets/voxygen/i18n/en/skills.ron @@ -238,7 +238,8 @@ "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": "Increases distance of leap by 20%{SP}", - // Pick + // 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", diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index bab3f56117..5fa8c3c112 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -1175,7 +1175,7 @@ impl CharacterAbility { } }, Some(ToolKind::Pick) => { - use skills::PickSkill::*; + use skills::MiningSkill::*; if let BasicMelee { ref mut buildup_duration, ref mut swing_duration, diff --git a/common/src/comp/skills.rs b/common/src/comp/skills.rs index c130b067c9..bd7e999f04 100644 --- a/common/src/comp/skills.rs +++ b/common/src/comp/skills.rs @@ -107,7 +107,7 @@ pub enum Skill { Roll(RollSkill), Climb(ClimbSkill), Swim(SwimSkill), - Pick(PickSkill), + Pick(MiningSkill), } pub enum SkillError { @@ -265,7 +265,7 @@ pub enum SwimSkill { } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum PickSkill { +pub enum MiningSkill { Speed, OreGain, GemGain, diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 3220a8a25b..9653ef4fc1 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -334,13 +334,13 @@ pub fn handle_mine_block( exp: *exp_reward, }); } - use common::comp::skills::{PickSkill, Skill}; + 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(PickSkill::OreGain)) + .skill_level(Skill::Pick(MiningSkill::OreGain)) .ok() .flatten() .unwrap_or(0) as f64, @@ -351,7 +351,7 @@ pub fn handle_mine_block( if item.item_definition_id().contains("mineral.gem.") && rng.gen_bool( 0.05 * skillset - .skill_level(Skill::Pick(PickSkill::GemGain)) + .skill_level(Skill::Pick(MiningSkill::GemGain)) .ok() .flatten() .unwrap_or(0) as f64, diff --git a/server/src/persistence/json_models.rs b/server/src/persistence/json_models.rs index b74955ae46..d359cc4672 100644 --- a/server/src/persistence/json_models.rs +++ b/server/src/persistence/json_models.rs @@ -40,7 +40,7 @@ pub fn skill_to_db_string(skill: comp::skills::Skill) -> String { use comp::{ item::tool::ToolKind, skills::{ - AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, PickSkill, RollSkill, + AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, MiningSkill, RollSkill, SceptreSkill, Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, }, }; @@ -135,9 +135,9 @@ pub fn skill_to_db_string(skill: comp::skills::Skill) -> String { Climb(ClimbSkill::Cost) => "Climb Cost", Climb(ClimbSkill::Speed) => "Climb Speed", Swim(SwimSkill::Speed) => "Swim Speed", - Pick(PickSkill::Speed) => "Pick Speed", - Pick(PickSkill::OreGain) => "Pick OreGain", - Pick(PickSkill::GemGain) => "Pick GemGain", + 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::Axe)) => "Unlock Weapon Axe", UnlockGroup(SkillGroupKind::Weapon(ToolKind::Hammer)) => "Unlock Weapon Hammer", @@ -163,7 +163,7 @@ pub fn db_string_to_skill(skill_string: &str) -> comp::skills::Skill { use comp::{ item::tool::ToolKind, skills::{ - AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, PickSkill, RollSkill, + AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, MiningSkill, RollSkill, SceptreSkill, Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, }, }; @@ -258,9 +258,9 @@ pub fn db_string_to_skill(skill_string: &str) -> comp::skills::Skill { "Climb Cost" => Climb(ClimbSkill::Cost), "Climb Speed" => Climb(ClimbSkill::Speed), "Swim Speed" => Swim(SwimSkill::Speed), - "Pick Speed" => Pick(PickSkill::Speed), - "Pick GemGain" => Pick(PickSkill::GemGain), - "Pick OreGain" => Pick(PickSkill::OreGain), + "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 Axe" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Axe)), "Unlock Weapon Hammer" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Hammer)), diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index 48182bda38..96a0cb6fc7 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -240,7 +240,7 @@ const TREES: [&str; 8] = [ "Sceptre", "Bow", "Fire Staff", - "Pickaxe", + "Mining", ]; pub enum Event { @@ -359,7 +359,7 @@ impl<'a> Widget for Diary<'a> { "Sceptre" => self.imgs.sceptre, "Bow" => self.imgs.bow, "Fire Staff" => self.imgs.staff, - "Pickaxe" => self.imgs.pickaxe, + "Mining" => self.imgs.mining, _ => self.imgs.nothing, }); @@ -508,7 +508,7 @@ impl<'a> Widget for Diary<'a> { self.localized_strings.get("common.weapons.staff") }, SelectedSkillTree::Weapon(ToolKind::Pick) => { - self.localized_strings.get("common.tool.pick") + self.localized_strings.get("common.tool.mining") }, _ => "Unknown", }; @@ -1988,8 +1988,8 @@ impl<'a> Widget for Diary<'a> { ); }, SelectedSkillTree::Weapon(ToolKind::Pick) => { - use skills::PickSkill::*; - // Pick + use skills::MiningSkill::*; + // Mining Image::new(animate_by_pulse( &self .item_imgs @@ -2104,7 +2104,7 @@ fn skill_tree_from_str(string: &str) -> Option { "Sceptre" => Some(SelectedSkillTree::Weapon(ToolKind::Sceptre)), "Bow" => Some(SelectedSkillTree::Weapon(ToolKind::Bow)), "Fire Staff" => Some(SelectedSkillTree::Weapon(ToolKind::Staff)), - "Pickaxe" => Some(SelectedSkillTree::Weapon(ToolKind::Pick)), + "Mining" => Some(SelectedSkillTree::Weapon(ToolKind::Pick)), _ => None, } } diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index e451dd3409..2a48f5e028 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -78,7 +78,8 @@ image_ids! { hammer: "voxygen.element.weapons.hammer", bow: "voxygen.element.weapons.bow", staff: "voxygen.element.weapons.staff", - pickaxe: "voxygen.element.weapons.pickaxe", + mining: "voxygen.element.weapons.mining", + pickaxe: "voxygen.element.skills.pickaxe", lock: "voxygen.element.ui.diary.buttons.lock", wpn_icon_border_skills: "voxygen.element.ui.diary.buttons.border_skills", wpn_icon_border: "voxygen.element.ui.generic.buttons.border", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 90aef9563f..605833dd5f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1353,7 +1353,7 @@ impl Hud { Weapon(ToolKind::Sceptre) => &i18n.get("common.weapons.sceptre"), Weapon(ToolKind::Bow) => &i18n.get("common.weapons.bow"), Weapon(ToolKind::Staff) => &i18n.get("common.weapons.staff"), - Weapon(ToolKind::Pick) => &i18n.get("common.tool.pick"), + Weapon(ToolKind::Pick) => &i18n.get("common.tool.mining"), _ => "Unknown", }; Text::new(skill) @@ -1378,6 +1378,7 @@ impl Hud { Weapon(ToolKind::Sceptre) => self.imgs.sceptre, Weapon(ToolKind::Bow) => self.imgs.bow, Weapon(ToolKind::Staff) => self.imgs.staff, + Weapon(ToolKind::Pick) => self.imgs.mining, _ => self.imgs.swords_crossed, }) .w_h(20.0, 20.0) From 9d4a65e8ac0117a83fd8518785619575e09acd6d Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Wed, 9 Jun 2021 16:03:25 -0400 Subject: [PATCH 31/37] Adjust mining xp numbers and add SkillGroupKind information to Outcome::ExpChange. --- .../resource_experience_manifest.ron | 32 +++++++++---------- common/src/outcome.rs | 4 ++- server/src/events/entity_manipulation.rs | 5 +-- server/src/events/interaction.rs | 4 ++- voxygen/src/hud/mod.rs | 9 ++++-- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/assets/server/manifests/resource_experience_manifest.ron b/assets/server/manifests/resource_experience_manifest.ron index c597adef8f..829cea21c8 100644 --- a/assets/server/manifests/resource_experience_manifest.ron +++ b/assets/server/manifests/resource_experience_manifest.ron @@ -1,19 +1,19 @@ ResourceExperienceManifest({ - "common.items.mineral.gem.amethyst": 20, - "common.items.mineral.gem.sapphire": 50, - "common.items.mineral.gem.topaz": 20, - "common.items.mineral.gem.diamond": 100, - "common.items.mineral.gem.emerald": 50, - "common.items.mineral.gem.ruby": 75, + "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": 25, - "common.items.mineral.ore.gold": 100, - "common.items.mineral.ore.iron": 20, - "common.items.mineral.ore.silver": 90, - "common.items.mineral.ore.velorite": 30, - "common.items.mineral.ore.veloritefrag": 20, - "common.items.mineral.ore.bloodstone": 80, - "common.items.mineral.ore.cobalt": 60, - "common.items.mineral.ore.copper": 10, - "common.items.mineral.ore.tin": 10, + "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, }) diff --git a/common/src/outcome.rs b/common/src/outcome.rs index 559139d23a..d1ec35e15c 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -1,5 +1,6 @@ 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 vek::*; @@ -36,6 +37,7 @@ pub enum Outcome { ExpChange { uid: Uid, exp: i32, + xp_pools: HashSet, }, SkillPointGain { uid: Uid, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index c559ba19b6..15dd48bae3 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1011,12 +1011,13 @@ fn handle_exp_gain( add_tool_from_slot(EquipSlot::InactiveMainhand); add_tool_from_slot(EquipSlot::InactiveOffhand); let num_pools = xp_pools.len() as f32; - for pool in xp_pools { - skill_set.change_experience(pool, (exp_reward / num_pools).ceil() as i32); + for pool in xp_pools.iter() { + skill_set.change_experience(*pool, (exp_reward / num_pools).ceil() as i32); } outcomes.push(Outcome::ExpChange { uid: *uid, exp: exp_reward as i32, + xp_pools, }); } diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 9653ef4fc1..03b5fe992a 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -28,9 +28,10 @@ use crate::{ Server, }; -use hashbrown::HashMap; +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) { let ecs = server.state_mut().ecs(); @@ -332,6 +333,7 @@ pub fn handle_mine_block( .push(Outcome::ExpChange { uid, exp: *exp_reward, + xp_pools: HashSet::from_iter(vec![SkillGroupKind::Weapon(tool)]), }); } use common::comp::skills::{MiningSkill, Skill}; diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 605833dd5f..b2944c0f39 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1353,7 +1353,7 @@ impl Hud { Weapon(ToolKind::Sceptre) => &i18n.get("common.weapons.sceptre"), Weapon(ToolKind::Bow) => &i18n.get("common.weapons.bow"), Weapon(ToolKind::Staff) => &i18n.get("common.weapons.staff"), - Weapon(ToolKind::Pick) => &i18n.get("common.tool.mining"), + Weapon(ToolKind::Pick) => &i18n.get("common.tool.pick"), _ => "Unknown", }; Text::new(skill) @@ -1378,7 +1378,6 @@ impl Hud { Weapon(ToolKind::Sceptre) => self.imgs.sceptre, Weapon(ToolKind::Bow) => self.imgs.bow, Weapon(ToolKind::Staff) => self.imgs.staff, - Weapon(ToolKind::Pick) => self.imgs.mining, _ => self.imgs.swords_crossed, }) .w_h(20.0, 20.0) @@ -3659,7 +3658,11 @@ impl Hud { pub fn handle_outcome(&mut self, outcome: &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, exp_change: *exp, timer: 4.0, From 45f79059d0c0623ebc37411491e11b7ab5f2a88f Mon Sep 17 00:00:00 2001 From: Monty Marz Date: Thu, 10 Jun 2021 02:23:27 +0200 Subject: [PATCH 32/37] mining exp sct color and icon; add mining to skill_preset --- assets/server/manifests/presets.ron | 4 ++ assets/voxygen/element/weapons/pickaxe.png | 3 ++ server/src/cmd.rs | 2 +- voxygen/src/hud/img_ids.rs | 1 + voxygen/src/hud/mod.rs | 48 +++++++++++++++------- 5 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 assets/voxygen/element/weapons/pickaxe.png diff --git a/assets/server/manifests/presets.ron b/assets/server/manifests/presets.ron index 615af7a8da..136ef46054 100644 --- a/assets/server/manifests/presets.ron +++ b/assets/server/manifests/presets.ron @@ -127,6 +127,10 @@ (Sceptre(ADuration), 2), (Sceptre(ARange), 2), (Sceptre(ACost), 2), + // Mining + (Pick(Speed), 3), + (Pick(OreGain), 3), + (Pick(GemGain), 3), ], // Just copypasta from max with random reductions "middle": [ diff --git a/assets/voxygen/element/weapons/pickaxe.png b/assets/voxygen/element/weapons/pickaxe.png new file mode 100644 index 0000000000..9346118046 --- /dev/null +++ b/assets/voxygen/element/weapons/pickaxe.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a179c4898dabc76a0f0ebd660fb8c979f2548a9bd367c71b91a9eb06676ae23 +size 466 diff --git a/server/src/cmd.rs b/server/src/cmd.rs index eb6af36349..e2499761af 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -2610,7 +2610,7 @@ fn parse_skill_tree(skill_tree: &str) -> CmdResult "bow" => Ok(SkillGroupKind::Weapon(ToolKind::Bow)), "staff" => Ok(SkillGroupKind::Weapon(ToolKind::Staff)), "sceptre" => Ok(SkillGroupKind::Weapon(ToolKind::Sceptre)), - "pick" => Ok(SkillGroupKind::Weapon(ToolKind::Pick)), + "mining" => Ok(SkillGroupKind::Weapon(ToolKind::Pick)), _ => Err(format!("{} is not a skill group!", skill_tree)), } } diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 2a48f5e028..6b5b5cfec0 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -80,6 +80,7 @@ image_ids! { 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", wpn_icon_border_skills: "voxygen.element.ui.diary.buttons.border_skills", wpn_icon_border: "voxygen.element.ui.generic.buttons.border", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index b2944c0f39..a1501f6a2c 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -98,7 +98,7 @@ use conrod_core::{ widget::{self, Button, Image, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; use rand::Rng; use specs::{Entity as EcsEntity, Join, WorldExt}; use std::{ @@ -211,6 +211,7 @@ widget_ids! { player_rank_up_icon, sct_exp_bgs[], sct_exps[], + sct_exp_icons[], sct_lvl_bg, sct_lvl, hurt_bg, @@ -324,6 +325,7 @@ pub struct ExpFloater { pub exp_change: i32, pub timer: f32, pub rand_offset: (f32, f32), + pub xp_pools: HashSet, } pub struct SkillPointGain { @@ -1252,7 +1254,11 @@ impl Hud { &mut self.ids.player_scts, &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 let font_size_xp = 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 { + let xp_pool = &floater.xp_pools; // Don't show 0 Exp Text::new(&format!("{} Exp", floater.exp_change.max(1))) .font_size(font_size_xp) @@ -1280,12 +1287,25 @@ impl Hud { Text::new(&format!("{} Exp", floater.exp_change.max(1))) .font_size(font_size_xp) .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( 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, ) .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,7 +1373,7 @@ impl Hud { Weapon(ToolKind::Sceptre) => &i18n.get("common.weapons.sceptre"), Weapon(ToolKind::Bow) => &i18n.get("common.weapons.bow"), Weapon(ToolKind::Staff) => &i18n.get("common.weapons.staff"), - Weapon(ToolKind::Pick) => &i18n.get("common.tool.pick"), + Weapon(ToolKind::Pick) => &i18n.get("common.tool.mining"), _ => "Unknown", }; Text::new(skill) @@ -1378,6 +1398,7 @@ impl Hud { Weapon(ToolKind::Sceptre) => self.imgs.sceptre, Weapon(ToolKind::Bow) => self.imgs.bow, Weapon(ToolKind::Staff) => self.imgs.staff, + Weapon(ToolKind::Pick) => self.imgs.mining, _ => self.imgs.swords_crossed, }) .w_h(20.0, 20.0) @@ -3658,16 +3679,15 @@ impl Hud { pub fn handle_outcome(&mut self, outcome: &Outcome) { match outcome { - Outcome::ExpChange { - uid, - exp, - xp_pools: _, - } => self.floaters.exp_floaters.push(ExpFloater { - owner: *uid, - exp_change: *exp, - timer: 4.0, - rand_offset: rand::thread_rng().gen::<(f32, f32)>(), - }), + Outcome::ExpChange { uid, exp, xp_pools } => { + self.floaters.exp_floaters.push(ExpFloater { + owner: *uid, + exp_change: *exp, + timer: 4.0, + rand_offset: rand::thread_rng().gen::<(f32, f32)>(), + xp_pools: xp_pools.clone(), + }) + }, Outcome::SkillPointGain { uid, skill_tree, From 104de523b7d9728f69b5ea376c474c242434447b Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Sun, 13 Jun 2021 14:35:44 -0400 Subject: [PATCH 33/37] Move changelog entry for mining to 0.10 section. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99fa93a4cc..d42eaa89b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Added a skill tree for mining, which gains xp from mining ores and gems. ### Changed @@ -85,7 +86,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Meat drops from animals - New ores, plants and hides to be looted from the world and processed into craft ingredients - Added more crafting stations, loom, spinning wheel, tanning rack, forge -- Added a skill tree for mining, which gains xp from mining ores and gems. ### Changed From 1af9ac568f0abd43028158bd2f98144c88a5e885 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Sun, 13 Jun 2021 20:38:03 -0400 Subject: [PATCH 34/37] Move force-movement e2e check so that it doesn't confer immunity to arrows. --- common/systems/src/phys.rs | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 1f4521496c..613431035c 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -333,21 +333,6 @@ impl<'a> PhysicsData<'a> { }; } - // Don't apply e2e pushback to entities that are in a forced movement state - // (e.g. roll, leapmelee). This allows leaps to work properly (since you won't - // get pushed away before delivering the hit), and allows rolling through an - // enemy when trapped (e.g. with minotaur). This allows using e2e pushback to - // gain speed by jumping out of a roll while in the middle of a collider, this - // is an intentional combat mechanic. - if let Some(cs) = char_state_maybe { - if cs.is_forced_movement() { - return PhysicsMetrics { - entity_entity_collision_checks, - entity_entity_collisions, - }; - } - } - let z_limits = calc_z_limit(char_state_maybe, collider); // Resets touch_entities in physics @@ -440,16 +425,21 @@ impl<'a> PhysicsData<'a> { entity_entity_collisions += 1; } - // Don't apply repulsive force to projectiles or if - // we're - // colliding - // with a terrain-like entity, or if we are a - // terrain-like - // entity + // Don't apply e2e pushback to entities that are in a forced movement state + // (e.g. roll, leapmelee). This allows leaps to work properly (since you won't + // get pushed away before delivering the hit), and allows rolling through an + // enemy when trapped (e.g. with minotaur). This allows using e2e pushback to + // gain speed by jumping out of a roll while in the middle of a collider, this + // is an intentional combat mechanic. + let forced_movement = matches!(char_state_maybe, Some(cs) if cs.is_forced_movement()); + + // Don't apply repulsive force to projectiles or if we're colliding with a + // terrain-like entity, or if we are a terrain-like entity // // Don't apply force when entity is a sticky which is on the // ground (or on the wall) - if !(is_sticky && !is_mid_air) + if !forced_movement && + !(is_sticky && !is_mid_air) && diff.magnitude_squared() > 0.0 && !is_projectile && !matches!( From d5cbe27612a08312d72fcf9e297ecffba07b095c Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Mon, 14 Jun 2021 13:55:25 +0300 Subject: [PATCH 35/37] Responding to review - make `skillset_builder::Preset` void enum and left comment about how to extend it - add `.with_default_equipment()` in case if preset is missing to `basic_summon` loadout creation to match old `build_loadout()` behaviour --- common/src/skillset_builder.rs | 14 +++++--------- common/src/states/basic_summon.rs | 3 ++- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/common/src/skillset_builder.rs b/common/src/skillset_builder.rs index 8fddd2b7b1..4f5726b1d2 100644 --- a/common/src/skillset_builder.rs +++ b/common/src/skillset_builder.rs @@ -6,10 +6,11 @@ use crate::assets::{self, AssetExt}; use serde::{Deserialize, Serialize}; use tracing::warn; +/// `SkillSetBuilder` preset. Consider using loading from assets, when possible. +/// When you're adding new enum variant, +/// handle it in [`with_preset`](SkillSetBuilder::with_preset) method #[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug)] -pub enum Preset { - Empty, -} +pub enum Preset {} #[derive(Debug, Deserialize, Clone)] struct SkillSetTree(Vec); @@ -89,12 +90,7 @@ impl SkillSetBuilder { /// Applies preset #[must_use] - pub const fn with_preset(self, preset: Preset) -> Self { - match preset { - Preset::Empty => {}, - } - self - } + pub const fn with_preset(self, _preset: Preset) -> Self { self } #[must_use] /// # Panics diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs index 1c094b8883..6a49e33c60 100644 --- a/common/src/states/basic_summon.rs +++ b/common/src/states/basic_summon.rs @@ -84,10 +84,11 @@ impl CharacterBehavior for Data { let loadout = { let loadout_builder = LoadoutBuilder::new().with_default_maintool(&body); + // If preset is none, use default equipment if let Some(preset) = loadout_config { loadout_builder.with_preset(preset).build() } else { - loadout_builder.build() + loadout_builder.with_default_equipment(&body).build() } }; From a77b156cd2ecbffce832e99b9eeb14ac23a4192f Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Mon, 31 May 2021 16:34:31 +0200 Subject: [PATCH 36/37] Added dungeon shape to MagicaVoxel export example --- world/examples/dungeon_voxel_export.rs | 209 +++++++++++++++++++++++++ world/src/site/dungeon/mod.rs | 53 +++---- 2 files changed, 234 insertions(+), 28 deletions(-) create mode 100644 world/examples/dungeon_voxel_export.rs diff --git a/world/examples/dungeon_voxel_export.rs b/world/examples/dungeon_voxel_export.rs new file mode 100644 index 0000000000..3f0b58d783 --- /dev/null +++ b/world/examples/dungeon_voxel_export.rs @@ -0,0 +1,209 @@ +use std::{ + collections::HashMap, + fs::File, + io::{prelude::*, SeekFrom}, +}; +type Result = std::io::Result<()>; + +use common::{ + terrain::{Block, BlockKind}, + vol::{BaseVol, ReadVol, RectSizedVol, WriteVol}, +}; +use vek::{Vec2, Vec3}; +use veloren_world::{index::Index, IndexOwned}; + +/// This exports a dungeon (structure only, no entities or sprites) to a +/// MagicaVoxel .vox file + +fn main() -> Result { + let export_path = "dungeon.vox"; + let seed = 0; + + println!("Saving into {}", export_path); + let mut volume = ExportVol::new(); + let index = IndexOwned::new(Index::new(seed)); + let dungeon = veloren_world::site::Dungeon::generate( + volume.size_xy().map(|p| p as i32 / 2), + None, + &mut rand::thread_rng(), + ); + dungeon.apply_to(index.as_index_ref(), Vec2::new(0, 0), |_| None, &mut volume); + volume.write(&mut File::create(export_path)?) +} + +struct ExportVol { + models: HashMap, Vec>, + width: i32, + default_block: Block, +} + +impl ExportVol { + const CHUNK_SIZE: i32 = 256; + + fn new() -> Self { + Self { + models: HashMap::new(), + width: 1000, + default_block: Block::empty(), + } + } + + fn write(&self, file: &mut File) -> Result { + // We need to split the structure into multiple models if it's too big + // However, the create_vox crate doesn't yet work with scene graphs + // Luckily, writing vox files is easy enough + // File format defined at https://github.com/ephtracy/voxel-model + + fn write_i32(file: &mut File, value: i32) -> Result { + // The spec doesn't specify endianess?!? + file.write_all(&value.to_le_bytes()) + } + + fn write_chunk( + file: &mut File, + name: &str, + write_body: &dyn Fn(&mut File) -> Result, + ) -> Result { + file.write_all(name.as_bytes())?; + write_i32(file, 28)?; // Chunk size (unknown at this point) + write_i32(file, 0)?; // Size of child chunks + let chunk_start = file.stream_position()?; + write_body(file)?; + let chunk_end = file.stream_position()?; + file.seek(SeekFrom::Start(chunk_start - 8))?; + write_i32(file, chunk_end as i32 - chunk_start as i32)?; + file.seek(SeekFrom::Start(chunk_end))?; + Ok(()) + } + + fn write_translation_node( + file: &mut File, + id: i32, + child_id: i32, + pos: Vec3, + ) -> Result { + write_chunk(file, "nTRN", &|file| { + write_i32(file, id)?; // Node index + write_i32(file, 0)?; // Number of attributes + write_i32(file, child_id)?; // Child node index + write_i32(file, -1)?; // Reserved + write_i32(file, 0)?; // Layer + write_i32(file, 1)?; // Frames + write_i32(file, 1)?; // Number of frame attributes + write_i32(file, "_t".len() as i32)?; // Attribute name len + file.write_all("_t".as_bytes())?; // Attribute name + let translation_string = format!("{} {} {}", pos.x, pos.y, pos.z); + write_i32(file, translation_string.len() as i32)?; // Value len + file.write_all(translation_string.as_bytes()) // Value + }) + } + + write!(file, "VOX ")?; // Magic number + write_i32(file, 150)?; // Version + + write!(file, "MAIN")?; + write_i32(file, 0)?; // Chunk size + write_i32(file, 0)?; // Size of child chunks (set later) + let chunks_start = file.stream_position()?; + + // Model data + for (_, model) in self.models.iter() { + write_chunk(file, "SIZE", &|file| { + write_i32(file, Self::CHUNK_SIZE)?; // Size X + write_i32(file, Self::CHUNK_SIZE)?; // Size Y + write_i32(file, Self::CHUNK_SIZE) // Size Z + })?; + write_chunk(file, "XYZI", &|file| { + write_i32(file, model.len() as i32 / 4)?; // Number of voxels + file.write_all(&model) + })?; + } + + // Scene graph + // Root Transform node + write_translation_node(file, 0, 1, Vec3::new(0, 0, 0))?; + + // Group node + write_chunk(file, "nGRP", &|file| { + write_i32(file, 1)?; // Node index + write_i32(file, 0)?; // Number of attributes + write_i32(file, self.models.len() as i32)?; // Number of child nodes + for index in 0..self.models.len() { + write_i32(file, index as i32 * 2 + 2)?; + } + Ok(()) + })?; + + for (index, (model_pos, _)) in self.models.iter().enumerate() { + // Transform node + let pos = model_pos + .map(|p| p * Self::CHUNK_SIZE + Self::CHUNK_SIZE / 2 + if p < 0 { 0 } else { 1 }); + let pos = pos - Vec3::new(self.width / 2, self.width / 2, 0); + let transform_node_id = index as i32 * 2 + 2; + let shape_node_id = index as i32 * 2 + 3; + write_translation_node(file, transform_node_id, shape_node_id, pos)?; + + // Shape node + write_chunk(file, "nSHP", &|file| { + write_i32(file, shape_node_id)?; + write_i32(file, 0)?; // Number of attributes + write_i32(file, 1)?; // Number of models + write_i32(file, index as i32)?; // Model index (independent of scene graph index) + write_i32(file, 0) // Number model of attributes + })?; + } + + // Palette + write_chunk(file, "RGBA", &|file| { + file.write_all(&[220, 220, 255, 0])?; // Air + file.write_all(&[100, 100, 100, 0])?; // Rock + file.write_all(&[0; 4 * (256 - 2)]) + })?; + + let chunks_end = file.stream_position()?; + file.seek(SeekFrom::Start(chunks_start - 4))?; + write_i32(file, chunks_end as i32 - chunks_start as i32)?; + + Ok(()) + } +} + +impl BaseVol for ExportVol { + type Error = (); + type Vox = Block; +} + +impl RectSizedVol for ExportVol { + fn lower_bound_xy(&self) -> Vec2 { Vec2::new(0, 0) } + + fn upper_bound_xy(&self) -> Vec2 { Vec2::new(self.width, self.width) } +} + +impl ReadVol for ExportVol { + fn get(&self, _: vek::Vec3) -> std::result::Result<&Self::Vox, Self::Error> { + Ok(&self.default_block) + } +} + +impl WriteVol for ExportVol { + fn set( + &mut self, + pos: vek::Vec3, + vox: Self::Vox, + ) -> std::result::Result { + // Because the dungeon may need to be split into multiple models, we can't + // stream this directly to the file + + let model_pos = pos.map(|p| p.div_euclid(Self::CHUNK_SIZE)); + let rel_pos = pos.map(|p| (p % Self::CHUNK_SIZE) as u8); + self.models + .entry(model_pos) + .or_default() + .extend_from_slice(&[rel_pos.x, rel_pos.y, rel_pos.z, match vox.kind() { + BlockKind::Air => 1, + BlockKind::Rock => 2, + _ => 3, + }]); + Ok(vox) + } +} diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 266fba7ed4..c84f9aec9a 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -129,35 +129,32 @@ impl Dungeon { let rpos = wpos2d - self.origin; // Apply the dungeon entrance - let col_sample = if let Some(col) = get_column(offs) { - col - } else { - continue; - }; - for z in entrance.get_bounds().min.z..entrance.get_bounds().max.z { - let wpos = Vec3::new(offs.x, offs.y, self.alt + z + ALT_OFFSET); - let spos = Vec3::new(rpos.x - TILE_SIZE / 2, rpos.y - TILE_SIZE / 2, z); - if let Some(block) = entrance - .get(spos) - .ok() - .copied() - .map(|sb| { - block_from_structure( - index, - sb, - spos, - self.origin, - self.seed, - col_sample, - // TODO: Take environment into account. - Block::air, - ) - }) - .unwrap_or(None) - { - let _ = vol.set(wpos, block); + if let Some(col_sample) = get_column(offs) { + for z in entrance.get_bounds().min.z..entrance.get_bounds().max.z { + let wpos = Vec3::new(offs.x, offs.y, self.alt + z + ALT_OFFSET); + let spos = Vec3::new(rpos.x - TILE_SIZE / 2, rpos.y - TILE_SIZE / 2, z); + if let Some(block) = entrance + .get(spos) + .ok() + .copied() + .map(|sb| { + block_from_structure( + index, + sb, + spos, + self.origin, + self.seed, + col_sample, + // TODO: Take environment into account. + Block::air, + ) + }) + .unwrap_or(None) + { + let _ = vol.set(wpos, block); + } } - } + }; // Apply the dungeon internals let mut z = self.alt + ALT_OFFSET; From c5f82b241dc341d60efb52f0e68c2adad22f0c38 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Mon, 14 Jun 2021 13:39:50 -0400 Subject: [PATCH 37/37] Mitigate conrod widget id crash by disabling pickaxe icon in xp scroller. --- common/src/cmd.rs | 2 +- voxygen/src/hud/mod.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index d8752cffd5..892da21885 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -127,7 +127,7 @@ lazy_static! { .iter() .map(|s| s.to_string()) .collect(); - static ref SKILL_TREES: Vec = vec!["general", "sword", "axe", "hammer", "bow", "staff", "sceptre", "pick"] + static ref SKILL_TREES: Vec = vec!["general", "sword", "axe", "hammer", "bow", "staff", "sceptre", "mining"] .iter() .map(|s| s.to_string()) .collect(); diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index a1501f6a2c..983277688d 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1254,10 +1254,10 @@ impl Hud { &mut self.ids.player_scts, &mut ui_widgets.widget_id_generator(), ); - let player_sct_icon_id = player_sct_id_walker.next( + /*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 let font_size_xp = @@ -1299,13 +1299,13 @@ impl Hud { ui_widgets.win_h * (0.15 * floater.rand_offset.1 as f64) + y, ) .set(player_sct_id, ui_widgets); - // Exp Source Image - if xp_pool.contains(&SkillGroupKind::Weapon(ToolKind::Pick)) { + // Exp Source Image (TODO: fix widget id crash) + /*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); - } + }*/ } } }