Merge branch 'juliancoffee/asset_entity' into 'master'

EntityInfo assetization.

See merge request veloren/veloren!2382
This commit is contained in:
Marcel 2021-06-14 12:56:28 +00:00
commit b0702d792a
82 changed files with 1627 additions and 1496 deletions

View File

@ -10,7 +10,7 @@ BasicSummon(
)),
scale: None,
health_scaling: 80,
loadout_config: Some(Husk),
loadout_config: Some(HuskSummon),
skillset_config: None,
),
)
)

View File

@ -10,4 +10,4 @@ BasicSummon(
loadout_config: None,
skillset_config: None,
),
)
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-0.gnarling"),
skillset_asset: Some("common.skillset.dungeon.tier-0.bow"),
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-0.gnarling"),
skillset_asset: None,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-0.gnarling"),
skillset_asset: None,
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-1.adlet_bow"),
skillset_asset: Some("common.skillset.dungeon.tier-1.bow"),
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-1.adlet_spear"),
skillset_asset: None,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-1.adlet_spear"),
skillset_asset: None,
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-2.sahagin"),
skillset_asset: Some("common.skillset.dungeon.tier-2.bow"),
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-2.sahagin"),
skillset_asset: None,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-2.sahagin"),
skillset_asset: None,
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-3.haniwa"),
skillset_asset: Some("common.skillset.dungeon.tier-3.bow"),
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-3.haniwa"),
skillset_asset: None,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-3.haniwa"),
skillset_asset: None,
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-4.myrmidon"),
skillset_asset: Some("common.skillset.dungeon.tier-4.bow"),
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-4.myrmidon"),
skillset_asset: None,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-4.myrmidon"),
skillset_asset: None,
)

View File

@ -0,0 +1,17 @@
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"))),
(1.0, Some(Item("common.items.weapons.bow.velorite"))),
])),
second_tool: None,
loadout_asset: Some("common.loadout.dungeon.tier-5.beastmaster"),
// TODO: make own skillset for him?
skillset_asset: Some("common.skillset.dungeon.tier-5.enemy"),
)

View File

@ -0,0 +1,12 @@
EntityConfig (
name: Some("Mindflayer"),
body: Some(RandomWith("mindflayer")),
loot: Some(LootTable("common.loot_tables.dungeon.tier-5.boss")),
main_tool: None,
second_tool: None,
loadout_asset: None,
skillset_asset: None,
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-5.husk"),
skillset_asset: None,
)

View File

@ -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,
)

View File

@ -0,0 +1,12 @@
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,
loadout_asset: Some("common.loadout.dungeon.tier-5.warlock"),
skillset_asset: Some("common.skillset.dungeon.tier-5.enemy"),
)

View File

@ -0,0 +1,18 @@
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"))),
(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_asset: Some("common.loadout.dungeon.tier-5.warlord"),
skillset_asset: Some("common.skillset.dungeon.tier-5.enemy"),
)

View File

@ -0,0 +1,30 @@
EntityConfig (
/// Name of Entity
name: Some("Paddy"),
/// Body
/// Can be Exact (Body with all fields e.g BodyType, Species, Hair color and such)
/// or RandomWith (will generate random body or species)
body: Some(RandomWith("humanoid")),
/// Main and second tools
/// Can be Option<Item> (with asset_specifier for item)
/// or Choice
/// (array of pairs with weight of choosing some item and Option<Item>)
main_tool: Some(Item("common.items.weapons.axe_1h.orichalcum-0")),
second_tool: None,
/// Loadout Config (with asset_specifier for loadout)
loadout_asset: Some("common.loadout.village.merchant"),
/// Skillset Config (with asset_specifier for skillset)
skillset_asset: Some("common.skillset.village.merchant"),
/// Loot
/// Can be Item (with asset_specifier for item)
/// or LootTable (with asset_specifier for loot table)
loot: Some(LootTable("common.loot_tables.humanoids")),
/// Meta Info (level, alignment, agency, etc)
// meta: {},
)

View File

@ -0,0 +1,13 @@
EntityConfig (
name: Some("Guard"),
// body is specified outsite
body: None,
loot: None,
main_tool: Some(Item("common.items.weapons.sword.iron-4")),
second_tool: None,
loadout_asset: None,
skillset_asset: Some("common.skillset.village.guard"),
)

View File

@ -0,0 +1,14 @@
EntityConfig (
name: Some("Merchant"),
// 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,
loadout_asset: None,
skillset_asset: Some("common.skillset.village.merchant"),
)

View File

@ -0,0 +1,20 @@
EntityConfig (
name: None,
// 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"))),
(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,
)

View File

@ -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.hide.leather.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"))),
]),
})

View File

@ -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))),
])

View File

@ -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))),
])

View File

@ -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))),
])

View File

@ -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))),
])

View File

@ -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))),
])

View File

@ -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))),
])

View File

@ -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))),
])

View File

@ -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"),
])

View File

@ -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))),
])

View File

@ -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))),
])

View File

@ -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))),
])

View File

@ -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))),
])

View File

@ -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))),
])

View File

@ -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 {

View File

@ -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 {

View File

@ -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,24 +40,21 @@ 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,
Husk,
Beastmaster,
Warlord,
Warlock,
Villager,
Guard,
Merchant,
#[derive(Copy, Clone, PartialEq, Deserialize, Serialize, Debug, EnumIter)]
pub enum Preset {
HuskSummon,
}
#[derive(Debug, Deserialize, Clone)]
enum ItemSpec {
pub struct LoadoutSpec(HashMap<EquipSlot, ItemSpec>);
impl assets::Asset for LoadoutSpec {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
#[derive(Debug, Deserialize, Clone)]
pub enum ItemSpec {
/// One specific item.
/// Example:
/// Item("common.items.armor.steel.foot")
@ -73,24 +69,30 @@ enum ItemSpec {
}
impl ItemSpec {
fn try_to_item(&self, asset_specifier: &str) -> Option<Item> {
pub fn try_to_item(&self, asset_specifier: &str, rng: &mut impl Rng) -> Option<Item> {
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)
},
})
},
}
}
#[cfg(test)]
// Read everything and checks if it's loading
fn validate(&self, key: EquipSlot) {
/// # 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)),
ItemSpec::Choice(items) => {
@ -108,43 +110,27 @@ impl ItemSpec {
}
}
fn choose<'a>(items: &'a [(f32, Option<ItemSpec>)], asset_specifier: &str) -> &'a Option<ItemSpec> {
let mut rng = rand::thread_rng();
items.choose_weighted(&mut rng, |item| item.0).map_or_else(
fn choose<'a>(
items: &'a [(f32, Option<ItemSpec>)],
asset_specifier: &str,
rng: &mut impl Rng,
) -> &'a Option<ItemSpec> {
items.choose_weighted(rng, |item| item.0).map_or_else(
|err| match err {
WeightedError::NoItem | WeightedError::AllWeightsZero => &None,
WeightedError::InvalidWeight => {
let err = format!("Negative values of probability in {}.", asset_specifier);
if cfg!(tests) {
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!(tests) {
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<EquipSlot, ItemSpec>);
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");
@ -163,8 +149,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)]
pub fn default_main_tool(body: &Body) -> Option<Item> {
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",
@ -336,7 +322,9 @@ pub fn default_main_tool(body: &Body) -> Option<Item> {
)),
},
_ => None,
}
};
maybe_tool.unwrap_or_else(Item::empty)
}
impl Default for LoadoutBuilder {
@ -348,56 +336,106 @@ 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 {
/// 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 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(Some(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,
..
}) => builder.chest(Some(Item::new_from_asset_expect(
}) => 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,
..
}) => builder.chest(Some(Item::new_from_asset_expect(
}) => 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,
..
}) => builder.chest(Some(Item::new_from_asset_expect(
}) => 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,
..
}) => builder.chest(Some(Item::new_from_asset_expect(
}) => 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,
..
}) => builder.chest(Some(Item::new_from_asset_expect(
}) => self.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(
}) => self.chest(Some(Item::new_from_asset_expect(
"common.items.npc_armor.golem.claygolem",
))),
_ => builder,
_ => self,
};
builder.active_mainhand(active_item)
self
}
#[must_use]
pub fn from_asset_expect(asset_specifier: &str) -> Self {
let loadout = Self::new();
pub fn with_preset(mut self, preset: Preset) -> Self {
let rng = &mut rand::thread_rng();
match preset {
Preset::HuskSummon => {
self = self.with_asset_expect("common.loadout.dungeon.tier-5.husk", rng)
},
}
loadout.apply_asset_expect(asset_specifier)
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
@ -409,10 +447,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 apply_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,219 +523,9 @@ 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") }
/// 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 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.apply_asset_expect("common.loadouts.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")
},
Some(ToolKind::Spear | ToolKind::Staff) => {
builder.apply_asset_expect("common.loadouts.dungeon.tier-1.adlet_spear")
},
_ => builder,
},
LoadoutConfig::Sahagin => {
builder.apply_asset_expect("common.loadouts.dungeon.tier-2.sahagin")
},
LoadoutConfig::Haniwa => {
builder.apply_asset_expect("common.loadouts.dungeon.tier-3.haniwa")
},
LoadoutConfig::Myrmidon => {
builder.apply_asset_expect("common.loadouts.dungeon.tier-4.myrmidon")
},
LoadoutConfig::Husk => {
builder.apply_asset_expect("common.loadouts.dungeon.tier-5.husk")
},
LoadoutConfig::Beastmaster => {
builder.apply_asset_expect("common.loadouts.dungeon.tier-5.beastmaster")
},
LoadoutConfig::Warlord => {
builder.apply_asset_expect("common.loadouts.dungeon.tier-5.warlord")
},
LoadoutConfig::Warlock => {
builder.apply_asset_expect("common.loadouts.dungeon.tier-5.warlock")
},
LoadoutConfig::Villager => builder
.apply_asset_expect("common.loadouts.village.villager")
.bag(ArmorSlot::Bag1, Some(make_potion_bag(10))),
LoadoutConfig::Guard => builder
.apply_asset_expect("common.loadouts.village.guard")
.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 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);
// 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
.apply_asset_expect("common.loadouts.village.merchant")
.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())
pub fn defaults(self) -> Self {
let rng = &mut rand::thread_rng();
self.with_asset_expect("common.loadout.default", rng)
}
#[must_use]
@ -828,40 +656,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 Preset::iter() {
std::mem::drop(LoadoutBuilder::default().with_preset(preset));
}
}
@ -885,17 +686,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),
));
}
};
@ -947,7 +742,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 {

View File

@ -1,13 +1,44 @@
use crate::{
comp::{self, humanoid, inventory::loadout_builder::LoadoutConfig, Alignment, Body, Item},
assets::{self, AssetExt},
comp::{
self, agent, humanoid,
inventory::loadout_builder::{ItemSpec, LoadoutBuilder},
Alignment, Body, Item,
},
lottery::{LootSpec, Lottery},
npc::{self, NPC_NAMES},
skillset_builder::SkillSetConfig,
trade,
trade::SiteInformation,
};
use serde::Deserialize;
use vek::*;
pub enum EntityTemplate {
Traveller,
#[derive(Debug, Deserialize, Clone)]
enum BodyKind {
RandomWith(String),
}
#[derive(Debug, Deserialize, Clone)]
enum LootKind {
Item(String),
LootTable(String),
}
#[derive(Debug, Deserialize, Clone)]
struct EntityConfig {
name: Option<String>,
body: Option<BodyKind>,
loot: Option<LootKind>,
main_tool: Option<ItemSpec>,
second_tool: Option<ItemSpec>,
loadout_asset: Option<String>,
skillset_asset: Option<String>,
}
impl assets::Asset for EntityConfig {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
#[derive(Clone)]
@ -17,6 +48,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,11 +57,12 @@ pub struct EntityInfo {
// TODO: Properly give NPCs skills
pub level: Option<u16>,
pub loot_drop: Option<Item>,
pub loadout_config: Option<LoadoutConfig>,
pub skillset_config: Option<SkillSetConfig>,
pub loadout_asset: Option<String>,
pub make_loadout: Option<fn(LoadoutBuilder, Option<&trade::SiteInformation>) -> LoadoutBuilder>,
pub skillset_asset: Option<String>,
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 */
}
@ -41,6 +74,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,
@ -48,13 +82,85 @@ impl EntityInfo {
scale: 1.0,
level: None,
loot_drop: None,
loadout_config: None,
skillset_config: None,
loadout_asset: None,
make_loadout: None,
skillset_asset: None,
pet: None,
trading_information: None,
}
}
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,
body,
loot,
main_tool,
second_tool,
loadout_asset,
skillset_asset,
} = config;
if let Some(name) = name {
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::<npc::NpcBody>().unwrap_or_else(|err| {
panic!("failed to parse body {:?}. Err: {:?}", &string, err)
});
let body = body_creator();
self = self.with_body(body);
},
}
}
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::<LootSpec>::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))
{
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_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
}
pub fn do_if(mut self, cond: bool, f: impl FnOnce(Self) -> Self) -> Self {
if cond {
self = f(self);
@ -92,6 +198,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
@ -117,13 +228,21 @@ impl EntityInfo {
self
}
pub fn with_loadout_config(mut self, config: LoadoutConfig) -> Self {
self.loadout_config = Some(config);
pub fn with_loadout_asset(mut self, asset: String) -> Self {
self.loadout_asset = Some(asset);
self
}
pub fn with_skillset_config(mut self, config: SkillSetConfig) -> Self {
self.skillset_config = Some(config);
pub fn with_lazy_loadout(
mut self,
creator: fn(LoadoutBuilder, Option<&trade::SiteInformation>) -> LoadoutBuilder,
) -> Self {
self.make_loadout = Some(creator);
self
}
pub fn with_skillset_asset(mut self, asset: String) -> Self {
self.skillset_asset = Some(asset);
self
}
@ -184,3 +303,90 @@ pub fn get_npc_name<
) -> &'a str {
&body_data.species[&species].generic
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{comp::inventory::slot::EquipSlot, SkillSetBuilder};
use assets::Error;
#[test]
fn test_all_entity_assets() {
#[derive(Clone)]
struct EntityList(Vec<EntityConfig>);
impl assets::Compound for EntityList {
fn load<S: assets::source::Source>(
cache: &assets::AssetCache<S>,
specifier: &str,
) -> Result<Self, Error> {
let list = cache
.load::<assets::Directory>(specifier)?
.read()
.iter()
.map(|spec| EntityConfig::load_cloned(spec))
.collect::<Result<_, Error>>()?;
Ok(Self(list))
}
}
// It just load everything that could
let entity_configs = EntityList::load_expect_cloned("common.entity.*").0;
for config in entity_configs {
let EntityConfig {
main_tool,
second_tool,
loadout_asset,
skillset_asset,
name: _name,
body,
loot,
} = 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(body) = body {
match body {
BodyKind::RandomWith(string) => {
let npc::NpcBody(_body_kind, mut body_creator) =
string.parse::<npc::NpcBody>().unwrap_or_else(|err| {
panic!("failed to parse body {:?}. Err: {:?}", &string, err)
});
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::<LootSpec>::load_expect(&asset);
},
}
}
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));
}
}
}
}

View File

@ -1,30 +1,57 @@
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;
/// `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 SkillSetConfig {
Adlet,
Gnarling,
Sahagin,
Haniwa,
Myrmidon,
Guard,
Villager,
Merchant,
Outcast,
Highwayman,
Bandit,
CultistNovice,
CultistAcolyte,
Warlord,
Warlock,
Mindflayer,
pub enum Preset {}
#[derive(Debug, Deserialize, Clone)]
struct SkillSetTree(Vec<SkillNode>);
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<u16>)),
Group(SkillGroupKind),
}
#[must_use]
fn skills_from_asset_expect(asset_specifier: &str) -> Vec<(Skill, Option<u16>)> {
let nodes = SkillSetTree::load_expect(asset_specifier).read().0.clone();
skills_from_nodes(nodes)
}
#[must_use]
fn skills_from_nodes(nodes: Vec<SkillNode>) -> Vec<(Skill, Option<u16>)> {
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,680 +61,128 @@ impl Default for SkillSetBuilder {
}
impl SkillSetBuilder {
pub fn build_skillset(main_tool: &Option<Item>, config: Option<SkillSetConfig>) -> 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 { self }
#[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<u16>) -> 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
);
}
}
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
);
common_base::dev_panic!(err, or 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,
);
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));
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
);
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<u16>) -> bool {
if let Ok(applied_level) = skill_set.skill_level(skill) {
applied_level == level
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use assets::Error;
#[test]
fn test_all_skillset_assets() {
#[derive(Clone)]
struct SkillSetList(Vec<SkillSetTree>);
impl assets::Compound for SkillSetList {
fn load<S: assets::source::Source>(
cache: &assets::AssetCache<S>,
specifier: &str,
) -> Result<Self, Error> {
let list = cache
.load::<assets::Directory>(specifier)?
.read()
.iter()
.map(|spec| SkillSetTree::load_cloned(spec))
.collect::<Result<_, Error>>()?;
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
});
}
}
}

View File

@ -1,12 +1,12 @@
use crate::{
comp::{
self,
inventory::loadout_builder::{LoadoutBuilder, LoadoutConfig},
inventory::loadout_builder::{self, LoadoutBuilder},
Behavior, BehaviorCapability, CharacterState, StateUpdate,
},
event::{LocalEvent, ServerEvent},
outcome::Outcome,
skillset_builder::{SkillSetBuilder, SkillSetConfig},
skillset_builder::{self, SkillSetBuilder},
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
@ -74,22 +74,34 @@ 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 loadout = LoadoutBuilder::build_loadout(
let SummonInfo {
body,
None,
self.static_data.summon_info.loadout_config,
None,
)
.build();
let stats = comp::Stats::new("Summon".to_string());
let skill_set = SkillSetBuilder::build_skillset(
&None,
self.static_data.summon_info.skillset_config,
)
.build();
loadout_config,
skillset_config,
..
} = self.static_data.summon_info;
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.with_default_equipment(&body).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());
// Send server event to create npc
update.server_events.push_front(ServerEvent::CreateNpc {
pos: *data.pos,
@ -175,6 +187,7 @@ pub struct SummonInfo {
body: comp::Body,
scale: Option<comp::Scale>,
health_scaling: u16,
loadout_config: Option<LoadoutConfig>,
skillset_config: Option<SkillSetConfig>,
// TODO: use assets for specifying skills and loadout?
loadout_config: Option<loadout_builder::Preset>,
skillset_config: Option<skillset_builder::Preset>,
}

View File

@ -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

View File

@ -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", Some(&mut rng))
.bag(
comp::inventory::slot::ArmorSlot::Bag1,
Some(comp::inventory::loadout_builder::make_potion_bag(100)),

View File

@ -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,50 @@ 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_asset,
main_tool,
loadout_asset,
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();
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 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 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);
} 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());
}
loadout_builder.build()
};
let health = comp::Health::new(body, entity.level.unwrap_or(0));
let poise = comp::Poise::new(body);
@ -219,7 +251,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 +282,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

View File

@ -9,14 +9,10 @@ use crate::{
};
use common::{
assets::{AssetExt, AssetHandle},
assets::AssetHandle,
astar::Astar,
comp::{
inventory::loadout_builder,
{self},
},
comp::{self},
generation::{ChunkSupplement, EntityInfo},
lottery::{LootSpec, Lottery},
store::{Id, Store},
terrain::{Block, BlockKind, SpriteKind, Structure, StructuresGroup, TerrainChunkSize},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol},
@ -570,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(
@ -600,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 {
@ -643,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 {
@ -926,350 +922,150 @@ impl Floor {
}
fn enemy_0(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-0.enemy");
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(loadout_builder::LoadoutConfig::Gnarling)
.with_skillset_config(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",
},
))
match dynamic_rng.gen_range(0..5) {
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::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-1.enemy");
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",
},
))
match dynamic_rng.gen_range(0..5) {
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::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-2.enemy");
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(loadout_builder::LoadoutConfig::Sahagin)
.with_skillset_config(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",
},
))
match dynamic_rng.gen_range(0..5) {
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::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-3.enemy");
match dynamic_rng.gen_range(0..4) {
fn enemy_3(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo {
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",
)),
_ => 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(loadout_builder::LoadoutConfig::Haniwa)
.with_skillset_config(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",
},
)),
.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::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-4.enemy");
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(loadout_builder::LoadoutConfig::Myrmidon)
.with_skillset_config(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",
},
))
}
fn enemy_5(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo {
let chosen = Lottery::<LootSpec>::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_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_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_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",
},
)),
match dynamic_rng.gen_range(0..5) {
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_fallback(_dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo {
let chosen = Lottery::<LootSpec>::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_5(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo {
match dynamic_rng.gen_range(0..6) {
0 => entity
.with_body(comp::Body::Object(comp::object::Body::Crossbow))
.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 boss_0(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-0.boss");
fn enemy_fallback(entity: EntityInfo) -> EntityInfo {
entity.with_asset_expect("common.entity.dungeon.fallback.enemy")
}
fn boss_0(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-1.boss");
fn boss_1(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-2.boss");
fn boss_2(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-3.boss");
fn boss_3(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-4.boss");
fn boss_4(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-5.boss");
fn boss_5(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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_name("Mindflayer".to_string())
.with_loot_drop(chosen.read().choose().to_item())
.with_skillset_config(common::skillset_builder::SkillSetConfig::Mindflayer),
.with_asset_expect("common.entity.dungeon.tier-5.boss"),
]
}
fn boss_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-0.miniboss");
fn boss_fallback(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.creature.quad_small.generic");
fn mini_boss_0(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.creature.quad_low.fanged");
fn mini_boss_2(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
let chosen =
Lottery::<LootSpec>::load_expect("common.loot_tables.creature.quad_medium.carapace");
fn mini_boss_3(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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<i32>) -> Vec<EntityInfo> {
let chosen = Lottery::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-4.miniboss");
fn mini_boss_4(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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"),
]
}
@ -1277,62 +1073,29 @@ fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<Entit
let mut entities = Vec::new();
match dynamic_rng.gen_range(0..2) {
0 => {
let trainer_loot =
Lottery::<LootSpec>::load_expect("common.loot_tables.dungeon.tier-5.miniboss");
let hound_loot =
Lottery::<LootSpec>::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_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_main_tool(comp::Item::new_from_asset_expect(
match dynamic_rng.gen_range(0..3) {
0 => "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))
.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::<LootSpec>::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_name("Cultist Husk".to_string())
.with_loot_drop(chosen.read().choose().to_item())
.with_loadout_config(loadout_builder::LoadoutConfig::Husk)
.with_asset_expect("common.entity.dungeon.tier-5.husk")
});
},
}
entities
}
fn mini_boss_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
fn mini_boss_fallback(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
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"),
]
}
@ -1342,19 +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);
}
//FIXME: it will miss items with rng branching
#[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));
@ -1364,20 +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);
}
//FIXME: it will miss items with rng branching
#[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);
}
}

View File

@ -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;
@ -946,43 +952,19 @@ impl Settlement {
.do_if(is_human && dynamic_rng.gen(), |entity| {
match dynamic_rng.gen_range(0..6) {
0 => 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_loadout_config(loadout_builder::LoadoutConfig::Guard)
.with_skillset_config(
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_loadout_config(loadout_builder::LoadoutConfig::Merchant)
.with_skillset_config(
common::skillset_builder::SkillSetConfig::Merchant,
)
.with_economy(&economy),
.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_loadout_config(loadout_builder::LoadoutConfig::Villager)
.with_skillset_config(
common::skillset_builder::SkillSetConfig::Villager,
),
.with_lazy_loadout(villager_loadout)
.with_asset_expect("common.entity.village.villager"),
}
});
@ -1043,6 +1025,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,