mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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.
This commit is contained in:
parent
aad65c6159
commit
5f3eaddb70
@ -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<SkillSet> (with asset_specifier for skillset)
|
||||
skillset_config: None,
|
||||
// skillset_config: None,
|
||||
|
||||
/// Meta Info (level, alignment, agency, etc)
|
||||
meta: {},
|
||||
// meta: {},
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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<Item> {
|
||||
fn default_main_tool(body: &Body) -> Option<Item> {
|
||||
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<Item>) -> 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<Item>,
|
||||
config: Option<LoadoutConfig>,
|
||||
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<Item>) -> 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),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
@ -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<agent::Mark>,
|
||||
pub body: Body,
|
||||
pub name: Option<String>,
|
||||
pub main_tool: Option<Item>,
|
||||
@ -25,13 +31,16 @@ pub struct EntityInfo {
|
||||
// TODO: Properly give NPCs skills
|
||||
pub level: Option<u16>,
|
||||
pub loot_drop: Option<Item>,
|
||||
// FIXME: using both preset and asset is silly, make it enum
|
||||
// so it will be correct by construction
|
||||
pub loadout_config: Option<String>,
|
||||
pub loadout_preset: Option<LoadoutConfig>,
|
||||
pub loadout_preset: Option<LoadoutPreset>,
|
||||
pub make_loadout: Option<fn(LoadoutBuilder, Option<&trade::SiteInformation>) -> LoadoutBuilder>,
|
||||
pub skillset_config: Option<String>,
|
||||
pub skillset_preset: Option<SkillSetConfig>,
|
||||
pub pet: Option<Box<EntityInfo>>,
|
||||
// we can't use DHashMap, do we want to move that into common?
|
||||
pub trading_information: Option<crate::trade::SiteInformation>,
|
||||
pub trading_information: Option<trade::SiteInformation>,
|
||||
//Option<hashbrown::HashMap<crate::trade::Good, (f32, f32)>>, /* 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 {
|
||||
|
@ -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<comp::Scale>,
|
||||
health_scaling: u16,
|
||||
// TODO: use assets for specifying skills and loadouts?
|
||||
loadout_preset: Option<LoadoutConfig>,
|
||||
loadout_preset: Option<LoadoutPreset>,
|
||||
skillset_preset: Option<SkillSetConfig>,
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)),
|
||||
|
@ -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::<f32>();
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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::<LootSpec>::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(
|
||||
.with_skillset_preset(common::skillset_builder::SkillSetConfig::Adlet)
|
||||
.with_loot_drop(chosen.read().choose().to_item());
|
||||
|
||||
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",
|
||||
},
|
||||
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<i32>) -> Vec<EntityInfo
|
||||
))
|
||||
.with_name("Mindflayer".to_string())
|
||||
.with_loot_drop(chosen.read().choose().to_item())
|
||||
.with_skillset_config(common::skillset_builder::SkillSetConfig::Mindflayer),
|
||||
.with_skillset_preset(common::skillset_builder::SkillSetConfig::Mindflayer),
|
||||
]
|
||||
}
|
||||
|
||||
@ -1286,8 +1293,8 @@ fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<Entit
|
||||
.with_body(comp::Body::Humanoid(comp::humanoid::Body::random()))
|
||||
.with_name("Beastmaster".to_string())
|
||||
.with_loot_drop(trainer_loot.read().choose().to_item())
|
||||
.with_loadout_config(loadout_builder::LoadoutConfig::Beastmaster)
|
||||
.with_skillset_config(common::skillset_builder::SkillSetConfig::CultistAcolyte)
|
||||
.with_loadout_config("common.loadout.dungeon.tier-5.beastmaster")
|
||||
.with_skillset_preset(common::skillset_builder::SkillSetConfig::CultistAcolyte)
|
||||
.with_main_tool(comp::Item::new_from_asset_expect(
|
||||
match dynamic_rng.gen_range(0..3) {
|
||||
0 => "common.items.weapons.axe.malachite_axe-0",
|
||||
@ -1321,7 +1328,7 @@ fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<Entit
|
||||
))
|
||||
.with_name("Cultist Husk".to_string())
|
||||
.with_loot_drop(chosen.read().choose().to_item())
|
||||
.with_loadout_config(loadout_builder::LoadoutConfig::Husk)
|
||||
.with_loadout_config("common.loadout.dungeon.tier-5.husk")
|
||||
});
|
||||
},
|
||||
}
|
||||
|
@ -16,14 +16,20 @@ use crate::{
|
||||
use common::{
|
||||
astar::Astar,
|
||||
comp::{
|
||||
self, bird_medium, humanoid, inventory::loadout_builder, object, quadruped_small, Item,
|
||||
self, agent, bird_medium, humanoid,
|
||||
inventory::{
|
||||
loadout_builder::{make_potion_bag, LoadoutBuilder},
|
||||
slot::ArmorSlot,
|
||||
trade_pricing::TradePricing,
|
||||
},
|
||||
object, quadruped_small, Item,
|
||||
},
|
||||
generation::{ChunkSupplement, EntityInfo},
|
||||
path::Path,
|
||||
spiral::Spiral2d,
|
||||
store::{Id, Store},
|
||||
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
|
||||
trade::SiteInformation,
|
||||
trade::{self, Good, SiteInformation},
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol},
|
||||
};
|
||||
use fxhash::FxHasher64;
|
||||
@ -950,9 +956,10 @@ impl Settlement {
|
||||
"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_loadout_config(loadout_builder::LoadoutConfig::Guard)
|
||||
.with_skillset_config(
|
||||
.with_skillset_preset(
|
||||
common::skillset_builder::SkillSetConfig::Guard,
|
||||
),
|
||||
1 | 2 => entity
|
||||
@ -960,12 +967,13 @@ 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) {
|
||||
@ -979,8 +987,8 @@ impl Settlement {
|
||||
//_ => "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,
|
||||
|
Loading…
Reference in New Issue
Block a user