mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'juliancoffee/asset_entity' into 'master'
EntityInfo assetization. See merge request veloren/veloren!2382
This commit is contained in:
commit
b0702d792a
@ -10,7 +10,7 @@ BasicSummon(
|
||||
)),
|
||||
scale: None,
|
||||
health_scaling: 80,
|
||||
loadout_config: Some(Husk),
|
||||
loadout_config: Some(HuskSummon),
|
||||
skillset_config: None,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
@ -10,4 +10,4 @@ BasicSummon(
|
||||
loadout_config: None,
|
||||
skillset_config: None,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
12
assets/common/entity/dungeon/fallback/boss.ron
Normal file
12
assets/common/entity/dungeon/fallback/boss.ron
Normal 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,
|
||||
)
|
20
assets/common/entity/dungeon/fallback/enemy.ron
Normal file
20
assets/common/entity/dungeon/fallback/enemy.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/fallback/miniboss.ron
Normal file
12
assets/common/entity/dungeon/fallback/miniboss.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-0/boss.ron
Normal file
12
assets/common/entity/dungeon/tier-0/boss.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-0/bow.ron
Normal file
12
assets/common/entity/dungeon/tier-0/bow.ron
Normal 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"),
|
||||
)
|
12
assets/common/entity/dungeon/tier-0/miniboss.ron
Normal file
12
assets/common/entity/dungeon/tier-0/miniboss.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-0/spear.ron
Normal file
12
assets/common/entity/dungeon/tier-0/spear.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-0/staff.ron
Normal file
12
assets/common/entity/dungeon/tier-0/staff.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-1/boss.ron
Normal file
12
assets/common/entity/dungeon/tier-1/boss.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-1/bow.ron
Normal file
12
assets/common/entity/dungeon/tier-1/bow.ron
Normal 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"),
|
||||
)
|
12
assets/common/entity/dungeon/tier-1/rat.ron
Normal file
12
assets/common/entity/dungeon/tier-1/rat.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-1/spear.ron
Normal file
12
assets/common/entity/dungeon/tier-1/spear.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-1/staff.ron
Normal file
12
assets/common/entity/dungeon/tier-1/staff.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-2/boss.ron
Normal file
12
assets/common/entity/dungeon/tier-2/boss.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-2/bow.ron
Normal file
12
assets/common/entity/dungeon/tier-2/bow.ron
Normal 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"),
|
||||
)
|
12
assets/common/entity/dungeon/tier-2/hakulaq.ron
Normal file
12
assets/common/entity/dungeon/tier-2/hakulaq.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-2/spear.ron
Normal file
12
assets/common/entity/dungeon/tier-2/spear.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-2/staff.ron
Normal file
12
assets/common/entity/dungeon/tier-2/staff.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-3/bonerattler.ron
Normal file
12
assets/common/entity/dungeon/tier-3/bonerattler.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-3/boss.ron
Normal file
12
assets/common/entity/dungeon/tier-3/boss.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-3/bow.ron
Normal file
12
assets/common/entity/dungeon/tier-3/bow.ron
Normal 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"),
|
||||
)
|
12
assets/common/entity/dungeon/tier-3/sentry.ron
Normal file
12
assets/common/entity/dungeon/tier-3/sentry.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-3/spear.ron
Normal file
12
assets/common/entity/dungeon/tier-3/spear.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-3/staff.ron
Normal file
12
assets/common/entity/dungeon/tier-3/staff.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-4/boss.ron
Normal file
12
assets/common/entity/dungeon/tier-4/boss.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-4/bow.ron
Normal file
12
assets/common/entity/dungeon/tier-4/bow.ron
Normal 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"),
|
||||
)
|
12
assets/common/entity/dungeon/tier-4/miniboss.ron
Normal file
12
assets/common/entity/dungeon/tier-4/miniboss.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-4/spear.ron
Normal file
12
assets/common/entity/dungeon/tier-4/spear.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-4/staff.ron
Normal file
12
assets/common/entity/dungeon/tier-4/staff.ron
Normal 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,
|
||||
)
|
17
assets/common/entity/dungeon/tier-5/beastmaster.ron
Normal file
17
assets/common/entity/dungeon/tier-5/beastmaster.ron
Normal 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"),
|
||||
)
|
12
assets/common/entity/dungeon/tier-5/boss.ron
Normal file
12
assets/common/entity/dungeon/tier-5/boss.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-5/hound.ron
Normal file
12
assets/common/entity/dungeon/tier-5/hound.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-5/husk.ron
Normal file
12
assets/common/entity/dungeon/tier-5/husk.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-5/turret.ron
Normal file
12
assets/common/entity/dungeon/tier-5/turret.ron
Normal 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,
|
||||
)
|
12
assets/common/entity/dungeon/tier-5/warlock.ron
Normal file
12
assets/common/entity/dungeon/tier-5/warlock.ron
Normal 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"),
|
||||
)
|
18
assets/common/entity/dungeon/tier-5/warlord.ron
Normal file
18
assets/common/entity/dungeon/tier-5/warlord.ron
Normal 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"),
|
||||
)
|
30
assets/common/entity/test.ron
Normal file
30
assets/common/entity/test.ron
Normal 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: {},
|
||||
)
|
13
assets/common/entity/village/guard.ron
Normal file
13
assets/common/entity/village/guard.ron
Normal 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"),
|
||||
)
|
14
assets/common/entity/village/merchant.ron
Normal file
14
assets/common/entity/village/merchant.ron
Normal 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"),
|
||||
)
|
20
assets/common/entity/village/villager.ron
Normal file
20
assets/common/entity/village/villager.ron
Normal 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,
|
||||
)
|
28
assets/common/loadout/world/traveler.ron
Normal file
28
assets/common/loadout/world/traveler.ron
Normal 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"))),
|
||||
]),
|
||||
})
|
12
assets/common/skillset/dungeon/tier-0/bow.ron
Normal file
12
assets/common/skillset/dungeon/tier-0/bow.ron
Normal 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))),
|
||||
])
|
12
assets/common/skillset/dungeon/tier-1/bow.ron
Normal file
12
assets/common/skillset/dungeon/tier-1/bow.ron
Normal 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))),
|
||||
])
|
12
assets/common/skillset/dungeon/tier-2/bow.ron
Normal file
12
assets/common/skillset/dungeon/tier-2/bow.ron
Normal 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))),
|
||||
])
|
12
assets/common/skillset/dungeon/tier-3/bow.ron
Normal file
12
assets/common/skillset/dungeon/tier-3/bow.ron
Normal 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))),
|
||||
])
|
12
assets/common/skillset/dungeon/tier-4/bow.ron
Normal file
12
assets/common/skillset/dungeon/tier-4/bow.ron
Normal 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))),
|
||||
])
|
21
assets/common/skillset/dungeon/tier-5/axe.ron
Normal file
21
assets/common/skillset/dungeon/tier-5/axe.ron
Normal 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))),
|
||||
])
|
21
assets/common/skillset/dungeon/tier-5/bow.ron
Normal file
21
assets/common/skillset/dungeon/tier-5/bow.ron
Normal 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))),
|
||||
])
|
8
assets/common/skillset/dungeon/tier-5/enemy.ron
Normal file
8
assets/common/skillset/dungeon/tier-5/enemy.ron
Normal 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"),
|
||||
])
|
21
assets/common/skillset/dungeon/tier-5/hammer.ron
Normal file
21
assets/common/skillset/dungeon/tier-5/hammer.ron
Normal 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))),
|
||||
])
|
21
assets/common/skillset/dungeon/tier-5/staff.ron
Normal file
21
assets/common/skillset/dungeon/tier-5/staff.ron
Normal 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))),
|
||||
])
|
19
assets/common/skillset/dungeon/tier-5/sword.ron
Normal file
19
assets/common/skillset/dungeon/tier-5/sword.ron
Normal 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))),
|
||||
])
|
24
assets/common/skillset/village/guard.ron
Normal file
24
assets/common/skillset/village/guard.ron
Normal 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))),
|
||||
])
|
17
assets/common/skillset/village/merchant.ron
Normal file
17
assets/common/skillset/village/merchant.ron
Normal 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))),
|
||||
])
|
@ -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 {
|
||||
|
@ -32,6 +32,12 @@ pub enum Alignment {
|
||||
Passive,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum Mark {
|
||||
Merchant,
|
||||
Guard,
|
||||
}
|
||||
|
||||
impl Alignment {
|
||||
// Always attacks
|
||||
pub fn hostile_towards(self, other: Alignment) -> bool {
|
||||
|
@ -7,12 +7,11 @@ use crate::{
|
||||
inventory::{
|
||||
loadout::Loadout,
|
||||
slot::{ArmorSlot, EquipSlot},
|
||||
trade_pricing::TradePricing,
|
||||
},
|
||||
item::{tool::ToolKind, Item, ItemKind},
|
||||
item::Item,
|
||||
object, quadruped_low, quadruped_medium, theropod, Body,
|
||||
},
|
||||
trade::{Good, SiteInformation},
|
||||
trade::SiteInformation,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use rand::{self, distributions::WeightedError, seq::SliceRandom, Rng};
|
||||
@ -41,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 {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)),
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user