mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add multiloot
This commit is contained in:
parent
1a287e4e89
commit
ab4076518f
@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- NPCs will now tell you about nearby towns and how to visit them
|
- NPCs will now tell you about nearby towns and how to visit them
|
||||||
- NPCs will migrate to new towns if they are dissatisfied with their current town
|
- NPCs will migrate to new towns if they are dissatisfied with their current town
|
||||||
- Female humanoids now have a greeting sound effect
|
- Female humanoids now have a greeting sound effect
|
||||||
|
- Loot that drops multiple items is now distributed fairly between damage contributors.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
name: Name("Frost Gigas"),
|
name: Name("Frost Gigas"),
|
||||||
body: RandomWith("gigas_frost"),
|
body: RandomWith("gigas_frost"),
|
||||||
alignment: Alignment(Enemy),
|
alignment: Alignment(Enemy),
|
||||||
loot: LootTable("common.loot_tables.world.world_bosses.gigas_frost.boss"),
|
loot: MultiDrop(LootTable("common.loot_tables.world.world_bosses.gigas_frost.boss"), 3, 4),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: FromBody,
|
loadout: FromBody,
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[
|
[
|
||||||
(4.0, ItemQuantity("common.items.crafting_ing.animal_misc.icy_fang", 1, 20)),
|
(4.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.icy_fang"), 1, 20)),
|
||||||
(2.0, Item("common.items.armor.misc.head.boreal_warhelm")),
|
(2.0, Item("common.items.armor.misc.head.boreal_warhelm")),
|
||||||
(1.0, Item("common.items.lantern.polaris")),
|
(1.0, Item("common.items.lantern.polaris")),
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(3.0, ItemQuantity("common.items.food.blue_cheese", 1, 15)),
|
(3.0, MultiDrop(Item("common.items.food.blue_cheese"), 1, 15)),
|
||||||
(1.0, Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap")),
|
(1.0, Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap")),
|
||||||
]
|
]
|
@ -1,14 +1,14 @@
|
|||||||
[
|
[
|
||||||
// Fireworks
|
// Fireworks
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_blue", 8, 10)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_blue"), 8, 10)),
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_green", 8, 10)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_green"), 8, 10)),
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_purple", 8, 10)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_purple"), 8, 10)),
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_red", 8, 10)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_red"), 8, 10)),
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_white", 8, 10)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_white"), 8, 10)),
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_yellow", 8, 10)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_yellow"), 8, 10)),
|
||||||
// Potions
|
// Potions
|
||||||
(10.0, ItemQuantity("common.items.consumable.potion_big", 2, 5)),
|
(10.0, MultiDrop(Item("common.items.consumable.potion_big"), 2, 5)),
|
||||||
// Misc
|
// Misc
|
||||||
(5.0, ItemQuantity("common.items.utility.collar", 2, 3)),
|
(5.0, MultiDrop(Item("common.items.utility.collar"), 2, 3)),
|
||||||
(5.0, ItemQuantity("common.items.utility.bomb", 8, 10)),
|
(5.0, MultiDrop(Item("common.items.utility.bomb"), 8, 10)),
|
||||||
]
|
]
|
@ -1,14 +1,14 @@
|
|||||||
[
|
[
|
||||||
// Fireworks
|
// Fireworks
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_blue", 3, 5)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_blue"), 3, 5)),
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_green", 3, 5)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_green"), 3, 5)),
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_purple", 3, 5)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_purple"), 3, 5)),
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_red", 3, 5)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_red"), 3, 5)),
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_white", 3, 5)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_white"), 3, 5)),
|
||||||
(1.0, ItemQuantity("common.items.utility.firework_yellow", 3, 5)),
|
(1.0, MultiDrop(Item("common.items.utility.firework_yellow"), 3, 5)),
|
||||||
// Potions
|
// Potions
|
||||||
(10.0, ItemQuantity("common.items.consumable.potion_med", 2, 5)),
|
(10.0, MultiDrop(Item("common.items.consumable.potion_med"), 2, 5)),
|
||||||
// Misc
|
// Misc
|
||||||
(5.0, ItemQuantity("common.items.utility.collar", 1, 2)),
|
(5.0, MultiDrop(Item("common.items.utility.collar"), 1, 2)),
|
||||||
(5.0, ItemQuantity("common.items.utility.bomb", 3, 5)),
|
(5.0, MultiDrop(Item("common.items.utility.bomb"), 3, 5)),
|
||||||
]
|
]
|
@ -7,7 +7,7 @@
|
|||||||
(1.0, Item("common.items.utility.firework_white")),
|
(1.0, Item("common.items.utility.firework_white")),
|
||||||
(1.0, Item("common.items.utility.firework_yellow")),
|
(1.0, Item("common.items.utility.firework_yellow")),
|
||||||
// Potions
|
// Potions
|
||||||
(10.0, ItemQuantity("common.items.consumable.potion_minor", 2, 5)),
|
(10.0, MultiDrop(Item("common.items.consumable.potion_minor"), 2, 5)),
|
||||||
// Misc
|
// Misc
|
||||||
(5.0, Item("common.items.utility.collar")),
|
(5.0, Item("common.items.utility.collar")),
|
||||||
(5.0, Item("common.items.utility.bomb")),
|
(5.0, Item("common.items.utility.bomb")),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.5, ItemQuantity("common.items.crafting_ing.hide.carapace", 2, 5)),
|
(1.5, MultiDrop(Item("common.items.crafting_ing.hide.carapace"), 2, 5)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.strong_pincer", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.strong_pincer"), 1, 2)),
|
||||||
]
|
]
|
@ -1,5 +1,5 @@
|
|||||||
[
|
[
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.sticky_thread", 2, 5)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.sticky_thread"), 2, 5)),
|
||||||
(1.0, Item("common.items.crafting_ing.animal_misc.venom_sac")),
|
(1.0, Item("common.items.crafting_ing.animal_misc.venom_sac")),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.strong_pincer", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.strong_pincer"), 1, 2)),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.carapace", 1, 3)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.carapace"), 1, 3)),
|
||||||
(1.0, Item("common.items.crafting_ing.animal_misc.strong_pincer")),
|
(1.0, Item("common.items.crafting_ing.animal_misc.strong_pincer")),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(2.0, ItemQuantity("common.items.flowers.plant_fiber", 1, 3)),
|
(2.0, MultiDrop(Item("common.items.flowers.plant_fiber"), 1, 3)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.viscous_ooze", 1, 1)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.viscous_ooze"), 1, 1)),
|
||||||
]
|
]
|
@ -1,5 +1,5 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.flowers.plant_fiber", 1, 3)),
|
(1.0, MultiDrop(Item("common.items.flowers.plant_fiber"), 1, 3)),
|
||||||
(1.0, Item("common.items.crafting_ing.animal_misc.strong_pincer")),
|
(1.0, Item("common.items.crafting_ing.animal_misc.strong_pincer")),
|
||||||
(0.5, ItemQuantity("common.items.crafting_ing.animal_misc.viscous_ooze", 1, 1)),
|
(0.5, MultiDrop(Item("common.items.crafting_ing.animal_misc.viscous_ooze"), 1, 1)),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.sticky_thread", 1, 3)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.sticky_thread"), 1, 3)),
|
||||||
(0.5, ItemQuantity("common.items.crafting_ing.animal_misc.viscous_ooze", 1, 1)),
|
(0.5, MultiDrop(Item("common.items.crafting_ing.animal_misc.viscous_ooze"), 1, 1)),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.sticky_thread", 2, 5)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.sticky_thread"), 2, 5)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.strong_pincer", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.strong_pincer"), 1, 2)),
|
||||||
]
|
]
|
@ -1,5 +1,5 @@
|
|||||||
[
|
[
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.sticky_thread", 1, 3)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.sticky_thread"), 1, 3)),
|
||||||
(1.0, Item("common.items.crafting_ing.animal_misc.venom_sac")),
|
(1.0, Item("common.items.crafting_ing.animal_misc.venom_sac")),
|
||||||
(1.0, Item("common.items.crafting_ing.animal_misc.strong_pincer")),
|
(1.0, Item("common.items.crafting_ing.animal_misc.strong_pincer")),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.sticky_thread", 1, 3)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.sticky_thread"), 1, 3)),
|
||||||
(1.0, Item("common.items.crafting_ing.animal_misc.strong_pincer")),
|
(1.0, Item("common.items.crafting_ing.animal_misc.strong_pincer")),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, Item("common.items.crafting_ing.hide.rugged_hide")),
|
(1.0, Item("common.items.crafting_ing.hide.rugged_hide")),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.long_tusk", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.long_tusk"), 1, 2)),
|
||||||
]
|
]
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.food.meat.bird_large_raw", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.food.meat.bird_large_raw"), 1, 2)),
|
||||||
(1.0, ItemQuantity("common.items.food.meat.beast_large_raw", 2, 2)),
|
(1.0, MultiDrop(Item("common.items.food.meat.beast_large_raw"), 2, 2)),
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.animal_misc.elegant_crest", 1, 3)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.elegant_crest"), 1, 3)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.scales", 2, 6)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.scales"), 2, 6)),
|
||||||
]
|
]
|
@ -1,5 +1,5 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.food.meat.bird_large_raw", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.food.meat.bird_large_raw"), 1, 2)),
|
||||||
(1.0, ItemQuantity("common.items.food.meat.beast_large_raw", 2, 2)),
|
(1.0, MultiDrop(Item("common.items.food.meat.beast_large_raw"), 2, 2)),
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.animal_misc.elegant_crest", 1, 3)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.elegant_crest"), 1, 3)),
|
||||||
]
|
]
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.food.meat.bird_large_raw", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.food.meat.bird_large_raw"), 1, 2)),
|
||||||
(1.0, ItemQuantity("common.items.food.meat.beast_large_raw", 2, 2)),
|
(1.0, MultiDrop(Item("common.items.food.meat.beast_large_raw"), 2, 2)),
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.animal_misc.elegant_crest", 1, 3)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.elegant_crest"), 1, 3)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.scales", 2, 6)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.scales"), 2, 6)),
|
||||||
]
|
]
|
@ -1,5 +1,5 @@
|
|||||||
[
|
[
|
||||||
(1.0, Item("common.items.food.meat.beast_large_raw")),
|
(1.0, Item("common.items.food.meat.beast_large_raw")),
|
||||||
(1.0, Item("common.items.crafting_ing.hide.animal_hide")),
|
(1.0, Item("common.items.crafting_ing.hide.animal_hide")),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.fur", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.fur"), 1, 2)),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, Item("common.items.crafting_ing.hide.animal_hide")),
|
(1.0, Item("common.items.crafting_ing.hide.animal_hide")),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.icy_fang", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.icy_fang"), 1, 2)),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.rugged_hide", 1, 2)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.rugged_hide"), 1, 2)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.long_tusk", 2, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.long_tusk"), 2, 2)),
|
||||||
]
|
]
|
@ -1,5 +1,5 @@
|
|||||||
[
|
[
|
||||||
(2.0, Item("common.items.crafting_ing.hide.rugged_hide")),
|
(2.0, Item("common.items.crafting_ing.hide.rugged_hide")),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.large_horn", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.large_horn"), 1, 2)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.long_tusk", 2, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.long_tusk"), 2, 2)),
|
||||||
]
|
]
|
@ -1,5 +1,5 @@
|
|||||||
[
|
[
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.rugged_hide", 1, 3)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.rugged_hide"), 1, 3)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.icy_fang", 2, 4)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.icy_fang"), 2, 4)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.long_tusk", 2, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.long_tusk"), 2, 2)),
|
||||||
]
|
]
|
@ -2,5 +2,5 @@
|
|||||||
(1.5, Item("common.items.food.meat.beast_small_raw")),
|
(1.5, Item("common.items.food.meat.beast_small_raw")),
|
||||||
(0.5, Item("common.items.food.meat.beast_large_raw")),
|
(0.5, Item("common.items.food.meat.beast_large_raw")),
|
||||||
(1.0, Item("common.items.crafting_ing.hide.animal_hide")),
|
(1.0, Item("common.items.crafting_ing.hide.animal_hide")),
|
||||||
(5.0, ItemQuantity("common.items.crafting_ing.cloth.wool", 2, 5)),
|
(5.0, MultiDrop(Item("common.items.crafting_ing.cloth.wool"), 2, 5)),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.fur", 1, 3)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.fur"), 1, 3)),
|
||||||
(0.25, LootTable("common.loot_tables.creature.quad_small.generic")),
|
(0.25, LootTable("common.loot_tables.creature.quad_small.generic")),
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.animal_hide", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.animal_hide"), 1, 2)),
|
||||||
(0.25, Item("common.items.food.meat.beast_small_raw")),
|
(0.25, Item("common.items.food.meat.beast_small_raw")),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(9.0, ItemQuantity("common.items.food.mushroom", 2, 4)),
|
(9.0, MultiDrop(Item("common.items.food.mushroom"), 2, 4)),
|
||||||
(1.0, Item("common.items.crafting_ing.animal_misc.long_tusk")),
|
(1.0, Item("common.items.crafting_ing.animal_misc.long_tusk")),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.wool", 2, 5)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.wool"), 2, 5)),
|
||||||
(0.25, Item("common.items.food.meat.beast_small_raw")),
|
(0.25, Item("common.items.food.meat.beast_small_raw")),
|
||||||
]
|
]
|
@ -1,5 +1,5 @@
|
|||||||
[
|
[
|
||||||
(4.0, ItemQuantity("common.items.crafting_ing.animal_misc.elegant_crest", 3, 6)),
|
(4.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.elegant_crest"), 3, 6)),
|
||||||
(1.0, Item("common.items.crafting_ing.animal_misc.raptor_feather")),
|
(1.0, Item("common.items.crafting_ing.animal_misc.raptor_feather")),
|
||||||
(0.5, Item("common.items.weapons.hammer.burnt_drumstick")),
|
(0.5, Item("common.items.weapons.hammer.burnt_drumstick")),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, Item("common.items.crafting_ing.hide.rugged_hide")),
|
(1.0, Item("common.items.crafting_ing.hide.rugged_hide")),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.long_tusk", 2, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.long_tusk"), 2, 2)),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.plate", 1, 3)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.plate"), 1, 3)),
|
||||||
(1.0, Item("common.items.crafting_ing.animal_misc.large_horn")),
|
(1.0, Item("common.items.crafting_ing.animal_misc.large_horn")),
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.elegant_crest", 1, 1)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.elegant_crest"), 1, 1)),
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.animal_misc.raptor_feather", 2, 6)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.raptor_feather"), 2, 6)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.scales", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.scales"), 1, 2)),
|
||||||
]
|
]
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.raptor_feather", 1, 2)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.raptor_feather"), 1, 2)),
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.scales", 2, 6)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.scales"), 2, 6)),
|
||||||
]
|
]
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
(0.5, Item("common.items.crafting_ing.abyssal_heart")),
|
(0.5, Item("common.items.crafting_ing.abyssal_heart")),
|
||||||
(2.0, Item("common.items.crafting_ing.coral_branch")),
|
(2.0, Item("common.items.crafting_ing.coral_branch")),
|
||||||
(2.5, ItemQuantity("common.items.utility.coins", 50, 100)),
|
(2.5, MultiDrop(Item("common.items.utility.coins"), 50, 100)),
|
||||||
(0.2, Item("common.items.tool.instruments.glass_flute")),
|
(0.2, Item("common.items.tool.instruments.glass_flute")),
|
||||||
]
|
]
|
@ -3,12 +3,12 @@
|
|||||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-0")),
|
(1.0, LootTable("common.loot_tables.weapons.components.tier-0")),
|
||||||
(1.0, LootTable("common.loot_tables.armor.tier-0")),
|
(1.0, LootTable("common.loot_tables.armor.tier-0")),
|
||||||
// Currency
|
// Currency
|
||||||
(3.0, ItemQuantity("common.items.utility.coins", 10, 20)),
|
(3.0, MultiDrop(Item("common.items.utility.coins"), 10, 20)),
|
||||||
// Materials
|
// Materials
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.linen", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.linen"), 3, 10)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.leather.simple_leather", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.leather.simple_leather"), 3, 10)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ingot.bronze", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.mineral.ingot.bronze"), 3, 10)),
|
||||||
(1.0, ItemQuantity("common.items.log.wood", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.log.wood"), 3, 10)),
|
||||||
// Consumables
|
// Consumables
|
||||||
(2.0, LootTable("common.loot_tables.consumable.poor")),
|
(2.0, LootTable("common.loot_tables.consumable.poor")),
|
||||||
]
|
]
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
// Chieftain Mask
|
// Chieftain Mask
|
||||||
(1.0, Item("common.items.armor.misc.head.gnarling_mask")),
|
(1.0, Item("common.items.armor.misc.head.gnarling_mask")),
|
||||||
// Indirect crafting materials for T2 gear
|
// Indirect crafting materials for T2 gear
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.sticky_thread", 2, 5)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.sticky_thread"), 2, 5)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ore.coal", 2, 5)),
|
(1.0, MultiDrop(Item("common.items.mineral.ore.coal"), 2, 5)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.leather.leather_strips", 2, 5)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.leather.leather_strips"), 2, 5)),
|
||||||
]
|
]
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(1.0, ItemQuantity("common.items.utility.coins", 2, 4)),
|
(1.0, MultiDrop(Item("common.items.utility.coins"), 2, 4)),
|
||||||
// Food
|
// Food
|
||||||
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
||||||
// Nothing
|
// Nothing
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, LootTable("common.loot_tables.dungeon.tier-0.enemy")),
|
(1.0, LootTable("common.loot_tables.dungeon.tier-0.enemy")),
|
||||||
(1.0, ItemQuantity("common.items.flowers.plant_fiber", 2, 4)),
|
(1.0, MultiDrop(Item("common.items.flowers.plant_fiber"), 2, 4)),
|
||||||
]
|
]
|
@ -1,11 +1,11 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(1.0, ItemQuantity("common.items.utility.coins", 10, 20)),
|
(1.0, MultiDrop(Item("common.items.utility.coins"), 10, 20)),
|
||||||
// Food
|
// Food
|
||||||
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
||||||
// Consumables
|
// Consumables
|
||||||
(2.0, LootTable("common.loot_tables.consumable.poor")),
|
(2.0, LootTable("common.loot_tables.consumable.poor")),
|
||||||
// Crafting ingredients
|
// Crafting ingredients
|
||||||
(1.0, ItemQuantity("common.items.log.wood", 5, 10)),
|
(1.0, MultiDrop(Item("common.items.log.wood"), 5, 10)),
|
||||||
(0.5, LootTable("common.loot_tables.weapons.components.secondary.sceptre")),
|
(0.5, LootTable("common.loot_tables.weapons.components.secondary.sceptre")),
|
||||||
]
|
]
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
(1.0, LootTable("common.loot_tables.armor.tier-1")),
|
(1.0, LootTable("common.loot_tables.armor.tier-1")),
|
||||||
(0.5, Item("common.items.armor.misc.head.hog_hood")),
|
(0.5, Item("common.items.armor.misc.head.hog_hood")),
|
||||||
// Currency
|
// Currency
|
||||||
(3.0, ItemQuantity("common.items.utility.coins", 20, 50)),
|
(3.0, MultiDrop(Item("common.items.utility.coins"), 20, 50)),
|
||||||
// Materials
|
// Materials
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.wool", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.wool"), 3, 10)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.leather.thick_leather", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.leather.thick_leather"), 3, 10)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ingot.iron", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.mineral.ingot.iron"), 3, 10)),
|
||||||
(1.0, ItemQuantity("common.items.log.bamboo", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.log.bamboo"), 3, 10)),
|
||||||
// Consumables
|
// Consumables
|
||||||
(2.0, LootTable("common.loot_tables.consumable.poor")),
|
(2.0, LootTable("common.loot_tables.consumable.poor")),
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(1.0, ItemQuantity("common.items.utility.coins", 4, 10)),
|
(1.0, MultiDrop(Item("common.items.utility.coins"), 4, 10)),
|
||||||
// Food
|
// Food
|
||||||
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
||||||
// Nothing
|
// Nothing
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-2")),
|
(1.0, LootTable("common.loot_tables.weapons.components.tier-2")),
|
||||||
(1.0, LootTable("common.loot_tables.armor.tier-2")),
|
(1.0, LootTable("common.loot_tables.armor.tier-2")),
|
||||||
// Currency
|
// Currency
|
||||||
(3.0, ItemQuantity("common.items.utility.coins", 50, 100)),
|
(3.0, MultiDrop(Item("common.items.utility.coins"), 50, 100)),
|
||||||
// Materials
|
// Materials
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.silk", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.silk"), 3, 10)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.scales", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.scales"), 3, 10)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ingot.steel", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.mineral.ingot.steel"), 3, 10)),
|
||||||
(1.0, ItemQuantity("common.items.log.hardwood", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.log.hardwood"), 3, 10)),
|
||||||
// Consumables
|
// Consumables
|
||||||
(2.0, LootTable("common.loot_tables.consumable.moderate")),
|
(2.0, LootTable("common.loot_tables.consumable.moderate")),
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(1.0, ItemQuantity("common.items.utility.coins", 10, 20)),
|
(1.0, MultiDrop(Item("common.items.utility.coins"), 10, 20)),
|
||||||
// Food
|
// Food
|
||||||
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
||||||
// Nothing
|
// Nothing
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-3")),
|
(1.0, LootTable("common.loot_tables.weapons.components.tier-3")),
|
||||||
(1.0, LootTable("common.loot_tables.armor.tier-3")),
|
(1.0, LootTable("common.loot_tables.armor.tier-3")),
|
||||||
// Currency
|
// Currency
|
||||||
(3.0, ItemQuantity("common.items.utility.coins", 100, 200)),
|
(3.0, MultiDrop(Item("common.items.utility.coins"), 100, 200)),
|
||||||
// Materials
|
// Materials
|
||||||
// Allow ironwood to have higher drops till entity droppers are implemented
|
// Allow ironwood to have higher drops till entity droppers are implemented
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.lifecloth", 2, 6)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.lifecloth"), 2, 6)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.carapace", 2, 6)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.carapace"), 2, 6)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ingot.cobalt", 2, 6)),
|
(1.0, MultiDrop(Item("common.items.mineral.ingot.cobalt"), 2, 6)),
|
||||||
(1.0, ItemQuantity("common.items.log.ironwood", 3, 7)),
|
(1.0, MultiDrop(Item("common.items.log.ironwood"), 3, 7)),
|
||||||
// Consumables
|
// Consumables
|
||||||
(2.0, LootTable("common.loot_tables.consumable.moderate")),
|
(2.0, LootTable("common.loot_tables.consumable.moderate")),
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(1.0, ItemQuantity("common.items.utility.coins", 20, 40)),
|
(1.0, MultiDrop(Item("common.items.utility.coins"), 20, 40)),
|
||||||
// Food
|
// Food
|
||||||
(1.0, LootTable("common.loot_tables.food.prepared")),
|
(1.0, LootTable("common.loot_tables.food.prepared")),
|
||||||
// Nothing
|
// Nothing
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
(1.0, LootTable("common.loot_tables.weapons.legendary_melee")),
|
(1.0, LootTable("common.loot_tables.weapons.legendary_melee")),
|
||||||
// Crafting material
|
// Crafting material
|
||||||
// Allow for DS and Eldwood to have higher drops till entity droppers are implemented
|
// Allow for DS and Eldwood to have higher drops till entity droppers are implemented
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.sunsilk", 1, 3)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.sunsilk"), 1, 3)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.dragon_scale", 3, 8)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.dragon_scale"), 3, 8)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ingot.orichalcum", 1, 3)),
|
(1.0, MultiDrop(Item("common.items.mineral.ingot.orichalcum"), 1, 3)),
|
||||||
(1.0, ItemQuantity("common.items.log.eldwood", 2, 6)),
|
(1.0, MultiDrop(Item("common.items.log.eldwood"), 2, 6)),
|
||||||
]
|
]
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
(1.0, LootTable("common.loot_tables.armor.tier-4")),
|
(1.0, LootTable("common.loot_tables.armor.tier-4")),
|
||||||
(1.0, Item("common.items.armor.misc.head.spikeguard")),
|
(1.0, Item("common.items.armor.misc.head.spikeguard")),
|
||||||
// Currency
|
// Currency
|
||||||
(3.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
(3.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||||
// Materials
|
// Materials
|
||||||
// Allow frostwood to have higher drops till entity droppers are implemented
|
// Allow frostwood to have higher drops till entity droppers are implemented
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.moonweave", 1, 5)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.moonweave"), 1, 5)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.plate", 1, 5)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.plate"), 1, 5)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ingot.bloodsteel", 1, 5)),
|
(1.0, MultiDrop(Item("common.items.mineral.ingot.bloodsteel"), 1, 5)),
|
||||||
(1.0, ItemQuantity("common.items.log.frostwood", 3, 6)),
|
(1.0, MultiDrop(Item("common.items.log.frostwood"), 3, 6)),
|
||||||
// Consumables
|
// Consumables
|
||||||
(2.0, LootTable("common.loot_tables.consumable.good")),
|
(2.0, LootTable("common.loot_tables.consumable.good")),
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(1.0, ItemQuantity("common.items.utility.coins", 40, 100)),
|
(1.0, MultiDrop(Item("common.items.utility.coins"), 40, 100)),
|
||||||
// Food
|
// Food
|
||||||
(1.0, LootTable("common.loot_tables.food.prepared")),
|
(1.0, LootTable("common.loot_tables.food.prepared")),
|
||||||
// Nothing
|
// Nothing
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(10.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
(10.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||||
// Food
|
// Food
|
||||||
(5.0, LootTable("common.loot_tables.food.prepared")),
|
(5.0, LootTable("common.loot_tables.food.prepared")),
|
||||||
// Consumables
|
// Consumables
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
// Crafting material
|
// Crafting material
|
||||||
// Allow for DS and Eldwood to have higher drops till entity droppers are implemented
|
// Allow for DS and Eldwood to have higher drops till entity droppers are implemented
|
||||||
(1.0, Item("common.items.crafting_ing.mindflayer_bag_damaged")),
|
(1.0, Item("common.items.crafting_ing.mindflayer_bag_damaged")),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.sunsilk", 1, 3)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.sunsilk"), 1, 3)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.dragon_scale", 3, 8)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.dragon_scale"), 3, 8)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ingot.orichalcum", 1, 3)),
|
(1.0, MultiDrop(Item("common.items.mineral.ingot.orichalcum"), 1, 3)),
|
||||||
(1.0, ItemQuantity("common.items.log.eldwood", 2, 6)),
|
(1.0, MultiDrop(Item("common.items.log.eldwood"), 2, 6)),
|
||||||
]
|
]
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
(1.0, LootTable("common.loot_tables.armor.tier-4")),
|
(1.0, LootTable("common.loot_tables.armor.tier-4")),
|
||||||
(0.1, Item("common.items.armor.cultist.bandana")),
|
(0.1, Item("common.items.armor.cultist.bandana")),
|
||||||
// Currency
|
// Currency
|
||||||
(3.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
(3.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||||
// Materials
|
// Materials
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.moonweave", 1, 5)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.moonweave"), 1, 5)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.plate", 1, 5)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.plate"), 1, 5)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ingot.bloodsteel", 1, 5)),
|
(1.0, MultiDrop(Item("common.items.mineral.ingot.bloodsteel"), 1, 5)),
|
||||||
(1.0, ItemQuantity("common.items.log.frostwood", 1, 5)),
|
(1.0, MultiDrop(Item("common.items.log.frostwood"), 1, 5)),
|
||||||
// Consumables
|
// Consumables
|
||||||
(2.0, LootTable("common.loot_tables.consumable.good")),
|
(2.0, LootTable("common.loot_tables.consumable.good")),
|
||||||
(0.1, ItemQuantity("common.items.food.spore_corruption", 1, 3)),
|
(0.1, MultiDrop(Item("common.items.food.spore_corruption"), 1, 3)),
|
||||||
]
|
]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(1.0, ItemQuantity("common.items.utility.coins", 100, 200)),
|
(1.0, MultiDrop(Item("common.items.utility.coins"), 100, 200)),
|
||||||
// Food
|
// Food
|
||||||
(1.0, LootTable("common.loot_tables.food.prepared")),
|
(1.0, LootTable("common.loot_tables.food.prepared")),
|
||||||
// Cheese
|
// Cheese
|
||||||
(2.0, ItemQuantity("common.items.food.cheese", 3, 5)),
|
(2.0, MultiDrop(Item("common.items.food.cheese"), 3, 5)),
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(10.0, ItemQuantity("common.items.utility.coins", 500, 1000)),
|
(10.0, MultiDrop(Item("common.items.utility.coins"), 500, 1000)),
|
||||||
// Food
|
// Food
|
||||||
(5.0, LootTable("common.loot_tables.food.prepared")),
|
(5.0, LootTable("common.loot_tables.food.prepared")),
|
||||||
// Consumables
|
// Consumables
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(30.0, ItemQuantity("common.items.utility.coins", 50, 100)),
|
(30.0, MultiDrop(Item("common.items.utility.coins"), 50, 100)),
|
||||||
// Nothing
|
// Nothing
|
||||||
(30.0, Nothing),
|
(30.0, Nothing),
|
||||||
// Special
|
// Special
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
[
|
[
|
||||||
//Currency
|
//Currency
|
||||||
(4.0, ItemQuantity("common.items.utility.coins", 50, 200)),
|
(4.0, MultiDrop(Item("common.items.utility.coins"), 50, 200)),
|
||||||
//Food
|
//Food
|
||||||
(4.0, LootTable("common.loot_tables.food.prepared")),
|
(4.0, LootTable("common.loot_tables.food.prepared")),
|
||||||
//Flowers, pretty
|
//Flowers, pretty
|
||||||
(2.0, ItemQuantity("common.items.flowers.red", 3, 6)),
|
(2.0, MultiDrop(Item("common.items.flowers.red"), 3, 6)),
|
||||||
//Weapon components
|
//Weapon components
|
||||||
(2.0, LootTable("common.loot_tables.weapons.components.tier-0")),
|
(2.0, LootTable("common.loot_tables.weapons.components.tier-0")),
|
||||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-1")),
|
(1.0, LootTable("common.loot_tables.weapons.components.tier-1")),
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
[
|
[
|
||||||
//Currency
|
//Currency
|
||||||
(3.0, ItemQuantity("common.items.utility.coins", 50, 200)),
|
(3.0, MultiDrop(Item("common.items.utility.coins"), 50, 200)),
|
||||||
(2.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
(2.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||||
//Food
|
//Food
|
||||||
(3.0, LootTable("common.loot_tables.food.prepared")),
|
(3.0, LootTable("common.loot_tables.food.prepared")),
|
||||||
//Ores
|
//Ores
|
||||||
(2.0, ItemQuantity("common.items.mineral.ore.iron", 2, 7)),
|
(2.0, MultiDrop(Item("common.items.mineral.ore.iron"), 2, 7)),
|
||||||
//Hides
|
//Hides
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.animal_hide", 5, 15)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.animal_hide"), 5, 15)),
|
||||||
//Flowers, pretty
|
//Flowers, pretty
|
||||||
(2.0, ItemQuantity("common.items.flowers.red", 3, 6)),
|
(2.0, MultiDrop(Item("common.items.flowers.red"), 3, 6)),
|
||||||
//Weapon components
|
//Weapon components
|
||||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-2")),
|
(1.0, LootTable("common.loot_tables.weapons.components.tier-2")),
|
||||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-3")),
|
(1.0, LootTable("common.loot_tables.weapons.components.tier-3")),
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
[
|
[
|
||||||
//Currency
|
//Currency
|
||||||
(3.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
(3.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||||
(2.0, ItemQuantity("common.items.utility.coins", 2000, 5000)),
|
(2.0, MultiDrop(Item("common.items.utility.coins"), 2000, 5000)),
|
||||||
//Food
|
//Food
|
||||||
(3.0, LootTable("common.loot_tables.food.prepared")),
|
(3.0, LootTable("common.loot_tables.food.prepared")),
|
||||||
//Ores
|
//Ores
|
||||||
(2.0, ItemQuantity("common.items.mineral.ore.coal", 5, 15)),
|
(2.0, MultiDrop(Item("common.items.mineral.ore.coal"), 5, 15)),
|
||||||
(2.0, ItemQuantity("common.items.mineral.ore.iron", 2, 7)),
|
(2.0, MultiDrop(Item("common.items.mineral.ore.iron"), 2, 7)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ore.cobalt", 2, 5)),
|
(1.0, MultiDrop(Item("common.items.mineral.ore.cobalt"), 2, 5)),
|
||||||
(0.1, ItemQuantity("common.items.mineral.gem.diamond", 2, 5)),
|
(0.1, MultiDrop(Item("common.items.mineral.gem.diamond"), 2, 5)),
|
||||||
//Hides
|
//Hides
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.animal_hide", 5, 15)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.animal_hide"), 5, 15)),
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.scales", 5, 15)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.scales"), 5, 15)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.carapace", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.carapace"), 3, 10)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.tough_hide", 3, 10)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.tough_hide"), 3, 10)),
|
||||||
//Flowers, very pretty
|
//Flowers, very pretty
|
||||||
(2.0, ItemQuantity("common.items.flowers.red", 5, 10)),
|
(2.0, MultiDrop(Item("common.items.flowers.red"), 5, 10)),
|
||||||
//Weapon components
|
//Weapon components
|
||||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-2")),
|
(1.0, LootTable("common.loot_tables.weapons.components.tier-2")),
|
||||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-3")),
|
(1.0, LootTable("common.loot_tables.weapons.components.tier-3")),
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
[
|
[
|
||||||
//Currency
|
//Currency
|
||||||
(2.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
(2.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||||
(1.0, ItemQuantity("common.items.utility.coins", 2000, 5000)),
|
(1.0, MultiDrop(Item("common.items.utility.coins"), 2000, 5000)),
|
||||||
//Food
|
//Food
|
||||||
(4.0, LootTable("common.loot_tables.food.prepared")),
|
(4.0, LootTable("common.loot_tables.food.prepared")),
|
||||||
//Ores
|
//Ores
|
||||||
(2.0, ItemQuantity("common.items.mineral.ore.coal", 10, 30)),
|
(2.0, MultiDrop(Item("common.items.mineral.ore.coal"), 10, 30)),
|
||||||
(2.0, ItemQuantity("common.items.mineral.ore.iron", 4, 14)),
|
(2.0, MultiDrop(Item("common.items.mineral.ore.iron"), 4, 14)),
|
||||||
(1.0, ItemQuantity("common.items.mineral.ore.cobalt", 4, 10)),
|
(1.0, MultiDrop(Item("common.items.mineral.ore.cobalt"), 4, 10)),
|
||||||
(0.1, ItemQuantity("common.items.mineral.gem.diamond", 4, 10)),
|
(0.1, MultiDrop(Item("common.items.mineral.gem.diamond"), 4, 10)),
|
||||||
//Hides
|
//Hides
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.animal_hide", 10, 30)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.animal_hide"), 10, 30)),
|
||||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.scales", 10, 30)),
|
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.scales"), 10, 30)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.carapace", 6, 20)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.carapace"), 6, 20)),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.tough_hide", 6, 20)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.tough_hide"), 6, 20)),
|
||||||
(0.3, ItemQuantity("common.items.crafting_ing.hide.plate", 1, 8)),
|
(0.3, MultiDrop(Item("common.items.crafting_ing.hide.plate"), 1, 8)),
|
||||||
(0.1, ItemQuantity("common.items.crafting_ing.hide.rugged_hide", 1, 6)),
|
(0.1, MultiDrop(Item("common.items.crafting_ing.hide.rugged_hide"), 1, 6)),
|
||||||
//Flowers, very pretty
|
//Flowers, very pretty
|
||||||
(3.0, ItemQuantity("common.items.flowers.red", 10, 20)),
|
(3.0, MultiDrop(Item("common.items.flowers.red"), 10, 20)),
|
||||||
(2.0, ItemQuantity("common.items.flowers.moonbell", 6, 12)),
|
(2.0, MultiDrop(Item("common.items.flowers.moonbell"), 6, 12)),
|
||||||
(1.0, ItemQuantity("common.items.flowers.pyrebloom", 3, 6)),
|
(1.0, MultiDrop(Item("common.items.flowers.pyrebloom"), 3, 6)),
|
||||||
//Weapon components
|
//Weapon components
|
||||||
(1.5, LootTable("common.loot_tables.weapons.components.tier-4")),
|
(1.5, LootTable("common.loot_tables.weapons.components.tier-4")),
|
||||||
(0.2, LootTable("common.loot_tables.weapons.components.tier-5")),
|
(0.2, LootTable("common.loot_tables.weapons.components.tier-5")),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[
|
[
|
||||||
(1.0, LootTable("common.loot_tables.armor.boreal")),
|
(1.0, LootTable("common.loot_tables.armor.boreal")),
|
||||||
(1.0, ItemQuantity("common.items.crafting_ing.glacial_crystal", 5, 15)),
|
(1.0, MultiDrop(Item("common.items.crafting_ing.glacial_crystal"), 5, 15)),
|
||||||
]
|
]
|
@ -1,6 +1,6 @@
|
|||||||
[
|
[
|
||||||
// Currency
|
// Currency
|
||||||
(1.0, ItemQuantity("common.items.utility.coins", 40, 100)),
|
(1.0, MultiDrop(Item("common.items.utility.coins"), 40, 100)),
|
||||||
// Food
|
// Food
|
||||||
(1.0, LootTable("common.loot_tables.food.prepared")),
|
(1.0, LootTable("common.loot_tables.food.prepared")),
|
||||||
// Nothing
|
// Nothing
|
||||||
|
@ -102,7 +102,7 @@ pub enum Event {
|
|||||||
},
|
},
|
||||||
Disconnect,
|
Disconnect,
|
||||||
DisconnectionNotification(u64),
|
DisconnectionNotification(u64),
|
||||||
InventoryUpdated(InventoryUpdateEvent),
|
InventoryUpdated(Vec<InventoryUpdateEvent>),
|
||||||
Kicked(String),
|
Kicked(String),
|
||||||
Notification(Notification),
|
Notification(Notification),
|
||||||
SetViewDistance(u32),
|
SetViewDistance(u32),
|
||||||
@ -2388,34 +2388,38 @@ impl Client {
|
|||||||
self.presence = None;
|
self.presence = None;
|
||||||
self.clean_state();
|
self.clean_state();
|
||||||
},
|
},
|
||||||
ServerGeneral::InventoryUpdate(inventory, event) => {
|
ServerGeneral::InventoryUpdate(inventory, events) => {
|
||||||
match event {
|
let mut update_inventory = false;
|
||||||
InventoryUpdateEvent::BlockCollectFailed { .. } => {},
|
for event in events.iter() {
|
||||||
InventoryUpdateEvent::EntityCollectFailed { .. } => {},
|
match event {
|
||||||
_ => {
|
InventoryUpdateEvent::BlockCollectFailed { .. } => {},
|
||||||
// Push the updated inventory component to the client
|
InventoryUpdateEvent::EntityCollectFailed { .. } => {},
|
||||||
// FIXME: Figure out whether this error can happen under normal gameplay,
|
_ => update_inventory = true,
|
||||||
// if not find a better way to handle it, if so maybe consider kicking the
|
}
|
||||||
// client back to login?
|
}
|
||||||
let entity = self.entity();
|
if update_inventory {
|
||||||
if let Err(e) = self
|
// Push the updated inventory component to the client
|
||||||
.state
|
// FIXME: Figure out whether this error can happen under normal gameplay,
|
||||||
.ecs_mut()
|
// if not find a better way to handle it, if so maybe consider kicking the
|
||||||
.write_storage()
|
// client back to login?
|
||||||
.insert(entity, inventory)
|
let entity = self.entity();
|
||||||
{
|
if let Err(e) = self
|
||||||
warn!(
|
.state
|
||||||
?e,
|
.ecs_mut()
|
||||||
"Received an inventory update event for client entity, but this \
|
.write_storage()
|
||||||
entity was not found... this may be a bug."
|
.insert(entity, inventory)
|
||||||
);
|
{
|
||||||
}
|
warn!(
|
||||||
},
|
?e,
|
||||||
|
"Received an inventory update event for client entity, but this \
|
||||||
|
entity was not found... this may be a bug."
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_available_recipes();
|
self.update_available_recipes();
|
||||||
|
|
||||||
frontend_events.push(Event::InventoryUpdated(event));
|
frontend_events.push(Event::InventoryUpdated(events));
|
||||||
},
|
},
|
||||||
ServerGeneral::SetViewDistance(vd) => {
|
ServerGeneral::SetViewDistance(vd) => {
|
||||||
self.view_distance = Some(vd);
|
self.view_distance = Some(vd);
|
||||||
|
@ -91,6 +91,7 @@ rand_chacha = "0.3"
|
|||||||
tracing-subscriber = { version = "0.3.7", default-features = false, features = ["fmt", "time", "ansi", "smallvec", "env-filter"] }
|
tracing-subscriber = { version = "0.3.7", default-features = false, features = ["fmt", "time", "ansi", "smallvec", "env-filter"] }
|
||||||
petgraph = "0.6.0"
|
petgraph = "0.6.0"
|
||||||
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "chonk_benchmark"
|
name = "chonk_benchmark"
|
||||||
harness = false
|
harness = false
|
||||||
@ -99,6 +100,10 @@ harness = false
|
|||||||
name = "color_benchmark"
|
name = "color_benchmark"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "loot_benchmark"
|
||||||
|
harness = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "csv_export"
|
name = "csv_export"
|
||||||
required-features = ["bin_csv"]
|
required-features = ["bin_csv"]
|
||||||
|
146
common/benches/loot_benchmark.rs
Normal file
146
common/benches/loot_benchmark.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
use rand::thread_rng;
|
||||||
|
use veloren_common::lottery::distribute_many;
|
||||||
|
|
||||||
|
fn criterion_benchmark(c: &mut Criterion) {
|
||||||
|
let mut c = c.benchmark_group("loot");
|
||||||
|
|
||||||
|
c.bench_function("loot distribute 1000 among 10", |b| {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let v = (1..=10).map(|i| (i as f32 * 10.0, i)).collect::<Vec<_>>();
|
||||||
|
let items = vec![1, 2, 997];
|
||||||
|
b.iter(|| {
|
||||||
|
distribute_many(
|
||||||
|
black_box(v.iter().copied()),
|
||||||
|
&mut rng,
|
||||||
|
black_box(&items),
|
||||||
|
|i| *i,
|
||||||
|
|a, b, c| {
|
||||||
|
black_box((a, b, c));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("loot distribute 1000 among 100", |b| {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let v = (1..=100).map(|i| (i as f32 * 10.0, i)).collect::<Vec<_>>();
|
||||||
|
let items = vec![1, 2, 997];
|
||||||
|
b.iter(|| {
|
||||||
|
distribute_many(
|
||||||
|
black_box(v.iter().copied()),
|
||||||
|
&mut rng,
|
||||||
|
black_box(&items),
|
||||||
|
|i| *i,
|
||||||
|
|a, b, c| {
|
||||||
|
black_box((a, b, c));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("loot distribute 10000 among 10", |b| {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let v = (1..=10).map(|i| (i as f32 * 10.0, i)).collect::<Vec<_>>();
|
||||||
|
let items = vec![1, 2, 3, 9994];
|
||||||
|
b.iter(|| {
|
||||||
|
distribute_many(
|
||||||
|
black_box(v.iter().copied()),
|
||||||
|
&mut rng,
|
||||||
|
black_box(&items),
|
||||||
|
|i| *i,
|
||||||
|
|a, b, c| {
|
||||||
|
black_box((a, b, c));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("loot distribute 10000 among 1", |b| {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let v = (1..=1).map(|i| (i as f32 * 10.0, i)).collect::<Vec<_>>();
|
||||||
|
let items = vec![1, 2, 3, 9994];
|
||||||
|
b.iter(|| {
|
||||||
|
distribute_many(
|
||||||
|
black_box(v.iter().copied()),
|
||||||
|
&mut rng,
|
||||||
|
black_box(&items),
|
||||||
|
|i| *i,
|
||||||
|
|a, b, c| {
|
||||||
|
black_box((a, b, c));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("loot distribute 100000 among 20", |b| {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let v = (1..=20).map(|i| (i as f32 * 10.0, i)).collect::<Vec<_>>();
|
||||||
|
let items = vec![1, 2, 3, 99994];
|
||||||
|
b.iter(|| {
|
||||||
|
distribute_many(
|
||||||
|
black_box(v.iter().copied()),
|
||||||
|
&mut rng,
|
||||||
|
black_box(&items),
|
||||||
|
|i| *i,
|
||||||
|
|a, b, c| {
|
||||||
|
black_box((a, b, c));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("loot distribute 1000 among 400", |b| {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let v = (1..=400).map(|i| (i as f32 * 10.0, i)).collect::<Vec<_>>();
|
||||||
|
let items = vec![1, 2, 997];
|
||||||
|
b.iter(|| {
|
||||||
|
distribute_many(
|
||||||
|
v.iter().copied(),
|
||||||
|
&mut rng,
|
||||||
|
black_box(&items),
|
||||||
|
|i| *i,
|
||||||
|
|a, b, c| {
|
||||||
|
black_box((a, b, c));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("loot distribute 1000 among 1000", |b| {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let v = (1..=1000).map(|i| (i as f32 * 10.0, i)).collect::<Vec<_>>();
|
||||||
|
let items = vec![1, 2, 997];
|
||||||
|
b.iter(|| {
|
||||||
|
distribute_many(
|
||||||
|
v.iter().copied(),
|
||||||
|
&mut rng,
|
||||||
|
black_box(&items),
|
||||||
|
|i| *i,
|
||||||
|
|a, b, c| {
|
||||||
|
black_box((a, b, c));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
c.bench_function("loot distribute 10000 among 1000", |b| {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let v = (1..=1000).map(|i| (i as f32 * 10.0, i)).collect::<Vec<_>>();
|
||||||
|
let items = vec![1, 2, 3, 9994];
|
||||||
|
b.iter(|| {
|
||||||
|
distribute_many(
|
||||||
|
v.iter().copied(),
|
||||||
|
&mut rng,
|
||||||
|
black_box(&items),
|
||||||
|
|i| *i,
|
||||||
|
|a, b, c| {
|
||||||
|
black_box((a, b, c));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, criterion_benchmark);
|
||||||
|
criterion_main!(benches);
|
@ -170,7 +170,7 @@ pub enum ServerGeneral {
|
|||||||
/// Trigger cleanup for when the client goes back to the `Registered` state
|
/// Trigger cleanup for when the client goes back to the `Registered` state
|
||||||
/// from an ingame state
|
/// from an ingame state
|
||||||
ExitInGameSuccess,
|
ExitInGameSuccess,
|
||||||
InventoryUpdate(comp::Inventory, comp::InventoryUpdateEvent),
|
InventoryUpdate(comp::Inventory, Vec<comp::InventoryUpdateEvent>),
|
||||||
/// NOTE: The client can infer that entity view distance will be at most the
|
/// NOTE: The client can infer that entity view distance will be at most the
|
||||||
/// terrain view distance that we send here (and if lower it won't be
|
/// terrain view distance that we send here (and if lower it won't be
|
||||||
/// modified). So we just need to send the terrain VD back to the client
|
/// modified). So we just need to send the terrain VD back to the client
|
||||||
|
@ -254,48 +254,53 @@ fn loot_table(loot_table: &str) -> Result<(), Box<dyn Error>> {
|
|||||||
.div(10_f32.powi(5))
|
.div(10_f32.powi(5))
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let get_hands = |hands| match hands {
|
fn write_loot_spec<W: std::io::Write>(
|
||||||
Some(Hands::One) => "One",
|
wtr: &mut csv::Writer<W>,
|
||||||
Some(Hands::Two) => "Two",
|
loot_spec: &LootSpec<String>,
|
||||||
None => "",
|
chance: &str,
|
||||||
};
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let get_hands = |hands| match hands {
|
||||||
match item {
|
Some(Hands::One) => "One",
|
||||||
LootSpec::Item(item) => wtr.write_record([&chance, "Item", item, "", ""])?,
|
Some(Hands::Two) => "Two",
|
||||||
LootSpec::ItemQuantity(item, lower, upper) => wtr.write_record([
|
None => "",
|
||||||
&chance,
|
};
|
||||||
"Item",
|
match loot_spec {
|
||||||
item,
|
LootSpec::Item(item) => wtr.write_record([chance, "Item", item, "", ""])?,
|
||||||
&lower.to_string(),
|
LootSpec::LootTable(table) => {
|
||||||
&upper.to_string(),
|
wtr.write_record([chance, "LootTable", table, "", ""])?
|
||||||
])?,
|
},
|
||||||
LootSpec::LootTable(table) => {
|
LootSpec::Nothing => wtr.write_record([chance, "Nothing", "", ""])?,
|
||||||
wtr.write_record([&chance, "LootTable", table, "", ""])?
|
LootSpec::ModularWeapon {
|
||||||
},
|
tool,
|
||||||
LootSpec::Nothing => wtr.write_record([&chance, "Nothing", "", ""])?,
|
material,
|
||||||
LootSpec::ModularWeapon {
|
hands,
|
||||||
tool,
|
} => wtr.write_record([
|
||||||
material,
|
chance,
|
||||||
hands,
|
"Modular Weapon",
|
||||||
} => wtr.write_record([
|
&get_tool_kind(tool),
|
||||||
&chance,
|
material.into(),
|
||||||
"Modular Weapon",
|
get_hands(*hands),
|
||||||
&get_tool_kind(tool),
|
])?,
|
||||||
material.into(),
|
LootSpec::ModularWeaponPrimaryComponent {
|
||||||
get_hands(*hands),
|
tool,
|
||||||
])?,
|
material,
|
||||||
LootSpec::ModularWeaponPrimaryComponent {
|
hands,
|
||||||
tool,
|
} => wtr.write_record([
|
||||||
material,
|
chance,
|
||||||
hands,
|
"Modular Weapon Primary Component",
|
||||||
} => wtr.write_record([
|
&get_tool_kind(tool),
|
||||||
&chance,
|
material.into(),
|
||||||
"Modular Weapon Primary Component",
|
get_hands(*hands),
|
||||||
&get_tool_kind(tool),
|
])?,
|
||||||
material.into(),
|
LootSpec::MultiDrop(loot, _, _) => {
|
||||||
get_hands(*hands),
|
// TODO: Write amount gotten somewhere?
|
||||||
])?,
|
write_loot_spec(wtr, loot, chance)?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write_loot_spec(&mut wtr, item, &chance)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
wtr.flush()?;
|
wtr.flush()?;
|
||||||
@ -406,16 +411,6 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
|
|||||||
"1".to_owned(),
|
"1".to_owned(),
|
||||||
])?;
|
])?;
|
||||||
},
|
},
|
||||||
LootSpec::ItemQuantity(item, lower, upper) => {
|
|
||||||
wtr.write_record(&[
|
|
||||||
name.clone(),
|
|
||||||
asset_path.to_owned(),
|
|
||||||
percent_chance,
|
|
||||||
item_name(item),
|
|
||||||
// Tab needed so excel doesn't think it is a date...
|
|
||||||
format!("{lower}-{upper}\t"),
|
|
||||||
])?;
|
|
||||||
},
|
|
||||||
LootSpec::Nothing => {
|
LootSpec::Nothing => {
|
||||||
wtr.write_record(&[
|
wtr.write_record(&[
|
||||||
name.clone(),
|
name.clone(),
|
||||||
@ -477,6 +472,7 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
LootSpec::LootTable(_) => unreachable!(),
|
LootSpec::LootTable(_) => unreachable!(),
|
||||||
|
LootSpec::MultiDrop(_, _, _) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -921,6 +921,34 @@ impl Item {
|
|||||||
new_item
|
new_item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stacked_duplicates<'a>(
|
||||||
|
&'a self,
|
||||||
|
ability_map: &'a AbilityMap,
|
||||||
|
msm: &'a MaterialStatManifest,
|
||||||
|
count: u32,
|
||||||
|
) -> impl Iterator<Item = Self> + 'a {
|
||||||
|
let max_stack_count = count / self.max_amount();
|
||||||
|
let rest = count % self.max_amount();
|
||||||
|
|
||||||
|
(0..max_stack_count)
|
||||||
|
.map(|_| {
|
||||||
|
let mut item = self.duplicate(ability_map, msm);
|
||||||
|
|
||||||
|
item.set_amount(item.max_amount())
|
||||||
|
.expect("max_amount() is always a valid amount.");
|
||||||
|
|
||||||
|
item
|
||||||
|
})
|
||||||
|
.chain((rest > 0).then(move || {
|
||||||
|
let mut item = self.duplicate(ability_map, msm);
|
||||||
|
|
||||||
|
item.set_amount(rest)
|
||||||
|
.expect("anything less than max_amount() is always a valid amount.");
|
||||||
|
|
||||||
|
item
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
/// FIXME: HACK: In order to set the entity ID asynchronously, we currently
|
/// FIXME: HACK: In order to set the entity ID asynchronously, we currently
|
||||||
/// start it at None, and then atomically set it when it's saved for the
|
/// start it at None, and then atomically set it when it's saved for the
|
||||||
/// first time in the database. Because this requires shared mutable
|
/// first time in the database. Because this requires shared mutable
|
||||||
@ -1167,8 +1195,8 @@ impl Item {
|
|||||||
|
|
||||||
pub fn slot_mut(&mut self, slot: usize) -> Option<&mut InvSlot> { self.slots.get_mut(slot) }
|
pub fn slot_mut(&mut self, slot: usize) -> Option<&mut InvSlot> { self.slots.get_mut(slot) }
|
||||||
|
|
||||||
pub fn try_reclaim_from_block(block: Block) -> Option<Self> {
|
pub fn try_reclaim_from_block(block: Block) -> Option<Vec<(u32, Self)>> {
|
||||||
block.get_sprite()?.collectible_id()??.to_item()
|
block.get_sprite()?.collectible_id()??.to_items()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ability_spec(&self) -> Option<Cow<AbilitySpec>> {
|
pub fn ability_spec(&self) -> Option<Cow<AbilitySpec>> {
|
||||||
@ -1282,6 +1310,16 @@ impl Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn flatten_counted_items<'a>(
|
||||||
|
items: &'a [(u32, Item)],
|
||||||
|
ability_map: &'a AbilityMap,
|
||||||
|
msm: &'a MaterialStatManifest,
|
||||||
|
) -> impl Iterator<Item = Item> + 'a {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(count, item)| item.stacked_duplicates(ability_map, msm, *count))
|
||||||
|
}
|
||||||
|
|
||||||
/// Provides common methods providing details about an item definition
|
/// Provides common methods providing details about an item definition
|
||||||
/// for either an `Item` containing the definition, or the actual `ItemDef`
|
/// for either an `Item` containing the definition, or the actual `ItemDef`
|
||||||
pub trait ItemDesc {
|
pub trait ItemDesc {
|
||||||
@ -1370,9 +1408,9 @@ impl Component for Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct ItemDrop(pub Item);
|
pub struct ItemDrops(pub Vec<(u32, Item)>);
|
||||||
|
|
||||||
impl Component for ItemDrop {
|
impl Component for ItemDrops {
|
||||||
type Storage = DenseVecStorage<Self>;
|
type Storage = DenseVecStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -995,13 +995,19 @@ impl Default for InventoryUpdateEvent {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct InventoryUpdate {
|
pub struct InventoryUpdate {
|
||||||
event: InventoryUpdateEvent,
|
events: Vec<InventoryUpdateEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InventoryUpdate {
|
impl InventoryUpdate {
|
||||||
pub fn new(event: InventoryUpdateEvent) -> Self { Self { event } }
|
pub fn new(event: InventoryUpdateEvent) -> Self {
|
||||||
|
Self {
|
||||||
|
events: vec![event],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn event(&self) -> InventoryUpdateEvent { self.event.clone() }
|
pub fn push(&mut self, event: InventoryUpdateEvent) { self.events.push(event); }
|
||||||
|
|
||||||
|
pub fn take_events(&mut self) -> Vec<InventoryUpdateEvent> { std::mem::take(&mut self.events) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for InventoryUpdate {
|
impl Component for InventoryUpdate {
|
||||||
|
@ -362,74 +362,84 @@ impl From<Vec<(f32, LootSpec<String>)>> for ProbabilityFile {
|
|||||||
} else {
|
} else {
|
||||||
1.0 / content.iter().map(|e| e.0).sum::<f32>()
|
1.0 / content.iter().map(|e| e.0).sum::<f32>()
|
||||||
};
|
};
|
||||||
|
fn get_content(
|
||||||
|
rescale: f32,
|
||||||
|
p0: f32,
|
||||||
|
loot: LootSpec<String>,
|
||||||
|
) -> Vec<(f32, ItemDefinitionIdOwned, f32)> {
|
||||||
|
match loot {
|
||||||
|
LootSpec::Item(asset) => {
|
||||||
|
vec![(p0 * rescale, ItemDefinitionIdOwned::Simple(asset), 1.0)]
|
||||||
|
},
|
||||||
|
LootSpec::LootTable(table_asset) => {
|
||||||
|
let unscaled = &ProbabilityFile::load_expect(&table_asset).read().content;
|
||||||
|
let scale = p0 * rescale;
|
||||||
|
unscaled
|
||||||
|
.iter()
|
||||||
|
.map(|(p1, asset, amount)| (*p1 * scale, asset.clone(), *amount))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
},
|
||||||
|
LootSpec::ModularWeapon {
|
||||||
|
tool,
|
||||||
|
material,
|
||||||
|
hands,
|
||||||
|
} => {
|
||||||
|
let mut primary = expand_primary_component(tool, material, hands);
|
||||||
|
let secondary: Vec<ItemDefinitionIdOwned> =
|
||||||
|
expand_secondary_component(tool, material, hands).collect();
|
||||||
|
let freq = if primary.is_empty() || secondary.is_empty() {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
p0 * rescale / ((primary.len() * secondary.len()) as f32)
|
||||||
|
};
|
||||||
|
let res: Vec<(f32, ItemDefinitionIdOwned, f32)> = primary
|
||||||
|
.drain(0..)
|
||||||
|
.flat_map(|p| {
|
||||||
|
secondary.iter().map(move |s| {
|
||||||
|
let components = vec![p.clone(), s.clone()];
|
||||||
|
(
|
||||||
|
freq,
|
||||||
|
ItemDefinitionIdOwned::Modular {
|
||||||
|
pseudo_base: ModularBase::Tool.pseudo_item_id().into(),
|
||||||
|
components,
|
||||||
|
},
|
||||||
|
1.0f32,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
res
|
||||||
|
},
|
||||||
|
LootSpec::ModularWeaponPrimaryComponent {
|
||||||
|
tool,
|
||||||
|
material,
|
||||||
|
hands,
|
||||||
|
} => {
|
||||||
|
let mut res = expand_primary_component(tool, material, hands);
|
||||||
|
let freq = if res.is_empty() {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
p0 * rescale / (res.len() as f32)
|
||||||
|
};
|
||||||
|
let res: Vec<(f32, ItemDefinitionIdOwned, f32)> =
|
||||||
|
res.drain(0..).map(|e| (freq, e, 1.0f32)).collect();
|
||||||
|
res
|
||||||
|
},
|
||||||
|
LootSpec::Nothing => Vec::new(),
|
||||||
|
LootSpec::MultiDrop(loot, a, b) => {
|
||||||
|
let average_count = (a + b) as f32 * 0.5;
|
||||||
|
let mut content = get_content(rescale, p0, *loot);
|
||||||
|
for (_, _, count) in content.iter_mut() {
|
||||||
|
*count *= average_count;
|
||||||
|
}
|
||||||
|
content
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Self {
|
Self {
|
||||||
content: content
|
content: content
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|(p0, loot)| match loot {
|
.flat_map(|(p0, loot)| get_content(rescale, p0, loot))
|
||||||
LootSpec::Item(asset) => {
|
|
||||||
vec![(p0 * rescale, ItemDefinitionIdOwned::Simple(asset), 1.0)]
|
|
||||||
},
|
|
||||||
LootSpec::ItemQuantity(asset, a, b) => vec![(
|
|
||||||
p0 * rescale,
|
|
||||||
ItemDefinitionIdOwned::Simple(asset),
|
|
||||||
(a + b) as f32 * 0.5,
|
|
||||||
)],
|
|
||||||
LootSpec::LootTable(table_asset) => {
|
|
||||||
let unscaled = &Self::load_expect(&table_asset).read().content;
|
|
||||||
let scale = p0 * rescale;
|
|
||||||
unscaled
|
|
||||||
.iter()
|
|
||||||
.map(|(p1, asset, amount)| (*p1 * scale, asset.clone(), *amount))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
},
|
|
||||||
LootSpec::ModularWeapon {
|
|
||||||
tool,
|
|
||||||
material,
|
|
||||||
hands,
|
|
||||||
} => {
|
|
||||||
let mut primary = expand_primary_component(tool, material, hands);
|
|
||||||
let secondary: Vec<ItemDefinitionIdOwned> =
|
|
||||||
expand_secondary_component(tool, material, hands).collect();
|
|
||||||
let freq = if primary.is_empty() || secondary.is_empty() {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
p0 * rescale / ((primary.len() * secondary.len()) as f32)
|
|
||||||
};
|
|
||||||
let res: Vec<(f32, ItemDefinitionIdOwned, f32)> = primary
|
|
||||||
.drain(0..)
|
|
||||||
.flat_map(|p| {
|
|
||||||
secondary.iter().map(move |s| {
|
|
||||||
let components = vec![p.clone(), s.clone()];
|
|
||||||
(
|
|
||||||
freq,
|
|
||||||
ItemDefinitionIdOwned::Modular {
|
|
||||||
pseudo_base: ModularBase::Tool.pseudo_item_id().into(),
|
|
||||||
components,
|
|
||||||
},
|
|
||||||
1.0f32,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
res
|
|
||||||
},
|
|
||||||
LootSpec::ModularWeaponPrimaryComponent {
|
|
||||||
tool,
|
|
||||||
material,
|
|
||||||
hands,
|
|
||||||
} => {
|
|
||||||
let mut res = expand_primary_component(tool, material, hands);
|
|
||||||
let freq = if res.is_empty() {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
p0 * rescale / (res.len() as f32)
|
|
||||||
};
|
|
||||||
let res: Vec<(f32, ItemDefinitionIdOwned, f32)> =
|
|
||||||
res.drain(0..).map(|e| (freq, e, 1.0f32)).collect();
|
|
||||||
res
|
|
||||||
},
|
|
||||||
LootSpec::Nothing => Vec::new(),
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1231,12 +1241,4 @@ mod tests {
|
|||||||
let probability: ProbabilityFile = loot_table.into();
|
let probability: ProbabilityFile = loot_table.into();
|
||||||
assert!(normalized(&probability));
|
assert!(normalized(&probability));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_normalizing_table4() {
|
|
||||||
let quantity = |asset: &str, a, b| LootSpec::ItemQuantity(asset.to_owned(), a, b);
|
|
||||||
let loot_table = vec![(1.0, quantity("such", 3, 5)), (1.0, quantity("much", 5, 9))];
|
|
||||||
let probability: ProbabilityFile = loot_table.into();
|
|
||||||
assert!(normalized(&probability));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ impl Component for LootOwner {
|
|||||||
type Storage = DerefFlaggedStorage<Self, specs::DenseVecStorage<Self>>;
|
type Storage = DerefFlaggedStorage<Self, specs::DenseVecStorage<Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum LootOwnerKind {
|
pub enum LootOwnerKind {
|
||||||
Player(Uid),
|
Player(Uid),
|
||||||
Group(Group),
|
Group(Group),
|
||||||
|
@ -92,7 +92,7 @@ pub use self::{
|
|||||||
self,
|
self,
|
||||||
item_key::ItemKey,
|
item_key::ItemKey,
|
||||||
tool::{self, AbilityItem},
|
tool::{self, AbilityItem},
|
||||||
Item, ItemConfig, ItemDrop,
|
Item, ItemConfig, ItemDrops,
|
||||||
},
|
},
|
||||||
slot, CollectFailedReason, Inventory, InventoryUpdate, InventoryUpdateEvent,
|
slot, CollectFailedReason, Inventory, InventoryUpdate, InventoryUpdateEvent,
|
||||||
},
|
},
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
// Cheese drop rate = 3/X = 29.6%
|
// Cheese drop rate = 3/X = 29.6%
|
||||||
// Coconut drop rate = 1/X = 9.85%
|
// Coconut drop rate = 1/X = 9.85%
|
||||||
|
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
assets::{self, AssetExt},
|
assets::{self, AssetExt},
|
||||||
comp::{inventory::item, Item},
|
comp::{inventory::item, Item},
|
||||||
@ -76,12 +78,136 @@ impl<T> Lottery<T> {
|
|||||||
pub fn total(&self) -> f32 { self.total }
|
pub fn total(&self) -> f32 { self.total }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to distribute stacked items fairly between weighted participants.
|
||||||
|
pub fn distribute_many<T: Copy + Eq + Hash, I>(
|
||||||
|
participants: impl IntoIterator<Item = (f32, T)>,
|
||||||
|
rng: &mut impl Rng,
|
||||||
|
items: &[I],
|
||||||
|
mut get_amount: impl FnMut(&I) -> u32,
|
||||||
|
mut exec_item: impl FnMut(&I, T, u32),
|
||||||
|
) {
|
||||||
|
struct Participant<T> {
|
||||||
|
// weight / total
|
||||||
|
weight: f32,
|
||||||
|
sorted_weight: f32,
|
||||||
|
data: T,
|
||||||
|
recieved_count: u32,
|
||||||
|
current_recieved_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Participant<T> {
|
||||||
|
fn give(&mut self, amount: u32) {
|
||||||
|
self.current_recieved_count += amount;
|
||||||
|
self.recieved_count += amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing to distribute, we can return early.
|
||||||
|
if items.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut total_weight = 0.0;
|
||||||
|
|
||||||
|
let mut participants = participants
|
||||||
|
.into_iter()
|
||||||
|
.map(|(weight, participant)| Participant {
|
||||||
|
weight,
|
||||||
|
sorted_weight: {
|
||||||
|
total_weight += weight;
|
||||||
|
total_weight - weight
|
||||||
|
},
|
||||||
|
data: participant,
|
||||||
|
recieved_count: 0,
|
||||||
|
current_recieved_count: 0,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let total_item_amount = items.iter().map(&mut get_amount).sum::<u32>();
|
||||||
|
|
||||||
|
let mut current_total_weight = total_weight;
|
||||||
|
|
||||||
|
for item in items.iter() {
|
||||||
|
let amount = get_amount(item);
|
||||||
|
let mut distributed = 0;
|
||||||
|
|
||||||
|
let Some(mut give) = participants
|
||||||
|
.iter()
|
||||||
|
.map(|participant| (total_item_amount as f32 * participant.weight / total_weight).ceil() as u32 - participant.recieved_count)
|
||||||
|
.min() else {
|
||||||
|
tracing::error!("Tried to distribute items to no participants.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
while distributed < amount {
|
||||||
|
// Can't give more than amount, and don't give more than the average between all
|
||||||
|
// to keep things well distributed.
|
||||||
|
let max_give = (amount / participants.len() as u32).clamp(1, amount - distributed);
|
||||||
|
give = give.clamp(1, max_give);
|
||||||
|
let x = rng.gen_range(0.0..=current_total_weight);
|
||||||
|
|
||||||
|
let index = participants
|
||||||
|
.binary_search_by(|item| item.sorted_weight.partial_cmp(&x).unwrap())
|
||||||
|
.unwrap_or_else(|i| i.saturating_sub(1));
|
||||||
|
|
||||||
|
let participant_count = participants.len();
|
||||||
|
|
||||||
|
let Some(winner) = participants
|
||||||
|
.get_mut(index) else {
|
||||||
|
tracing::error!("Tried to distribute items to no participants.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
winner.give(give);
|
||||||
|
distributed += give;
|
||||||
|
|
||||||
|
// If a participant has received enough, remove it.
|
||||||
|
if participant_count > 1
|
||||||
|
&& winner.recieved_count as f32 / total_item_amount as f32
|
||||||
|
>= winner.weight / total_weight
|
||||||
|
{
|
||||||
|
current_total_weight = index
|
||||||
|
.checked_sub(1)
|
||||||
|
.and_then(|i| Some(participants.get(i)?.sorted_weight))
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
let winner = participants.swap_remove(index);
|
||||||
|
exec_item(item, winner.data, winner.current_recieved_count);
|
||||||
|
|
||||||
|
// Keep participant weights correct so that we can binary search it.
|
||||||
|
for participant in &mut participants[index..] {
|
||||||
|
current_total_weight += participant.weight;
|
||||||
|
participant.sorted_weight = current_total_weight - participant.weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update max item give amount.
|
||||||
|
give = participants
|
||||||
|
.iter()
|
||||||
|
.map(|participant| {
|
||||||
|
(total_item_amount as f32 * participant.weight / total_weight).ceil() as u32
|
||||||
|
- participant.recieved_count
|
||||||
|
})
|
||||||
|
.min()
|
||||||
|
.unwrap_or(0);
|
||||||
|
} else {
|
||||||
|
give = give.min(
|
||||||
|
(total_item_amount as f32 * winner.weight / total_weight).ceil() as u32
|
||||||
|
- winner.recieved_count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for participant in participants.iter_mut() {
|
||||||
|
if participant.current_recieved_count != 0 {
|
||||||
|
exec_item(item, participant.data, participant.current_recieved_count);
|
||||||
|
participant.current_recieved_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub enum LootSpec<T: AsRef<str>> {
|
pub enum LootSpec<T: AsRef<str>> {
|
||||||
/// Asset specifier
|
/// Asset specifier
|
||||||
Item(T),
|
Item(T),
|
||||||
/// Asset specifier, lower range, upper range
|
|
||||||
ItemQuantity(T, u32, u32),
|
|
||||||
/// Loot table
|
/// Loot table
|
||||||
LootTable(T),
|
LootTable(T),
|
||||||
/// No loot given
|
/// No loot given
|
||||||
@ -97,78 +223,129 @@ pub enum LootSpec<T: AsRef<str>> {
|
|||||||
material: item::Material,
|
material: item::Material,
|
||||||
hands: Option<item::tool::Hands>,
|
hands: Option<item::tool::Hands>,
|
||||||
},
|
},
|
||||||
|
/// LootSpec, lower range, upper range
|
||||||
|
MultiDrop(Box<LootSpec<T>>, u32, u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<str>> LootSpec<T> {
|
impl<T: AsRef<str>> LootSpec<T> {
|
||||||
pub fn to_item(&self) -> Option<Item> {
|
fn to_items_inner(
|
||||||
let mut rng = thread_rng();
|
&self,
|
||||||
match self {
|
rng: &mut rand::rngs::ThreadRng,
|
||||||
Self::Item(item) => Item::new_from_asset(item.as_ref()).map_or_else(
|
amount: u32,
|
||||||
|
items: &mut Vec<(u32, Item)>,
|
||||||
|
) {
|
||||||
|
let convert_item = |item: &T| {
|
||||||
|
Item::new_from_asset(item.as_ref()).map_or_else(
|
||||||
|e| {
|
|e| {
|
||||||
warn!(?e, "error while loading item: {}", item.as_ref());
|
warn!(?e, "error while loading item: {}", item.as_ref());
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
Some,
|
Some,
|
||||||
),
|
)
|
||||||
Self::ItemQuantity(item, lower, upper) => {
|
};
|
||||||
let range = *lower..=*upper;
|
let mut push_item = |mut item: Item, count: u32| {
|
||||||
let quantity = thread_rng().gen_range(range);
|
let count = item.amount().saturating_mul(count);
|
||||||
match Item::new_from_asset(item.as_ref()) {
|
item.set_amount(1).expect("1 is always a valid amount.");
|
||||||
Ok(mut item) => {
|
let hash = item.item_hash();
|
||||||
// TODO: Handle multiple of an item that is unstackable
|
match items.binary_search_by_key(&hash, |(_, item)| item.item_hash()) {
|
||||||
if item.set_amount(quantity).is_err() {
|
Ok(i) => {
|
||||||
warn!("Tried to set quantity on non stackable item");
|
// Since item hash can collide with other items, we search nearby items with the
|
||||||
}
|
// same hash.
|
||||||
Some(item)
|
// NOTE: The `ParitalEq` implementation for `Item` doesn't compare some data
|
||||||
},
|
// like durability, or wether slots contain anything. Although since these are
|
||||||
Err(e) => {
|
// Newly loaded items we don't care about comparing those for deduplication
|
||||||
warn!(?e, "error while loading item: {}", item.as_ref());
|
// here.
|
||||||
None
|
let has_same_hash = |i: &usize| items[*i].1.item_hash() == hash;
|
||||||
},
|
if let Some(i) = (i..items.len())
|
||||||
|
.take_while(has_same_hash)
|
||||||
|
.chain((0..i).rev().take_while(has_same_hash))
|
||||||
|
.find(|i| items[*i].1 == item)
|
||||||
|
{
|
||||||
|
// We saturate at 4 billion items, could use u64 instead if this isn't
|
||||||
|
// desirable.
|
||||||
|
items[i].0 = items[i].0.saturating_add(count);
|
||||||
|
} else {
|
||||||
|
items.insert(i, (count, item));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(i) => items.insert(i, (count, item)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::Item(item) => {
|
||||||
|
if let Some(item) = convert_item(item) {
|
||||||
|
push_item(item, amount);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Self::LootTable(table) => Lottery::<LootSpec<String>>::load_expect(table.as_ref())
|
Self::LootTable(table) => {
|
||||||
.read()
|
let loot_spec = Lottery::<LootSpec<String>>::load_expect(table.as_ref()).read();
|
||||||
.choose()
|
for _ in 0..amount {
|
||||||
.to_item(),
|
loot_spec.choose().to_items_inner(rng, 1, items)
|
||||||
Self::Nothing => None,
|
}
|
||||||
|
},
|
||||||
|
Self::Nothing => {},
|
||||||
Self::ModularWeapon {
|
Self::ModularWeapon {
|
||||||
tool,
|
tool,
|
||||||
material,
|
material,
|
||||||
hands,
|
hands,
|
||||||
} => item::modular::random_weapon(*tool, *material, *hands, &mut rng).map_or_else(
|
} => {
|
||||||
|e| {
|
for _ in 0..amount {
|
||||||
warn!(
|
match item::modular::random_weapon(*tool, *material, *hands, rng) {
|
||||||
?e,
|
Ok(item) => push_item(item, 1),
|
||||||
"error while creating modular weapon. Toolkind: {:?}, Material: {:?}, \
|
Err(e) => {
|
||||||
Hands: {:?}",
|
warn!(
|
||||||
tool,
|
?e,
|
||||||
material,
|
"error while creating modular weapon. Toolkind: {:?}, Material: \
|
||||||
hands,
|
{:?}, Hands: {:?}",
|
||||||
);
|
tool,
|
||||||
None
|
material,
|
||||||
},
|
hands,
|
||||||
Some,
|
);
|
||||||
),
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
Self::ModularWeaponPrimaryComponent {
|
Self::ModularWeaponPrimaryComponent {
|
||||||
tool,
|
tool,
|
||||||
material,
|
material,
|
||||||
hands,
|
hands,
|
||||||
} => item::modular::random_weapon_primary_component(*tool, *material, *hands, &mut rng)
|
} => {
|
||||||
.map_or_else(
|
for _ in 0..amount {
|
||||||
|e| {
|
match item::modular::random_weapon(*tool, *material, *hands, rng) {
|
||||||
warn!(
|
Ok(item) => push_item(item, 1),
|
||||||
?e,
|
Err(e) => {
|
||||||
"error while creating modular weapon primary component. Toolkind: \
|
warn!(
|
||||||
{:?}, Material: {:?}, Hands: {:?}",
|
?e,
|
||||||
tool,
|
"error while creating modular weapon primary component. Toolkind: \
|
||||||
material,
|
{:?}, Material: {:?}, Hands: {:?}",
|
||||||
hands,
|
tool,
|
||||||
);
|
material,
|
||||||
None
|
hands,
|
||||||
},
|
);
|
||||||
|(comp, _)| Some(comp),
|
},
|
||||||
),
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::MultiDrop(loot_spec, lower, upper) => {
|
||||||
|
let sub_amount = rng.gen_range(*lower..=*upper);
|
||||||
|
// We saturate at 4 billion items, could use u64 instead if this isn't
|
||||||
|
// desirable.
|
||||||
|
loot_spec.to_items_inner(rng, sub_amount.saturating_mul(amount), items);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_items(&self) -> Option<Vec<(u32, Item)>> {
|
||||||
|
let mut items = Vec::new();
|
||||||
|
self.to_items_inner(&mut thread_rng(), 1, &mut items);
|
||||||
|
|
||||||
|
if !items.is_empty() {
|
||||||
|
items.sort_unstable_by_key(|(amount, _)| *amount);
|
||||||
|
|
||||||
|
Some(items)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,21 +366,6 @@ pub mod tests {
|
|||||||
LootSpec::Item(item) => {
|
LootSpec::Item(item) => {
|
||||||
Item::new_from_asset_expect(item);
|
Item::new_from_asset_expect(item);
|
||||||
},
|
},
|
||||||
LootSpec::ItemQuantity(item, lower, upper) => {
|
|
||||||
assert!(
|
|
||||||
*lower > 0,
|
|
||||||
"Lower quantity must be more than 0. It is {}.",
|
|
||||||
lower
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
upper >= lower,
|
|
||||||
"Upper quantity must be at least the value of lower quantity. Upper value: \
|
|
||||||
{}, low value: {}.",
|
|
||||||
upper,
|
|
||||||
lower
|
|
||||||
);
|
|
||||||
Item::new_from_asset_expect(item);
|
|
||||||
},
|
|
||||||
LootSpec::LootTable(loot_table) => {
|
LootSpec::LootTable(loot_table) => {
|
||||||
let loot_table = Lottery::<LootSpec<String>>::load_expect_cloned(loot_table);
|
let loot_table = Lottery::<LootSpec<String>>::load_expect_cloned(loot_table);
|
||||||
validate_table_contents(loot_table);
|
validate_table_contents(loot_table);
|
||||||
@ -236,6 +398,21 @@ pub mod tests {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
LootSpec::MultiDrop(loot_spec, lower, upper) => {
|
||||||
|
assert!(
|
||||||
|
*lower > 0,
|
||||||
|
"Lower quantity must be more than 0. It is {}.",
|
||||||
|
lower
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
upper >= lower,
|
||||||
|
"Upper quantity must be at least the value of lower quantity. Upper value: \
|
||||||
|
{}, low value: {}.",
|
||||||
|
upper,
|
||||||
|
lower
|
||||||
|
);
|
||||||
|
validate_loot_spec(loot_spec);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,4 +430,24 @@ pub mod tests {
|
|||||||
validate_table_contents(loot_table.clone());
|
validate_table_contents(loot_table.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_distribute_many() {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
|
// Known successful case
|
||||||
|
for _ in 0..10 {
|
||||||
|
distribute_many(
|
||||||
|
vec![(0.4f32, "a"), (0.4, "b"), (0.2, "c")],
|
||||||
|
&mut rng,
|
||||||
|
&[("item", 10)],
|
||||||
|
|(_, m)| *m,
|
||||||
|
|_item, winner, count| match winner {
|
||||||
|
"a" | "b" => assert_eq!(count, 4),
|
||||||
|
"c" => assert_eq!(count, 2),
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,7 +253,7 @@ impl State {
|
|||||||
ecs.register::<comp::MapMarker>();
|
ecs.register::<comp::MapMarker>();
|
||||||
ecs.register::<comp::Projectile>();
|
ecs.register::<comp::Projectile>();
|
||||||
ecs.register::<comp::Melee>();
|
ecs.register::<comp::Melee>();
|
||||||
ecs.register::<comp::ItemDrop>();
|
ecs.register::<comp::ItemDrops>();
|
||||||
ecs.register::<comp::ChatMode>();
|
ecs.register::<comp::ChatMode>();
|
||||||
ecs.register::<comp::Faction>();
|
ecs.register::<comp::Faction>();
|
||||||
ecs.register::<comp::invite::Invite>();
|
ecs.register::<comp::invite::Invite>();
|
||||||
|
@ -577,12 +577,20 @@ fn handle_give_item(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
insert_or_replace_component(
|
let mut inventory_update = server
|
||||||
server,
|
.state
|
||||||
target,
|
.ecs_mut()
|
||||||
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Given),
|
.write_storage::<comp::InventoryUpdate>();
|
||||||
"target",
|
if let Some(update) = inventory_update.get_mut(target) {
|
||||||
)?;
|
update.push(comp::InventoryUpdateEvent::Given);
|
||||||
|
} else {
|
||||||
|
inventory_update
|
||||||
|
.insert(
|
||||||
|
target,
|
||||||
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Given),
|
||||||
|
)
|
||||||
|
.map_err(|_| "Entity target is dead!")?;
|
||||||
|
}
|
||||||
res
|
res
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Invalid item: {}", item_name))
|
Err(format!("Invalid item: {}", item_name))
|
||||||
@ -686,8 +694,8 @@ fn handle_make_npc(
|
|||||||
entity_builder = entity_builder.with(agent);
|
entity_builder = entity_builder.with(agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(drop_item) = loot.to_item() {
|
if let Some(drop_items) = loot.to_items() {
|
||||||
entity_builder = entity_builder.with(comp::ItemDrop(drop_item));
|
entity_builder = entity_builder.with(comp::ItemDrops(drop_items));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some would say it's a hack, some would say it's incomplete
|
// Some would say it's a hack, some would say it's incomplete
|
||||||
|
@ -9,7 +9,7 @@ use common::{
|
|||||||
aura::{Aura, AuraKind, AuraTarget},
|
aura::{Aura, AuraKind, AuraTarget},
|
||||||
beam,
|
beam,
|
||||||
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
|
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
|
||||||
shockwave, Alignment, BehaviorCapability, Body, ItemDrop, LightEmitter, Object, Ori, Pos,
|
shockwave, Alignment, BehaviorCapability, Body, ItemDrops, LightEmitter, Object, Ori, Pos,
|
||||||
Projectile, TradingBehavior, Vel, WaypointArea,
|
Projectile, TradingBehavior, Vel, WaypointArea,
|
||||||
},
|
},
|
||||||
event::{EventBus, NpcBuilder, UpdateCharacterMetadata},
|
event::{EventBus, NpcBuilder, UpdateCharacterMetadata},
|
||||||
@ -118,8 +118,8 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) ->
|
|||||||
entity
|
entity
|
||||||
};
|
};
|
||||||
|
|
||||||
let entity = if let Some(drop_item) = npc.loot.to_item() {
|
let entity = if let Some(drop_items) = npc.loot.to_items() {
|
||||||
entity.with(ItemDrop(drop_item))
|
entity.with(ItemDrops(drop_items))
|
||||||
} else {
|
} else {
|
||||||
entity
|
entity
|
||||||
};
|
};
|
||||||
|
@ -19,13 +19,16 @@ use common::{
|
|||||||
self, aura, buff,
|
self, aura, buff,
|
||||||
chat::{KillSource, KillType},
|
chat::{KillSource, KillType},
|
||||||
inventory::item::{AbilityMap, MaterialStatManifest},
|
inventory::item::{AbilityMap, MaterialStatManifest},
|
||||||
|
item::flatten_counted_items,
|
||||||
loot_owner::LootOwnerKind,
|
loot_owner::LootOwnerKind,
|
||||||
Alignment, Auras, Body, CharacterState, Energy, Group, Health, HealthChange, Inventory,
|
Alignment, Auras, Body, CharacterState, Energy, Group, Health, HealthChange, Inventory,
|
||||||
Player, Poise, Pos, SkillSet, Stats,
|
Player, Poise, Pos, SkillSet, Stats,
|
||||||
},
|
},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
|
lottery::distribute_many,
|
||||||
outcome::{HealthChangeInfo, Outcome},
|
outcome::{HealthChangeInfo, Outcome},
|
||||||
resources::{Secs, Time},
|
resources::{Secs, Time},
|
||||||
|
spiral::Spiral2d,
|
||||||
states::utils::StageSection,
|
states::utils::StageSection,
|
||||||
terrain::{Block, BlockKind, TerrainGrid},
|
terrain::{Block, BlockKind, TerrainGrid},
|
||||||
trade::{TradeResult, Trades},
|
trade::{TradeResult, Trades},
|
||||||
@ -37,8 +40,7 @@ use common::{
|
|||||||
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
|
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
|
||||||
use common_state::BlockChange;
|
use common_state::BlockChange;
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use rand::{distributions::WeightedIndex, Rng};
|
use rand::Rng;
|
||||||
use rand_distr::Distribution;
|
|
||||||
use specs::{
|
use specs::{
|
||||||
join::Join, saveload::MarkerAllocator, Builder, Entity as EcsEntity, Entity, WorldExt,
|
join::Join, saveload::MarkerAllocator, Builder, Entity as EcsEntity, Entity, WorldExt,
|
||||||
};
|
};
|
||||||
@ -427,72 +429,88 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
|
|||||||
|
|
||||||
// Decide for a loot drop before turning into a lootbag
|
// Decide for a loot drop before turning into a lootbag
|
||||||
|
|
||||||
let item = {
|
let items = {
|
||||||
let mut item_drop = state.ecs().write_storage::<comp::ItemDrop>();
|
let mut item_drops = state.ecs().write_storage::<comp::ItemDrops>();
|
||||||
item_drop.remove(entity).map(|comp::ItemDrop(item)| item)
|
item_drops.remove(entity).map(|comp::ItemDrops(item)| item)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(item) = item {
|
if let Some(items) = items {
|
||||||
let pos = state.ecs().read_storage::<Pos>().get(entity).cloned();
|
let pos = state.ecs().read_storage::<Pos>().get(entity).cloned();
|
||||||
let vel = state.ecs().read_storage::<comp::Vel>().get(entity).cloned();
|
let vel = state.ecs().read_storage::<comp::Vel>().get(entity).cloned();
|
||||||
if let Some(pos) = pos {
|
if let Some(pos) = pos {
|
||||||
// Remove entries where zero exp was awarded - this happens because some
|
// Remove entries where zero exp was awarded - this happens because some
|
||||||
// entities like Object bodies don't give EXP.
|
// entities like Object bodies don't give EXP.
|
||||||
let _ = exp_awards.drain_filter(|(_, exp, _)| *exp < f32::EPSILON);
|
let mut item_receivers = HashMap::new();
|
||||||
|
for (entity, exp, group) in exp_awards {
|
||||||
|
if exp >= f32::EPSILON {
|
||||||
|
let loot_owner = if let Some(group) = group {
|
||||||
|
Some(LootOwnerKind::Group(group))
|
||||||
|
} else {
|
||||||
|
let uid = state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<Body>()
|
||||||
|
.get(entity)
|
||||||
|
.and_then(|body| {
|
||||||
|
// Only humanoids are awarded loot ownership - if the winner
|
||||||
|
// was a
|
||||||
|
// non-humanoid NPC the loot will be free-for-all
|
||||||
|
if matches!(body, Body::Humanoid(_)) {
|
||||||
|
Some(state.ecs().read_storage::<Uid>().get(entity).cloned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
|
||||||
let winner = if exp_awards.is_empty() {
|
uid.map(LootOwnerKind::Player)
|
||||||
None
|
};
|
||||||
} else {
|
|
||||||
// Use the awarded exp per entity as the weight distribution for drop chance
|
|
||||||
// Creating the WeightedIndex can only fail if there are weights <= 0 or no
|
|
||||||
// weights, which shouldn't ever happen
|
|
||||||
let dist = WeightedIndex::new(exp_awards.iter().map(|x| x.1))
|
|
||||||
.expect("Failed to create WeightedIndex for loot drop chance");
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let winner = exp_awards
|
|
||||||
.get(dist.sample(&mut rng))
|
|
||||||
.expect("Loot distribution failed to find a winner");
|
|
||||||
let (winner, group) = (winner.0, winner.2);
|
|
||||||
|
|
||||||
if let Some(group) = group {
|
*item_receivers.entry(loot_owner).or_insert(0.0) += exp;
|
||||||
Some(LootOwnerKind::Group(group))
|
|
||||||
} else {
|
|
||||||
let uid = state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<Body>()
|
|
||||||
.get(winner)
|
|
||||||
.and_then(|body| {
|
|
||||||
// Only humanoids are awarded loot ownership - if the winner
|
|
||||||
// was a
|
|
||||||
// non-humanoid NPC the loot will be free-for-all
|
|
||||||
if matches!(body, Body::Humanoid(_)) {
|
|
||||||
Some(state.ecs().read_storage::<Uid>().get(winner).cloned())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
uid.map(LootOwnerKind::Player)
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let item_drop_entity = state
|
if !item_receivers.is_empty() {
|
||||||
.create_item_drop(Pos(pos.0 + Vec3::unit_z() * 0.25), item)
|
let msm = &MaterialStatManifest::load().read();
|
||||||
.maybe_with(vel)
|
let ability_map = &AbilityMap::load().read();
|
||||||
.build();
|
let mut item_offset_spiral = Spiral2d::new();
|
||||||
|
|
||||||
// If there was a loot winner, assign them as the owner of the loot. There will
|
let mut rng = rand::thread_rng();
|
||||||
// not be a loot winner when an entity dies to environment damage and such so
|
distribute_many(
|
||||||
// the loot will be free-for-all.
|
item_receivers
|
||||||
if let Some(uid) = winner {
|
.iter()
|
||||||
debug!("Assigned UID {:?} as the winner for the loot drop", uid);
|
.map(|(loot_owner, weight)| (*weight, *loot_owner)),
|
||||||
|
&mut rng,
|
||||||
state
|
&items,
|
||||||
.ecs()
|
|(amount, _)| *amount,
|
||||||
.write_storage::<LootOwner>()
|
|(_, item), loot_owner, count| {
|
||||||
.insert(item_drop_entity, LootOwner::new(uid))
|
for item in item.stacked_duplicates(ability_map, msm, count) {
|
||||||
.unwrap();
|
let offset = item_offset_spiral
|
||||||
|
.next()
|
||||||
|
.map(|offset| offset.as_::<f32>() * 0.25)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let item_drop_entity = state
|
||||||
|
.create_item_drop(
|
||||||
|
Pos(pos.0 + Vec3::unit_z() * 0.25 + offset),
|
||||||
|
item,
|
||||||
|
)
|
||||||
|
.maybe_with(vel)
|
||||||
|
.build();
|
||||||
|
if let Some(loot_owner) = loot_owner {
|
||||||
|
debug!(
|
||||||
|
"Assigned UID {loot_owner:?} as the winner for the loot \
|
||||||
|
drop"
|
||||||
|
);
|
||||||
|
if let Err(err) = state
|
||||||
|
.ecs()
|
||||||
|
.write_storage::<LootOwner>()
|
||||||
|
.insert(item_drop_entity, LootOwner::new(loot_owner))
|
||||||
|
{
|
||||||
|
error!("Failed to set loot owner on item drop: {err}");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error!(
|
error!(
|
||||||
@ -1080,24 +1098,28 @@ pub fn handle_bonk(server: &mut Server, pos: Vec3<f32>, owner: Option<Uid>, targ
|
|||||||
{
|
{
|
||||||
drop(terrain);
|
drop(terrain);
|
||||||
drop(block_change);
|
drop(block_change);
|
||||||
if let Some(item) = comp::Item::try_reclaim_from_block(block) {
|
if let Some(items) = comp::Item::try_reclaim_from_block(block) {
|
||||||
server
|
let msm = &MaterialStatManifest::load().read();
|
||||||
.state
|
let ability_map = &AbilityMap::load().read();
|
||||||
.create_object(Default::default(), match block.get_sprite() {
|
for item in flatten_counted_items(&items, ability_map, msm) {
|
||||||
// Create different containers depending on the original sprite
|
server
|
||||||
Some(SpriteKind::Apple) => comp::object::Body::Apple,
|
.state
|
||||||
Some(SpriteKind::Beehive) => comp::object::Body::Hive,
|
.create_object(Default::default(), match block.get_sprite() {
|
||||||
Some(SpriteKind::Coconut) => comp::object::Body::Coconut,
|
// Create different containers depending on the original sprite
|
||||||
Some(SpriteKind::Bomb) => comp::object::Body::Bomb,
|
Some(SpriteKind::Apple) => comp::object::Body::Apple,
|
||||||
_ => comp::object::Body::Pouch,
|
Some(SpriteKind::Beehive) => comp::object::Body::Hive,
|
||||||
})
|
Some(SpriteKind::Coconut) => comp::object::Body::Coconut,
|
||||||
.with(Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)))
|
Some(SpriteKind::Bomb) => comp::object::Body::Bomb,
|
||||||
.with(item)
|
_ => comp::object::Body::Pouch,
|
||||||
.maybe_with(match block.get_sprite() {
|
})
|
||||||
Some(SpriteKind::Bomb) => Some(comp::Object::Bomb { owner }),
|
.with(Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)))
|
||||||
_ => None,
|
.with(item)
|
||||||
})
|
.maybe_with(match block.get_sprite() {
|
||||||
.build();
|
Some(SpriteKind::Bomb) => Some(comp::Object::Bomb { owner }),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,10 @@ use common::{
|
|||||||
agent::{AgentEvent, Sound, SoundKind},
|
agent::{AgentEvent, Sound, SoundKind},
|
||||||
dialogue::Subject,
|
dialogue::Subject,
|
||||||
inventory::slot::EquipSlot,
|
inventory::slot::EquipSlot,
|
||||||
|
item::{flatten_counted_items, MaterialStatManifest},
|
||||||
loot_owner::LootOwnerKind,
|
loot_owner::LootOwnerKind,
|
||||||
pet::is_mountable,
|
pet::is_mountable,
|
||||||
tool::ToolKind,
|
tool::{AbilityMap, ToolKind},
|
||||||
Inventory, LootOwner, Pos, SkillGroupKind,
|
Inventory, LootOwner, Pos, SkillGroupKind,
|
||||||
},
|
},
|
||||||
consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME},
|
consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME},
|
||||||
@ -178,7 +179,10 @@ pub fn handle_mine_block(
|
|||||||
let block = state.terrain().get(pos).ok().copied();
|
let block = state.terrain().get(pos).ok().copied();
|
||||||
if let Some(block) = block.filter(|b| b.mine_tool().map_or(false, |t| Some(t) == tool)) {
|
if let Some(block) = block.filter(|b| b.mine_tool().map_or(false, |t| Some(t) == tool)) {
|
||||||
// Drop item if one is recoverable from the block
|
// Drop item if one is recoverable from the block
|
||||||
if let Some(mut item) = comp::Item::try_reclaim_from_block(block) {
|
if let Some(items) = comp::Item::try_reclaim_from_block(block) {
|
||||||
|
let msm = &MaterialStatManifest::load().read();
|
||||||
|
let ability_map = &AbilityMap::load().read();
|
||||||
|
let mut items: Vec<_> = flatten_counted_items(&items, ability_map, msm).collect();
|
||||||
let maybe_uid = state.ecs().uid_from_entity(entity);
|
let maybe_uid = state.ecs().uid_from_entity(entity);
|
||||||
|
|
||||||
if let Some(mut skillset) = state
|
if let Some(mut skillset) = state
|
||||||
@ -186,12 +190,17 @@ pub fn handle_mine_block(
|
|||||||
.write_storage::<comp::SkillSet>()
|
.write_storage::<comp::SkillSet>()
|
||||||
.get_mut(entity)
|
.get_mut(entity)
|
||||||
{
|
{
|
||||||
if let (Some(tool), Some(uid), Some(exp_reward)) = (
|
if let (Some(tool), Some(uid), exp_reward @ 1..) = (
|
||||||
tool,
|
tool,
|
||||||
maybe_uid,
|
maybe_uid,
|
||||||
item.item_definition_id()
|
items
|
||||||
.itemdef_id()
|
.iter()
|
||||||
.and_then(|id| RESOURCE_EXPERIENCE_MANIFEST.read().0.get(id).copied()),
|
.filter_map(|item| {
|
||||||
|
item.item_definition_id().itemdef_id().and_then(|id| {
|
||||||
|
RESOURCE_EXPERIENCE_MANIFEST.read().0.get(id).copied()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.sum(),
|
||||||
) {
|
) {
|
||||||
let skill_group = SkillGroupKind::Weapon(tool);
|
let skill_group = SkillGroupKind::Weapon(tool);
|
||||||
let outcome_bus = state.ecs().read_resource::<EventBus<Outcome>>();
|
let outcome_bus = state.ecs().read_resource::<EventBus<Outcome>>();
|
||||||
@ -230,26 +239,30 @@ pub fn handle_mine_block(
|
|||||||
|
|
||||||
rng.gen_bool(chance_mod * f64::from(skill_level))
|
rng.gen_bool(chance_mod * f64::from(skill_level))
|
||||||
};
|
};
|
||||||
|
for item in items.iter_mut() {
|
||||||
|
let double_gain =
|
||||||
|
item.item_definition_id().itemdef_id().map_or(false, |id| {
|
||||||
|
(id.contains("mineral.ore.") && need_double_ore(&mut rng))
|
||||||
|
|| (id.contains("mineral.gem.") && need_double_gem(&mut rng))
|
||||||
|
});
|
||||||
|
|
||||||
let double_gain = item.item_definition_id().itemdef_id().map_or(false, |id| {
|
if double_gain {
|
||||||
(id.contains("mineral.ore.") && need_double_ore(&mut rng))
|
// Ignore non-stackable errors
|
||||||
|| (id.contains("mineral.gem.") && need_double_gem(&mut rng))
|
let _ = item.increase_amount(1);
|
||||||
});
|
}
|
||||||
|
|
||||||
if double_gain {
|
|
||||||
// Ignore non-stackable errors
|
|
||||||
let _ = item.increase_amount(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let item_drop = state
|
for item in items {
|
||||||
.create_item_drop(Default::default(), item)
|
let item_drop = state
|
||||||
.with(Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)));
|
.create_item_drop(Default::default(), item)
|
||||||
if let Some(uid) = maybe_uid {
|
.with(Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)));
|
||||||
item_drop.with(LootOwner::new(LootOwnerKind::Player(uid)))
|
if let Some(uid) = maybe_uid {
|
||||||
} else {
|
item_drop.with(LootOwner::new(LootOwnerKind::Player(uid)))
|
||||||
item_drop
|
} else {
|
||||||
|
item_drop
|
||||||
|
}
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set_block(pos, block.into_vacant());
|
state.set_block(pos, block.into_vacant());
|
||||||
|
@ -8,8 +8,9 @@ use common::{
|
|||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
group::members,
|
group::members,
|
||||||
item::{self, tool::AbilityMap, MaterialStatManifest},
|
item::{self, flatten_counted_items, tool::AbilityMap, MaterialStatManifest},
|
||||||
slot::{self, Slot},
|
slot::{self, Slot},
|
||||||
|
InventoryUpdate,
|
||||||
},
|
},
|
||||||
consts::MAX_PICKUP_RANGE,
|
consts::MAX_PICKUP_RANGE,
|
||||||
recipe::{
|
recipe::{
|
||||||
@ -262,7 +263,12 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
let mut block_change = ecs.write_resource::<common_state::BlockChange>();
|
let mut block_change = ecs.write_resource::<common_state::BlockChange>();
|
||||||
|
|
||||||
let block = terrain.get(sprite_pos).ok().copied();
|
let block = terrain.get(sprite_pos).ok().copied();
|
||||||
let mut drop_item = None;
|
let mut drop_items = Vec::new();
|
||||||
|
let mut inventory_updates = ecs.write_storage();
|
||||||
|
let inventory_update = inventory_updates
|
||||||
|
.entry(entity)
|
||||||
|
.expect("We know entity exists since we got its inventory.")
|
||||||
|
.or_insert_with(InventoryUpdate::default);
|
||||||
|
|
||||||
if let Some(block) = block {
|
if let Some(block) = block {
|
||||||
if block.is_collectible() && block_change.can_set_block(sprite_pos) {
|
if block.is_collectible() && block_change.can_set_block(sprite_pos) {
|
||||||
@ -275,45 +281,37 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's an item to be reclaimed from the block, add it to the inventory
|
// If there are items to be reclaimed from the block, add it to the inventory
|
||||||
if let Some(item) = comp::Item::try_reclaim_from_block(block) {
|
if let Some(items) = comp::Item::try_reclaim_from_block(block) {
|
||||||
// NOTE: We dup the item for message purposes.
|
let msm = &MaterialStatManifest::load().read();
|
||||||
let item_msg = item.duplicate(
|
let ability_map = &AbilityMap::load().read();
|
||||||
&ecs.read_resource::<AbilityMap>(),
|
for item in flatten_counted_items(&items, ability_map, msm) {
|
||||||
&ecs.read_resource::<MaterialStatManifest>(),
|
// NOTE: We dup the item for message purposes.
|
||||||
);
|
let item_msg = item.duplicate(ability_map, msm);
|
||||||
let event = match inventory.push(item) {
|
match inventory.push(item) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
if let Some(group_id) = ecs.read_storage::<Group>().get(entity) {
|
if let Some(group_id) = ecs.read_storage::<Group>().get(entity)
|
||||||
announce_loot_to_group(
|
{
|
||||||
group_id,
|
announce_loot_to_group(
|
||||||
ecs,
|
group_id,
|
||||||
entity,
|
ecs,
|
||||||
item_msg.duplicate(
|
entity,
|
||||||
&ecs.read_resource::<AbilityMap>(),
|
item_msg.duplicate(
|
||||||
&ecs.read_resource::<MaterialStatManifest>(),
|
&ecs.read_resource::<AbilityMap>(),
|
||||||
),
|
&ecs.read_resource::<MaterialStatManifest>(),
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
comp::InventoryUpdate::new(InventoryUpdateEvent::Collected(
|
}
|
||||||
item_msg,
|
inventory_update
|
||||||
))
|
.push(InventoryUpdateEvent::Collected(item_msg));
|
||||||
},
|
},
|
||||||
// The item we created was in some sense "fake" so it's safe to
|
// The item we created was in some sense "fake" so it's safe to
|
||||||
// drop it.
|
// drop it.
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
drop_item = Some(item_msg);
|
drop_items.push(item_msg);
|
||||||
comp::InventoryUpdate::new(
|
},
|
||||||
InventoryUpdateEvent::BlockCollectFailed {
|
}
|
||||||
pos: sprite_pos,
|
}
|
||||||
reason: CollectFailedReason::InventoryFull,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
ecs.write_storage()
|
|
||||||
.insert(entity, event)
|
|
||||||
.expect("We know entity exists since we got its inventory.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We made sure earlier the block was not already modified this tick
|
// We made sure earlier the block was not already modified this tick
|
||||||
@ -367,10 +365,18 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !drop_items.is_empty() {
|
||||||
|
inventory_update.push(InventoryUpdateEvent::BlockCollectFailed {
|
||||||
|
pos: sprite_pos,
|
||||||
|
reason: CollectFailedReason::InventoryFull,
|
||||||
|
})
|
||||||
|
}
|
||||||
drop(inventories);
|
drop(inventories);
|
||||||
drop(terrain);
|
drop(terrain);
|
||||||
drop(block_change);
|
drop(block_change);
|
||||||
if let Some(item) = drop_item {
|
drop(inventory_updates);
|
||||||
|
|
||||||
|
for item in drop_items {
|
||||||
state
|
state
|
||||||
.create_item_drop(Default::default(), item)
|
.create_item_drop(Default::default(), item)
|
||||||
.with(comp::Pos(
|
.with(comp::Pos(
|
||||||
|
@ -356,10 +356,10 @@ impl<'a> System<'a> for Sys {
|
|||||||
// TODO: Sync clients that don't have a position?
|
// TODO: Sync clients that don't have a position?
|
||||||
|
|
||||||
// Sync inventories
|
// Sync inventories
|
||||||
for (inventory, update, client) in (inventories, &inventory_updates, &clients).join() {
|
for (inventory, update, client) in (inventories, &mut inventory_updates, &clients).join() {
|
||||||
client.send_fallible(ServerGeneral::InventoryUpdate(
|
client.send_fallible(ServerGeneral::InventoryUpdate(
|
||||||
inventory.clone(),
|
inventory.clone(),
|
||||||
update.event(),
|
update.take_events(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2128,7 +2128,17 @@ impl Hud {
|
|||||||
.x_y(0.0, 100.0)
|
.x_y(0.0, 100.0)
|
||||||
.position_ingame(over_pos)
|
.position_ingame(over_pos)
|
||||||
.set(overitem_id, ui_widgets);
|
.set(overitem_id, ui_widgets);
|
||||||
} else if let Some(item) = Item::try_reclaim_from_block(*block) {
|
}
|
||||||
|
// TODO: Handle this better. The items returned from `try_reclaim_from_block`
|
||||||
|
// are based on rng. We probably want some function to get only gauranteed items
|
||||||
|
// from `LootSpec`.
|
||||||
|
else if let Some((amount, mut item)) = Item::try_reclaim_from_block(*block)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.next()
|
||||||
|
{
|
||||||
|
item.set_amount(amount.clamp(1, item.max_amount()))
|
||||||
|
.expect("amount >= 1 and <= max_amount is always a valid amount");
|
||||||
make_overitem(
|
make_overitem(
|
||||||
&item,
|
&item,
|
||||||
over_pos,
|
over_pos,
|
||||||
|
@ -321,62 +321,67 @@ impl SessionState {
|
|||||||
TradeResult::NotEnoughSpace => "hud-trade-result-nospace",
|
TradeResult::NotEnoughSpace => "hud-trade-result-nospace",
|
||||||
})));
|
})));
|
||||||
},
|
},
|
||||||
client::Event::InventoryUpdated(inv_event) => {
|
client::Event::InventoryUpdated(inv_events) => {
|
||||||
let sfx_triggers = self.scene.sfx_mgr.triggers.read();
|
let sfx_triggers = self.scene.sfx_mgr.triggers.read();
|
||||||
|
|
||||||
let sfx_trigger_item = sfx_triggers.get_key_value(&SfxEvent::from(&inv_event));
|
for inv_event in inv_events {
|
||||||
|
let sfx_trigger_item =
|
||||||
|
sfx_triggers.get_key_value(&SfxEvent::from(&inv_event));
|
||||||
|
|
||||||
match inv_event {
|
match inv_event {
|
||||||
InventoryUpdateEvent::Dropped
|
InventoryUpdateEvent::Dropped
|
||||||
| InventoryUpdateEvent::Swapped
|
| InventoryUpdateEvent::Swapped
|
||||||
| InventoryUpdateEvent::Given
|
| InventoryUpdateEvent::Given
|
||||||
| InventoryUpdateEvent::Collected(_)
|
| InventoryUpdateEvent::Collected(_)
|
||||||
| InventoryUpdateEvent::EntityCollectFailed { .. }
|
| InventoryUpdateEvent::EntityCollectFailed { .. }
|
||||||
| InventoryUpdateEvent::BlockCollectFailed { .. }
|
| InventoryUpdateEvent::BlockCollectFailed { .. }
|
||||||
| InventoryUpdateEvent::Craft => {
|
| InventoryUpdateEvent::Craft => {
|
||||||
global_state.audio.emit_ui_sfx(sfx_trigger_item, Some(1.0));
|
global_state.audio.emit_ui_sfx(sfx_trigger_item, Some(1.0));
|
||||||
},
|
},
|
||||||
_ => global_state.audio.emit_sfx(
|
_ => global_state.audio.emit_sfx(
|
||||||
sfx_trigger_item,
|
sfx_trigger_item,
|
||||||
client.position().unwrap_or_default(),
|
client.position().unwrap_or_default(),
|
||||||
Some(1.0),
|
Some(1.0),
|
||||||
underwater,
|
underwater,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
match inv_event {
|
match inv_event {
|
||||||
InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {
|
InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {
|
||||||
self.hud.add_failed_block_pickup(
|
self.hud.add_failed_block_pickup(
|
||||||
pos,
|
pos,
|
||||||
HudCollectFailedReason::from_server_reason(
|
|
||||||
&reason,
|
|
||||||
client.state().ecs(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
InventoryUpdateEvent::EntityCollectFailed {
|
|
||||||
entity: uid,
|
|
||||||
reason,
|
|
||||||
} => {
|
|
||||||
if let Some(entity) = client.state().ecs().entity_from_uid(uid.into()) {
|
|
||||||
self.hud.add_failed_entity_pickup(
|
|
||||||
entity,
|
|
||||||
HudCollectFailedReason::from_server_reason(
|
HudCollectFailedReason::from_server_reason(
|
||||||
&reason,
|
&reason,
|
||||||
client.state().ecs(),
|
client.state().ecs(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
InventoryUpdateEvent::EntityCollectFailed {
|
||||||
InventoryUpdateEvent::Collected(item) => {
|
entity: uid,
|
||||||
self.hud.new_loot_message(LootMessage {
|
reason,
|
||||||
amount: item.amount(),
|
} => {
|
||||||
item,
|
if let Some(entity) =
|
||||||
taken_by: "You".to_string(),
|
client.state().ecs().entity_from_uid(uid.into())
|
||||||
});
|
{
|
||||||
},
|
self.hud.add_failed_entity_pickup(
|
||||||
_ => {},
|
entity,
|
||||||
};
|
HudCollectFailedReason::from_server_reason(
|
||||||
|
&reason,
|
||||||
|
client.state().ecs(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
InventoryUpdateEvent::Collected(item) => {
|
||||||
|
self.hud.new_loot_message(LootMessage {
|
||||||
|
amount: item.amount(),
|
||||||
|
item,
|
||||||
|
taken_by: "You".to_string(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
client::Event::Disconnect => return Ok(TickAction::Disconnect),
|
client::Event::Disconnect => return Ok(TickAction::Disconnect),
|
||||||
client::Event::DisconnectionNotification(time) => {
|
client::Event::DisconnectionNotification(time) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user