mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'floppy_multiloot' into 'master'
Add multiloot See merge request veloren/veloren!3839
This commit is contained in:
commit
4d79c89a0c
@ -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 migrate to new towns if they are dissatisfied with their current town
|
||||
- Female humanoids now have a greeting sound effect
|
||||
- Loot that drops multiple items is now distributed fairly between damage contributors.
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
name: Name("Frost Gigas"),
|
||||
body: RandomWith("gigas_frost"),
|
||||
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: (
|
||||
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")),
|
||||
(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,14 +1,14 @@
|
||||
[
|
||||
// Fireworks
|
||||
(1.0, ItemQuantity("common.items.utility.firework_blue", 8, 10)),
|
||||
(1.0, ItemQuantity("common.items.utility.firework_green", 8, 10)),
|
||||
(1.0, ItemQuantity("common.items.utility.firework_purple", 8, 10)),
|
||||
(1.0, ItemQuantity("common.items.utility.firework_red", 8, 10)),
|
||||
(1.0, ItemQuantity("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_blue"), 8, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.firework_green"), 8, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.firework_purple"), 8, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.firework_red"), 8, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.firework_white"), 8, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.firework_yellow"), 8, 10)),
|
||||
// Potions
|
||||
(10.0, ItemQuantity("common.items.consumable.potion_big", 2, 5)),
|
||||
(10.0, MultiDrop(Item("common.items.consumable.potion_big"), 2, 5)),
|
||||
// Misc
|
||||
(5.0, ItemQuantity("common.items.utility.collar", 2, 3)),
|
||||
(5.0, ItemQuantity("common.items.utility.bomb", 8, 10)),
|
||||
(5.0, MultiDrop(Item("common.items.utility.collar"), 2, 3)),
|
||||
(5.0, MultiDrop(Item("common.items.utility.bomb"), 8, 10)),
|
||||
]
|
@ -1,14 +1,14 @@
|
||||
[
|
||||
// Fireworks
|
||||
(1.0, ItemQuantity("common.items.utility.firework_blue", 3, 5)),
|
||||
(1.0, ItemQuantity("common.items.utility.firework_green", 3, 5)),
|
||||
(1.0, ItemQuantity("common.items.utility.firework_purple", 3, 5)),
|
||||
(1.0, ItemQuantity("common.items.utility.firework_red", 3, 5)),
|
||||
(1.0, ItemQuantity("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_blue"), 3, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.firework_green"), 3, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.firework_purple"), 3, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.firework_red"), 3, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.firework_white"), 3, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.firework_yellow"), 3, 5)),
|
||||
// Potions
|
||||
(10.0, ItemQuantity("common.items.consumable.potion_med", 2, 5)),
|
||||
(10.0, MultiDrop(Item("common.items.consumable.potion_med"), 2, 5)),
|
||||
// Misc
|
||||
(5.0, ItemQuantity("common.items.utility.collar", 1, 2)),
|
||||
(5.0, ItemQuantity("common.items.utility.bomb", 3, 5)),
|
||||
(5.0, MultiDrop(Item("common.items.utility.collar"), 1, 2)),
|
||||
(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_yellow")),
|
||||
// Potions
|
||||
(10.0, ItemQuantity("common.items.consumable.potion_minor", 2, 5)),
|
||||
(10.0, MultiDrop(Item("common.items.consumable.potion_minor"), 2, 5)),
|
||||
// Misc
|
||||
(5.0, Item("common.items.utility.collar")),
|
||||
(5.0, Item("common.items.utility.bomb")),
|
||||
|
@ -1,4 +1,4 @@
|
||||
[
|
||||
(1.5, ItemQuantity("common.items.crafting_ing.hide.carapace", 2, 5)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.strong_pincer", 1, 2)),
|
||||
(1.5, MultiDrop(Item("common.items.crafting_ing.hide.carapace"), 2, 5)),
|
||||
(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, 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,4 +1,4 @@
|
||||
[
|
||||
(2.0, ItemQuantity("common.items.flowers.plant_fiber", 1, 3)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.viscous_ooze", 1, 1)),
|
||||
(2.0, MultiDrop(Item("common.items.flowers.plant_fiber"), 1, 3)),
|
||||
(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")),
|
||||
(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)),
|
||||
(0.5, ItemQuantity("common.items.crafting_ing.animal_misc.viscous_ooze", 1, 1)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.sticky_thread"), 1, 3)),
|
||||
(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, ItemQuantity("common.items.crafting_ing.animal_misc.strong_pincer", 1, 2)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.sticky_thread"), 2, 5)),
|
||||
(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.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,4 +1,4 @@
|
||||
[
|
||||
(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, ItemQuantity("common.items.food.meat.beast_large_raw", 2, 2)),
|
||||
(2.0, ItemQuantity("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.food.meat.bird_large_raw"), 1, 2)),
|
||||
(1.0, MultiDrop(Item("common.items.food.meat.beast_large_raw"), 2, 2)),
|
||||
(2.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.elegant_crest"), 1, 3)),
|
||||
(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, ItemQuantity("common.items.food.meat.beast_large_raw", 2, 2)),
|
||||
(2.0, ItemQuantity("common.items.crafting_ing.animal_misc.elegant_crest", 1, 3)),
|
||||
(1.0, MultiDrop(Item("common.items.food.meat.bird_large_raw"), 1, 2)),
|
||||
(1.0, MultiDrop(Item("common.items.food.meat.beast_large_raw"), 2, 2)),
|
||||
(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, ItemQuantity("common.items.food.meat.beast_large_raw", 2, 2)),
|
||||
(2.0, ItemQuantity("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.food.meat.bird_large_raw"), 1, 2)),
|
||||
(1.0, MultiDrop(Item("common.items.food.meat.beast_large_raw"), 2, 2)),
|
||||
(2.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.elegant_crest"), 1, 3)),
|
||||
(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.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, 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)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.long_tusk", 2, 2)),
|
||||
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.rugged_hide"), 1, 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")),
|
||||
(1.0, ItemQuantity("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.large_horn"), 1, 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)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.icy_fang", 2, 4)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.long_tusk", 2, 2)),
|
||||
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.rugged_hide"), 1, 3)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.icy_fang"), 2, 4)),
|
||||
(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")),
|
||||
(0.5, Item("common.items.food.meat.beast_large_raw")),
|
||||
(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")),
|
||||
]
|
||||
|
@ -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")),
|
||||
]
|
@ -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,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")),
|
||||
]
|
@ -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")),
|
||||
(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, 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,5 +1,5 @@
|
||||
[
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.animal_misc.elegant_crest", 1, 1)),
|
||||
(2.0, ItemQuantity("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.animal_misc.elegant_crest"), 1, 1)),
|
||||
(2.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.raptor_feather"), 2, 6)),
|
||||
(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)),
|
||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.scales", 2, 6)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.animal_misc.raptor_feather"), 1, 2)),
|
||||
(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")),
|
||||
(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")),
|
||||
]
|
@ -3,12 +3,12 @@
|
||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-0")),
|
||||
(1.0, LootTable("common.loot_tables.armor.tier-0")),
|
||||
// Currency
|
||||
(3.0, ItemQuantity("common.items.utility.coins", 10, 20)),
|
||||
(3.0, MultiDrop(Item("common.items.utility.coins"), 10, 20)),
|
||||
// Materials
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.linen", 3, 10)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.leather.simple_leather", 3, 10)),
|
||||
(1.0, ItemQuantity("common.items.mineral.ingot.bronze", 3, 10)),
|
||||
(1.0, ItemQuantity("common.items.log.wood", 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.linen"), 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.leather.simple_leather"), 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ingot.bronze"), 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.log.wood"), 3, 10)),
|
||||
// Consumables
|
||||
(2.0, LootTable("common.loot_tables.consumable.poor")),
|
||||
]
|
||||
|
@ -9,7 +9,7 @@
|
||||
// Chieftain Mask
|
||||
(1.0, Item("common.items.armor.misc.head.gnarling_mask")),
|
||||
// Indirect crafting materials for T2 gear
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.sticky_thread", 2, 5)),
|
||||
(1.0, ItemQuantity("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.sticky_thread"), 2, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ore.coal"), 2, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.leather.leather_strips"), 2, 5)),
|
||||
]
|
@ -1,6 +1,6 @@
|
||||
[
|
||||
// Currency
|
||||
(1.0, ItemQuantity("common.items.utility.coins", 2, 4)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.coins"), 2, 4)),
|
||||
// Food
|
||||
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
||||
// Nothing
|
||||
|
@ -1,4 +1,4 @@
|
||||
[
|
||||
(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
|
||||
(1.0, ItemQuantity("common.items.utility.coins", 10, 20)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.coins"), 10, 20)),
|
||||
// Food
|
||||
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
||||
// Consumables
|
||||
(2.0, LootTable("common.loot_tables.consumable.poor")),
|
||||
// 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")),
|
||||
]
|
||||
|
@ -4,12 +4,12 @@
|
||||
(1.0, LootTable("common.loot_tables.armor.tier-1")),
|
||||
(0.5, Item("common.items.armor.misc.head.hog_hood")),
|
||||
// Currency
|
||||
(3.0, ItemQuantity("common.items.utility.coins", 20, 50)),
|
||||
(3.0, MultiDrop(Item("common.items.utility.coins"), 20, 50)),
|
||||
// Materials
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.wool", 3, 10)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.leather.thick_leather", 3, 10)),
|
||||
(1.0, ItemQuantity("common.items.mineral.ingot.iron", 3, 10)),
|
||||
(1.0, ItemQuantity("common.items.log.bamboo", 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.wool"), 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.leather.thick_leather"), 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ingot.iron"), 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.log.bamboo"), 3, 10)),
|
||||
// Consumables
|
||||
(2.0, LootTable("common.loot_tables.consumable.poor")),
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[
|
||||
// Currency
|
||||
(1.0, ItemQuantity("common.items.utility.coins", 4, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.coins"), 4, 10)),
|
||||
// Food
|
||||
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
||||
// Nothing
|
||||
|
@ -3,12 +3,12 @@
|
||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-2")),
|
||||
(1.0, LootTable("common.loot_tables.armor.tier-2")),
|
||||
// Currency
|
||||
(3.0, ItemQuantity("common.items.utility.coins", 50, 100)),
|
||||
(3.0, MultiDrop(Item("common.items.utility.coins"), 50, 100)),
|
||||
// Materials
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.silk", 3, 10)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.scales", 3, 10)),
|
||||
(1.0, ItemQuantity("common.items.mineral.ingot.steel", 3, 10)),
|
||||
(1.0, ItemQuantity("common.items.log.hardwood", 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.silk"), 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.scales"), 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ingot.steel"), 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.log.hardwood"), 3, 10)),
|
||||
// Consumables
|
||||
(2.0, LootTable("common.loot_tables.consumable.moderate")),
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[
|
||||
// Currency
|
||||
(1.0, ItemQuantity("common.items.utility.coins", 10, 20)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.coins"), 10, 20)),
|
||||
// Food
|
||||
(1.0, LootTable("common.loot_tables.food.wild_ingredients")),
|
||||
// Nothing
|
||||
|
@ -3,13 +3,13 @@
|
||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-3")),
|
||||
(1.0, LootTable("common.loot_tables.armor.tier-3")),
|
||||
// Currency
|
||||
(3.0, ItemQuantity("common.items.utility.coins", 100, 200)),
|
||||
(3.0, MultiDrop(Item("common.items.utility.coins"), 100, 200)),
|
||||
// Materials
|
||||
// Allow ironwood to have higher drops till entity droppers are implemented
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.lifecloth", 2, 6)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.carapace", 2, 6)),
|
||||
(1.0, ItemQuantity("common.items.mineral.ingot.cobalt", 2, 6)),
|
||||
(1.0, ItemQuantity("common.items.log.ironwood", 3, 7)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.lifecloth"), 2, 6)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.carapace"), 2, 6)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ingot.cobalt"), 2, 6)),
|
||||
(1.0, MultiDrop(Item("common.items.log.ironwood"), 3, 7)),
|
||||
// Consumables
|
||||
(2.0, LootTable("common.loot_tables.consumable.moderate")),
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[
|
||||
// Currency
|
||||
(1.0, ItemQuantity("common.items.utility.coins", 20, 40)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.coins"), 20, 40)),
|
||||
// Food
|
||||
(1.0, LootTable("common.loot_tables.food.prepared")),
|
||||
// Nothing
|
||||
|
@ -8,8 +8,8 @@
|
||||
(1.0, LootTable("common.loot_tables.weapons.legendary_melee")),
|
||||
// Crafting material
|
||||
// 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, ItemQuantity("common.items.crafting_ing.hide.dragon_scale", 3, 8)),
|
||||
(1.0, ItemQuantity("common.items.mineral.ingot.orichalcum", 1, 3)),
|
||||
(1.0, ItemQuantity("common.items.log.eldwood", 2, 6)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.sunsilk"), 1, 3)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.dragon_scale"), 3, 8)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ingot.orichalcum"), 1, 3)),
|
||||
(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, Item("common.items.armor.misc.head.spikeguard")),
|
||||
// Currency
|
||||
(3.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
||||
(3.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||
// Materials
|
||||
// Allow frostwood to have higher drops till entity droppers are implemented
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.moonweave", 1, 5)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.plate", 1, 5)),
|
||||
(1.0, ItemQuantity("common.items.mineral.ingot.bloodsteel", 1, 5)),
|
||||
(1.0, ItemQuantity("common.items.log.frostwood", 3, 6)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.moonweave"), 1, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.plate"), 1, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ingot.bloodsteel"), 1, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.log.frostwood"), 3, 6)),
|
||||
// Consumables
|
||||
(2.0, LootTable("common.loot_tables.consumable.good")),
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[
|
||||
// Currency
|
||||
(1.0, ItemQuantity("common.items.utility.coins", 40, 100)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.coins"), 40, 100)),
|
||||
// Food
|
||||
(1.0, LootTable("common.loot_tables.food.prepared")),
|
||||
// Nothing
|
||||
|
@ -1,6 +1,6 @@
|
||||
[
|
||||
// Currency
|
||||
(10.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
||||
(10.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||
// Food
|
||||
(5.0, LootTable("common.loot_tables.food.prepared")),
|
||||
// Consumables
|
||||
|
@ -13,8 +13,8 @@
|
||||
// Crafting material
|
||||
// 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, ItemQuantity("common.items.crafting_ing.cloth.sunsilk", 1, 3)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.dragon_scale", 3, 8)),
|
||||
(1.0, ItemQuantity("common.items.mineral.ingot.orichalcum", 1, 3)),
|
||||
(1.0, ItemQuantity("common.items.log.eldwood", 2, 6)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.sunsilk"), 1, 3)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.dragon_scale"), 3, 8)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ingot.orichalcum"), 1, 3)),
|
||||
(1.0, MultiDrop(Item("common.items.log.eldwood"), 2, 6)),
|
||||
]
|
||||
|
@ -4,13 +4,13 @@
|
||||
(1.0, LootTable("common.loot_tables.armor.tier-4")),
|
||||
(0.1, Item("common.items.armor.cultist.bandana")),
|
||||
// Currency
|
||||
(3.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
||||
(3.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||
// Materials
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.cloth.moonweave", 1, 5)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.plate", 1, 5)),
|
||||
(1.0, ItemQuantity("common.items.mineral.ingot.bloodsteel", 1, 5)),
|
||||
(1.0, ItemQuantity("common.items.log.frostwood", 1, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.cloth.moonweave"), 1, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.plate"), 1, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ingot.bloodsteel"), 1, 5)),
|
||||
(1.0, MultiDrop(Item("common.items.log.frostwood"), 1, 5)),
|
||||
// Consumables
|
||||
(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
|
||||
(1.0, ItemQuantity("common.items.utility.coins", 100, 200)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.coins"), 100, 200)),
|
||||
// Food
|
||||
(1.0, LootTable("common.loot_tables.food.prepared")),
|
||||
// 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
|
||||
(10.0, ItemQuantity("common.items.utility.coins", 500, 1000)),
|
||||
(10.0, MultiDrop(Item("common.items.utility.coins"), 500, 1000)),
|
||||
// Food
|
||||
(5.0, LootTable("common.loot_tables.food.prepared")),
|
||||
// Consumables
|
||||
|
@ -1,6 +1,6 @@
|
||||
[
|
||||
// Currency
|
||||
(30.0, ItemQuantity("common.items.utility.coins", 50, 100)),
|
||||
(30.0, MultiDrop(Item("common.items.utility.coins"), 50, 100)),
|
||||
// Nothing
|
||||
(30.0, Nothing),
|
||||
// Special
|
||||
|
@ -1,10 +1,10 @@
|
||||
[
|
||||
//Currency
|
||||
(4.0, ItemQuantity("common.items.utility.coins", 50, 200)),
|
||||
(4.0, MultiDrop(Item("common.items.utility.coins"), 50, 200)),
|
||||
//Food
|
||||
(4.0, LootTable("common.loot_tables.food.prepared")),
|
||||
//Flowers, pretty
|
||||
(2.0, ItemQuantity("common.items.flowers.red", 3, 6)),
|
||||
(2.0, MultiDrop(Item("common.items.flowers.red"), 3, 6)),
|
||||
//Weapon components
|
||||
(2.0, LootTable("common.loot_tables.weapons.components.tier-0")),
|
||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-1")),
|
||||
|
@ -1,15 +1,15 @@
|
||||
[
|
||||
//Currency
|
||||
(3.0, ItemQuantity("common.items.utility.coins", 50, 200)),
|
||||
(2.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
||||
(3.0, MultiDrop(Item("common.items.utility.coins"), 50, 200)),
|
||||
(2.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||
//Food
|
||||
(3.0, LootTable("common.loot_tables.food.prepared")),
|
||||
//Ores
|
||||
(2.0, ItemQuantity("common.items.mineral.ore.iron", 2, 7)),
|
||||
(2.0, MultiDrop(Item("common.items.mineral.ore.iron"), 2, 7)),
|
||||
//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
|
||||
(2.0, ItemQuantity("common.items.flowers.red", 3, 6)),
|
||||
(2.0, MultiDrop(Item("common.items.flowers.red"), 3, 6)),
|
||||
//Weapon components
|
||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-2")),
|
||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-3")),
|
||||
|
@ -1,21 +1,21 @@
|
||||
[
|
||||
//Currency
|
||||
(3.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
||||
(2.0, ItemQuantity("common.items.utility.coins", 2000, 5000)),
|
||||
(3.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||
(2.0, MultiDrop(Item("common.items.utility.coins"), 2000, 5000)),
|
||||
//Food
|
||||
(3.0, LootTable("common.loot_tables.food.prepared")),
|
||||
//Ores
|
||||
(2.0, ItemQuantity("common.items.mineral.ore.coal", 5, 15)),
|
||||
(2.0, ItemQuantity("common.items.mineral.ore.iron", 2, 7)),
|
||||
(1.0, ItemQuantity("common.items.mineral.ore.cobalt", 2, 5)),
|
||||
(0.1, ItemQuantity("common.items.mineral.gem.diamond", 2, 5)),
|
||||
(2.0, MultiDrop(Item("common.items.mineral.ore.coal"), 5, 15)),
|
||||
(2.0, MultiDrop(Item("common.items.mineral.ore.iron"), 2, 7)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ore.cobalt"), 2, 5)),
|
||||
(0.1, MultiDrop(Item("common.items.mineral.gem.diamond"), 2, 5)),
|
||||
//Hides
|
||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.animal_hide", 5, 15)),
|
||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.scales", 5, 15)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.carapace", 3, 10)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.tough_hide", 3, 10)),
|
||||
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.animal_hide"), 5, 15)),
|
||||
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.scales"), 5, 15)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.carapace"), 3, 10)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.tough_hide"), 3, 10)),
|
||||
//Flowers, very pretty
|
||||
(2.0, ItemQuantity("common.items.flowers.red", 5, 10)),
|
||||
(2.0, MultiDrop(Item("common.items.flowers.red"), 5, 10)),
|
||||
//Weapon components
|
||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-2")),
|
||||
(1.0, LootTable("common.loot_tables.weapons.components.tier-3")),
|
||||
|
@ -1,25 +1,25 @@
|
||||
[
|
||||
//Currency
|
||||
(2.0, ItemQuantity("common.items.utility.coins", 200, 500)),
|
||||
(1.0, ItemQuantity("common.items.utility.coins", 2000, 5000)),
|
||||
(2.0, MultiDrop(Item("common.items.utility.coins"), 200, 500)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.coins"), 2000, 5000)),
|
||||
//Food
|
||||
(4.0, LootTable("common.loot_tables.food.prepared")),
|
||||
//Ores
|
||||
(2.0, ItemQuantity("common.items.mineral.ore.coal", 10, 30)),
|
||||
(2.0, ItemQuantity("common.items.mineral.ore.iron", 4, 14)),
|
||||
(1.0, ItemQuantity("common.items.mineral.ore.cobalt", 4, 10)),
|
||||
(0.1, ItemQuantity("common.items.mineral.gem.diamond", 4, 10)),
|
||||
(2.0, MultiDrop(Item("common.items.mineral.ore.coal"), 10, 30)),
|
||||
(2.0, MultiDrop(Item("common.items.mineral.ore.iron"), 4, 14)),
|
||||
(1.0, MultiDrop(Item("common.items.mineral.ore.cobalt"), 4, 10)),
|
||||
(0.1, MultiDrop(Item("common.items.mineral.gem.diamond"), 4, 10)),
|
||||
//Hides
|
||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.animal_hide", 10, 30)),
|
||||
(2.0, ItemQuantity("common.items.crafting_ing.hide.scales", 10, 30)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.carapace", 6, 20)),
|
||||
(1.0, ItemQuantity("common.items.crafting_ing.hide.tough_hide", 6, 20)),
|
||||
(0.3, ItemQuantity("common.items.crafting_ing.hide.plate", 1, 8)),
|
||||
(0.1, ItemQuantity("common.items.crafting_ing.hide.rugged_hide", 1, 6)),
|
||||
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.animal_hide"), 10, 30)),
|
||||
(2.0, MultiDrop(Item("common.items.crafting_ing.hide.scales"), 10, 30)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.carapace"), 6, 20)),
|
||||
(1.0, MultiDrop(Item("common.items.crafting_ing.hide.tough_hide"), 6, 20)),
|
||||
(0.3, MultiDrop(Item("common.items.crafting_ing.hide.plate"), 1, 8)),
|
||||
(0.1, MultiDrop(Item("common.items.crafting_ing.hide.rugged_hide"), 1, 6)),
|
||||
//Flowers, very pretty
|
||||
(3.0, ItemQuantity("common.items.flowers.red", 10, 20)),
|
||||
(2.0, ItemQuantity("common.items.flowers.moonbell", 6, 12)),
|
||||
(1.0, ItemQuantity("common.items.flowers.pyrebloom", 3, 6)),
|
||||
(3.0, MultiDrop(Item("common.items.flowers.red"), 10, 20)),
|
||||
(2.0, MultiDrop(Item("common.items.flowers.moonbell"), 6, 12)),
|
||||
(1.0, MultiDrop(Item("common.items.flowers.pyrebloom"), 3, 6)),
|
||||
//Weapon components
|
||||
(1.5, LootTable("common.loot_tables.weapons.components.tier-4")),
|
||||
(0.2, LootTable("common.loot_tables.weapons.components.tier-5")),
|
||||
|
@ -1,4 +1,4 @@
|
||||
[
|
||||
(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
|
||||
(1.0, ItemQuantity("common.items.utility.coins", 40, 100)),
|
||||
(1.0, MultiDrop(Item("common.items.utility.coins"), 40, 100)),
|
||||
// Food
|
||||
(1.0, LootTable("common.loot_tables.food.prepared")),
|
||||
// Nothing
|
||||
|
@ -102,7 +102,7 @@ pub enum Event {
|
||||
},
|
||||
Disconnect,
|
||||
DisconnectionNotification(u64),
|
||||
InventoryUpdated(InventoryUpdateEvent),
|
||||
InventoryUpdated(Vec<InventoryUpdateEvent>),
|
||||
Kicked(String),
|
||||
Notification(Notification),
|
||||
SetViewDistance(u32),
|
||||
@ -2388,34 +2388,38 @@ impl Client {
|
||||
self.presence = None;
|
||||
self.clean_state();
|
||||
},
|
||||
ServerGeneral::InventoryUpdate(inventory, event) => {
|
||||
match event {
|
||||
InventoryUpdateEvent::BlockCollectFailed { .. } => {},
|
||||
InventoryUpdateEvent::EntityCollectFailed { .. } => {},
|
||||
_ => {
|
||||
// Push the updated inventory component to the client
|
||||
// FIXME: Figure out whether this error can happen under normal gameplay,
|
||||
// if not find a better way to handle it, if so maybe consider kicking the
|
||||
// client back to login?
|
||||
let entity = self.entity();
|
||||
if let Err(e) = self
|
||||
.state
|
||||
.ecs_mut()
|
||||
.write_storage()
|
||||
.insert(entity, inventory)
|
||||
{
|
||||
warn!(
|
||||
?e,
|
||||
"Received an inventory update event for client entity, but this \
|
||||
entity was not found... this may be a bug."
|
||||
);
|
||||
}
|
||||
},
|
||||
ServerGeneral::InventoryUpdate(inventory, events) => {
|
||||
let mut update_inventory = false;
|
||||
for event in events.iter() {
|
||||
match event {
|
||||
InventoryUpdateEvent::BlockCollectFailed { .. } => {},
|
||||
InventoryUpdateEvent::EntityCollectFailed { .. } => {},
|
||||
_ => update_inventory = true,
|
||||
}
|
||||
}
|
||||
if update_inventory {
|
||||
// Push the updated inventory component to the client
|
||||
// FIXME: Figure out whether this error can happen under normal gameplay,
|
||||
// if not find a better way to handle it, if so maybe consider kicking the
|
||||
// client back to login?
|
||||
let entity = self.entity();
|
||||
if let Err(e) = self
|
||||
.state
|
||||
.ecs_mut()
|
||||
.write_storage()
|
||||
.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();
|
||||
|
||||
frontend_events.push(Event::InventoryUpdated(event));
|
||||
frontend_events.push(Event::InventoryUpdated(events));
|
||||
},
|
||||
ServerGeneral::SetViewDistance(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"] }
|
||||
petgraph = "0.6.0"
|
||||
|
||||
|
||||
[[bench]]
|
||||
name = "chonk_benchmark"
|
||||
harness = false
|
||||
@ -99,6 +100,10 @@ harness = false
|
||||
name = "color_benchmark"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "loot_benchmark"
|
||||
harness = false
|
||||
|
||||
[[bin]]
|
||||
name = "csv_export"
|
||||
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
|
||||
/// from an ingame state
|
||||
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
|
||||
/// 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
|
||||
|
@ -254,48 +254,53 @@ fn loot_table(loot_table: &str) -> Result<(), Box<dyn Error>> {
|
||||
.div(10_f32.powi(5))
|
||||
.to_string();
|
||||
|
||||
let get_hands = |hands| match hands {
|
||||
Some(Hands::One) => "One",
|
||||
Some(Hands::Two) => "Two",
|
||||
None => "",
|
||||
};
|
||||
|
||||
match item {
|
||||
LootSpec::Item(item) => wtr.write_record([&chance, "Item", item, "", ""])?,
|
||||
LootSpec::ItemQuantity(item, lower, upper) => wtr.write_record([
|
||||
&chance,
|
||||
"Item",
|
||||
item,
|
||||
&lower.to_string(),
|
||||
&upper.to_string(),
|
||||
])?,
|
||||
LootSpec::LootTable(table) => {
|
||||
wtr.write_record([&chance, "LootTable", table, "", ""])?
|
||||
},
|
||||
LootSpec::Nothing => wtr.write_record([&chance, "Nothing", "", ""])?,
|
||||
LootSpec::ModularWeapon {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => wtr.write_record([
|
||||
&chance,
|
||||
"Modular Weapon",
|
||||
&get_tool_kind(tool),
|
||||
material.into(),
|
||||
get_hands(*hands),
|
||||
])?,
|
||||
LootSpec::ModularWeaponPrimaryComponent {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => wtr.write_record([
|
||||
&chance,
|
||||
"Modular Weapon Primary Component",
|
||||
&get_tool_kind(tool),
|
||||
material.into(),
|
||||
get_hands(*hands),
|
||||
])?,
|
||||
fn write_loot_spec<W: std::io::Write>(
|
||||
wtr: &mut csv::Writer<W>,
|
||||
loot_spec: &LootSpec<String>,
|
||||
chance: &str,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let get_hands = |hands| match hands {
|
||||
Some(Hands::One) => "One",
|
||||
Some(Hands::Two) => "Two",
|
||||
None => "",
|
||||
};
|
||||
match loot_spec {
|
||||
LootSpec::Item(item) => wtr.write_record([chance, "Item", item, "", ""])?,
|
||||
LootSpec::LootTable(table) => {
|
||||
wtr.write_record([chance, "LootTable", table, "", ""])?
|
||||
},
|
||||
LootSpec::Nothing => wtr.write_record([chance, "Nothing", "", ""])?,
|
||||
LootSpec::ModularWeapon {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => wtr.write_record([
|
||||
chance,
|
||||
"Modular Weapon",
|
||||
&get_tool_kind(tool),
|
||||
material.into(),
|
||||
get_hands(*hands),
|
||||
])?,
|
||||
LootSpec::ModularWeaponPrimaryComponent {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => wtr.write_record([
|
||||
chance,
|
||||
"Modular Weapon Primary Component",
|
||||
&get_tool_kind(tool),
|
||||
material.into(),
|
||||
get_hands(*hands),
|
||||
])?,
|
||||
LootSpec::MultiDrop(loot, _, _) => {
|
||||
// TODO: Write amount gotten somewhere?
|
||||
write_loot_spec(wtr, loot, chance)?;
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
write_loot_spec(&mut wtr, item, &chance)?;
|
||||
}
|
||||
|
||||
wtr.flush()?;
|
||||
@ -406,16 +411,6 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
|
||||
"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 => {
|
||||
wtr.write_record(&[
|
||||
name.clone(),
|
||||
@ -477,6 +472,7 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
},
|
||||
LootSpec::LootTable(_) => unreachable!(),
|
||||
LootSpec::MultiDrop(_, _, _) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -921,6 +921,34 @@ impl 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
|
||||
/// 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
|
||||
@ -1167,8 +1195,8 @@ impl Item {
|
||||
|
||||
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> {
|
||||
block.get_sprite()?.collectible_id()??.to_item()
|
||||
pub fn try_reclaim_from_block(block: Block) -> Option<Vec<(u32, Self)>> {
|
||||
block.get_sprite()?.collectible_id()??.to_items()
|
||||
}
|
||||
|
||||
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
|
||||
/// for either an `Item` containing the definition, or the actual `ItemDef`
|
||||
pub trait ItemDesc {
|
||||
@ -1370,9 +1408,9 @@ impl Component for Item {
|
||||
}
|
||||
|
||||
#[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>;
|
||||
}
|
||||
|
||||
|
@ -995,13 +995,19 @@ impl Default for InventoryUpdateEvent {
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct InventoryUpdate {
|
||||
event: InventoryUpdateEvent,
|
||||
events: Vec<InventoryUpdateEvent>,
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -362,74 +362,84 @@ impl From<Vec<(f32, LootSpec<String>)>> for ProbabilityFile {
|
||||
} else {
|
||||
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 {
|
||||
content: content
|
||||
.into_iter()
|
||||
.flat_map(|(p0, loot)| match 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(),
|
||||
})
|
||||
.flat_map(|(p0, loot)| get_content(rescale, p0, loot))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
@ -1231,12 +1241,4 @@ mod tests {
|
||||
let probability: ProbabilityFile = loot_table.into();
|
||||
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>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum LootOwnerKind {
|
||||
Player(Uid),
|
||||
Group(Group),
|
||||
|
@ -92,7 +92,7 @@ pub use self::{
|
||||
self,
|
||||
item_key::ItemKey,
|
||||
tool::{self, AbilityItem},
|
||||
Item, ItemConfig, ItemDrop,
|
||||
Item, ItemConfig, ItemDrops,
|
||||
},
|
||||
slot, CollectFailedReason, Inventory, InventoryUpdate, InventoryUpdateEvent,
|
||||
},
|
||||
|
@ -26,6 +26,8 @@
|
||||
// Cheese drop rate = 3/X = 29.6%
|
||||
// Coconut drop rate = 1/X = 9.85%
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::{
|
||||
assets::{self, AssetExt},
|
||||
comp::{inventory::item, Item},
|
||||
@ -76,12 +78,136 @@ impl<T> Lottery<T> {
|
||||
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)]
|
||||
pub enum LootSpec<T: AsRef<str>> {
|
||||
/// Asset specifier
|
||||
Item(T),
|
||||
/// Asset specifier, lower range, upper range
|
||||
ItemQuantity(T, u32, u32),
|
||||
/// Loot table
|
||||
LootTable(T),
|
||||
/// No loot given
|
||||
@ -97,78 +223,129 @@ pub enum LootSpec<T: AsRef<str>> {
|
||||
material: item::Material,
|
||||
hands: Option<item::tool::Hands>,
|
||||
},
|
||||
/// LootSpec, lower range, upper range
|
||||
MultiDrop(Box<LootSpec<T>>, u32, u32),
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> LootSpec<T> {
|
||||
pub fn to_item(&self) -> Option<Item> {
|
||||
let mut rng = thread_rng();
|
||||
match self {
|
||||
Self::Item(item) => Item::new_from_asset(item.as_ref()).map_or_else(
|
||||
fn to_items_inner(
|
||||
&self,
|
||||
rng: &mut rand::rngs::ThreadRng,
|
||||
amount: u32,
|
||||
items: &mut Vec<(u32, Item)>,
|
||||
) {
|
||||
let convert_item = |item: &T| {
|
||||
Item::new_from_asset(item.as_ref()).map_or_else(
|
||||
|e| {
|
||||
warn!(?e, "error while loading item: {}", item.as_ref());
|
||||
None
|
||||
},
|
||||
Some,
|
||||
),
|
||||
Self::ItemQuantity(item, lower, upper) => {
|
||||
let range = *lower..=*upper;
|
||||
let quantity = thread_rng().gen_range(range);
|
||||
match Item::new_from_asset(item.as_ref()) {
|
||||
Ok(mut item) => {
|
||||
// TODO: Handle multiple of an item that is unstackable
|
||||
if item.set_amount(quantity).is_err() {
|
||||
warn!("Tried to set quantity on non stackable item");
|
||||
}
|
||||
Some(item)
|
||||
},
|
||||
Err(e) => {
|
||||
warn!(?e, "error while loading item: {}", item.as_ref());
|
||||
None
|
||||
},
|
||||
)
|
||||
};
|
||||
let mut push_item = |mut item: Item, count: u32| {
|
||||
let count = item.amount().saturating_mul(count);
|
||||
item.set_amount(1).expect("1 is always a valid amount.");
|
||||
let hash = item.item_hash();
|
||||
match items.binary_search_by_key(&hash, |(_, item)| item.item_hash()) {
|
||||
Ok(i) => {
|
||||
// Since item hash can collide with other items, we search nearby items with the
|
||||
// same hash.
|
||||
// NOTE: The `ParitalEq` implementation for `Item` doesn't compare some data
|
||||
// like durability, or wether slots contain anything. Although since these are
|
||||
// Newly loaded items we don't care about comparing those for deduplication
|
||||
// here.
|
||||
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())
|
||||
.read()
|
||||
.choose()
|
||||
.to_item(),
|
||||
Self::Nothing => None,
|
||||
Self::LootTable(table) => {
|
||||
let loot_spec = Lottery::<LootSpec<String>>::load_expect(table.as_ref()).read();
|
||||
for _ in 0..amount {
|
||||
loot_spec.choose().to_items_inner(rng, 1, items)
|
||||
}
|
||||
},
|
||||
Self::Nothing => {},
|
||||
Self::ModularWeapon {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => item::modular::random_weapon(*tool, *material, *hands, &mut rng).map_or_else(
|
||||
|e| {
|
||||
warn!(
|
||||
?e,
|
||||
"error while creating modular weapon. Toolkind: {:?}, Material: {:?}, \
|
||||
Hands: {:?}",
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
);
|
||||
None
|
||||
},
|
||||
Some,
|
||||
),
|
||||
} => {
|
||||
for _ in 0..amount {
|
||||
match item::modular::random_weapon(*tool, *material, *hands, rng) {
|
||||
Ok(item) => push_item(item, 1),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
?e,
|
||||
"error while creating modular weapon. Toolkind: {:?}, Material: \
|
||||
{:?}, Hands: {:?}",
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Self::ModularWeaponPrimaryComponent {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => item::modular::random_weapon_primary_component(*tool, *material, *hands, &mut rng)
|
||||
.map_or_else(
|
||||
|e| {
|
||||
warn!(
|
||||
?e,
|
||||
"error while creating modular weapon primary component. Toolkind: \
|
||||
{:?}, Material: {:?}, Hands: {:?}",
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
);
|
||||
None
|
||||
},
|
||||
|(comp, _)| Some(comp),
|
||||
),
|
||||
} => {
|
||||
for _ in 0..amount {
|
||||
match item::modular::random_weapon(*tool, *material, *hands, rng) {
|
||||
Ok(item) => push_item(item, 1),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
?e,
|
||||
"error while creating modular weapon primary component. Toolkind: \
|
||||
{:?}, Material: {:?}, Hands: {:?}",
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
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) => {
|
||||
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) => {
|
||||
let loot_table = Lottery::<LootSpec<String>>::load_expect_cloned(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());
|
||||
}
|
||||
}
|
||||
|
||||
#[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::Projectile>();
|
||||
ecs.register::<comp::Melee>();
|
||||
ecs.register::<comp::ItemDrop>();
|
||||
ecs.register::<comp::ItemDrops>();
|
||||
ecs.register::<comp::ChatMode>();
|
||||
ecs.register::<comp::Faction>();
|
||||
ecs.register::<comp::invite::Invite>();
|
||||
|
@ -577,12 +577,20 @@ fn handle_give_item(
|
||||
});
|
||||
}
|
||||
|
||||
insert_or_replace_component(
|
||||
server,
|
||||
target,
|
||||
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::Given),
|
||||
"target",
|
||||
)?;
|
||||
let mut inventory_update = server
|
||||
.state
|
||||
.ecs_mut()
|
||||
.write_storage::<comp::InventoryUpdate>();
|
||||
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
|
||||
} else {
|
||||
Err(format!("Invalid item: {}", item_name))
|
||||
@ -686,8 +694,8 @@ fn handle_make_npc(
|
||||
entity_builder = entity_builder.with(agent);
|
||||
}
|
||||
|
||||
if let Some(drop_item) = loot.to_item() {
|
||||
entity_builder = entity_builder.with(comp::ItemDrop(drop_item));
|
||||
if let Some(drop_items) = loot.to_items() {
|
||||
entity_builder = entity_builder.with(comp::ItemDrops(drop_items));
|
||||
}
|
||||
|
||||
// Some would say it's a hack, some would say it's incomplete
|
||||
|
@ -9,7 +9,7 @@ use common::{
|
||||
aura::{Aura, AuraKind, AuraTarget},
|
||||
beam,
|
||||
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,
|
||||
},
|
||||
event::{EventBus, NpcBuilder, UpdateCharacterMetadata},
|
||||
@ -118,8 +118,8 @@ pub fn handle_create_npc(server: &mut Server, pos: Pos, mut npc: NpcBuilder) ->
|
||||
entity
|
||||
};
|
||||
|
||||
let entity = if let Some(drop_item) = npc.loot.to_item() {
|
||||
entity.with(ItemDrop(drop_item))
|
||||
let entity = if let Some(drop_items) = npc.loot.to_items() {
|
||||
entity.with(ItemDrops(drop_items))
|
||||
} else {
|
||||
entity
|
||||
};
|
||||
|
@ -19,13 +19,16 @@ use common::{
|
||||
self, aura, buff,
|
||||
chat::{KillSource, KillType},
|
||||
inventory::item::{AbilityMap, MaterialStatManifest},
|
||||
item::flatten_counted_items,
|
||||
loot_owner::LootOwnerKind,
|
||||
Alignment, Auras, Body, CharacterState, Energy, Group, Health, HealthChange, Inventory,
|
||||
Player, Poise, Pos, SkillSet, Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
lottery::distribute_many,
|
||||
outcome::{HealthChangeInfo, Outcome},
|
||||
resources::{Secs, Time},
|
||||
spiral::Spiral2d,
|
||||
states::utils::StageSection,
|
||||
terrain::{Block, BlockKind, TerrainGrid},
|
||||
trade::{TradeResult, Trades},
|
||||
@ -37,8 +40,7 @@ use common::{
|
||||
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
|
||||
use common_state::BlockChange;
|
||||
use hashbrown::HashSet;
|
||||
use rand::{distributions::WeightedIndex, Rng};
|
||||
use rand_distr::Distribution;
|
||||
use rand::Rng;
|
||||
use specs::{
|
||||
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
|
||||
|
||||
let item = {
|
||||
let mut item_drop = state.ecs().write_storage::<comp::ItemDrop>();
|
||||
item_drop.remove(entity).map(|comp::ItemDrop(item)| item)
|
||||
let items = {
|
||||
let mut item_drops = state.ecs().write_storage::<comp::ItemDrops>();
|
||||
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 vel = state.ecs().read_storage::<comp::Vel>().get(entity).cloned();
|
||||
if let Some(pos) = pos {
|
||||
// Remove entries where zero exp was awarded - this happens because some
|
||||
// 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() {
|
||||
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);
|
||||
uid.map(LootOwnerKind::Player)
|
||||
};
|
||||
|
||||
if let Some(group) = group {
|
||||
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)
|
||||
*item_receivers.entry(loot_owner).or_insert(0.0) += exp;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let item_drop_entity = state
|
||||
.create_item_drop(Pos(pos.0 + Vec3::unit_z() * 0.25), item)
|
||||
.maybe_with(vel)
|
||||
.build();
|
||||
if !item_receivers.is_empty() {
|
||||
let msm = &MaterialStatManifest::load().read();
|
||||
let ability_map = &AbilityMap::load().read();
|
||||
let mut item_offset_spiral = Spiral2d::new();
|
||||
|
||||
// If there was a loot winner, assign them as the owner of the loot. There will
|
||||
// not be a loot winner when an entity dies to environment damage and such so
|
||||
// the loot will be free-for-all.
|
||||
if let Some(uid) = winner {
|
||||
debug!("Assigned UID {:?} as the winner for the loot drop", uid);
|
||||
|
||||
state
|
||||
.ecs()
|
||||
.write_storage::<LootOwner>()
|
||||
.insert(item_drop_entity, LootOwner::new(uid))
|
||||
.unwrap();
|
||||
let mut rng = rand::thread_rng();
|
||||
distribute_many(
|
||||
item_receivers
|
||||
.iter()
|
||||
.map(|(loot_owner, weight)| (*weight, *loot_owner)),
|
||||
&mut rng,
|
||||
&items,
|
||||
|(amount, _)| *amount,
|
||||
|(_, item), loot_owner, count| {
|
||||
for item in item.stacked_duplicates(ability_map, msm, count) {
|
||||
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 {
|
||||
error!(
|
||||
@ -1080,24 +1098,28 @@ pub fn handle_bonk(server: &mut Server, pos: Vec3<f32>, owner: Option<Uid>, targ
|
||||
{
|
||||
drop(terrain);
|
||||
drop(block_change);
|
||||
if let Some(item) = comp::Item::try_reclaim_from_block(block) {
|
||||
server
|
||||
.state
|
||||
.create_object(Default::default(), match block.get_sprite() {
|
||||
// Create different containers depending on the original sprite
|
||||
Some(SpriteKind::Apple) => comp::object::Body::Apple,
|
||||
Some(SpriteKind::Beehive) => comp::object::Body::Hive,
|
||||
Some(SpriteKind::Coconut) => comp::object::Body::Coconut,
|
||||
Some(SpriteKind::Bomb) => comp::object::Body::Bomb,
|
||||
_ => comp::object::Body::Pouch,
|
||||
})
|
||||
.with(Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)))
|
||||
.with(item)
|
||||
.maybe_with(match block.get_sprite() {
|
||||
Some(SpriteKind::Bomb) => Some(comp::Object::Bomb { owner }),
|
||||
_ => None,
|
||||
})
|
||||
.build();
|
||||
if let Some(items) = comp::Item::try_reclaim_from_block(block) {
|
||||
let msm = &MaterialStatManifest::load().read();
|
||||
let ability_map = &AbilityMap::load().read();
|
||||
for item in flatten_counted_items(&items, ability_map, msm) {
|
||||
server
|
||||
.state
|
||||
.create_object(Default::default(), match block.get_sprite() {
|
||||
// Create different containers depending on the original sprite
|
||||
Some(SpriteKind::Apple) => comp::object::Body::Apple,
|
||||
Some(SpriteKind::Beehive) => comp::object::Body::Hive,
|
||||
Some(SpriteKind::Coconut) => comp::object::Body::Coconut,
|
||||
Some(SpriteKind::Bomb) => comp::object::Body::Bomb,
|
||||
_ => comp::object::Body::Pouch,
|
||||
})
|
||||
.with(Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)))
|
||||
.with(item)
|
||||
.maybe_with(match block.get_sprite() {
|
||||
Some(SpriteKind::Bomb) => Some(comp::Object::Bomb { owner }),
|
||||
_ => None,
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,10 @@ use common::{
|
||||
agent::{AgentEvent, Sound, SoundKind},
|
||||
dialogue::Subject,
|
||||
inventory::slot::EquipSlot,
|
||||
item::{flatten_counted_items, MaterialStatManifest},
|
||||
loot_owner::LootOwnerKind,
|
||||
pet::is_mountable,
|
||||
tool::ToolKind,
|
||||
tool::{AbilityMap, ToolKind},
|
||||
Inventory, LootOwner, Pos, SkillGroupKind,
|
||||
},
|
||||
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();
|
||||
if let Some(block) = block.filter(|b| b.mine_tool().map_or(false, |t| Some(t) == tool)) {
|
||||
// Drop item if one is recoverable from the block
|
||||
if let Some(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);
|
||||
|
||||
if let Some(mut skillset) = state
|
||||
@ -186,12 +190,17 @@ pub fn handle_mine_block(
|
||||
.write_storage::<comp::SkillSet>()
|
||||
.get_mut(entity)
|
||||
{
|
||||
if let (Some(tool), Some(uid), Some(exp_reward)) = (
|
||||
if let (Some(tool), Some(uid), exp_reward @ 1..) = (
|
||||
tool,
|
||||
maybe_uid,
|
||||
item.item_definition_id()
|
||||
.itemdef_id()
|
||||
.and_then(|id| RESOURCE_EXPERIENCE_MANIFEST.read().0.get(id).copied()),
|
||||
items
|
||||
.iter()
|
||||
.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 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))
|
||||
};
|
||||
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| {
|
||||
(id.contains("mineral.ore.") && need_double_ore(&mut rng))
|
||||
|| (id.contains("mineral.gem.") && need_double_gem(&mut rng))
|
||||
});
|
||||
|
||||
if double_gain {
|
||||
// Ignore non-stackable errors
|
||||
let _ = item.increase_amount(1);
|
||||
if double_gain {
|
||||
// Ignore non-stackable errors
|
||||
let _ = item.increase_amount(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
let item_drop = state
|
||||
.create_item_drop(Default::default(), item)
|
||||
.with(Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)));
|
||||
if let Some(uid) = maybe_uid {
|
||||
item_drop.with(LootOwner::new(LootOwnerKind::Player(uid)))
|
||||
} else {
|
||||
item_drop
|
||||
for item in items {
|
||||
let item_drop = state
|
||||
.create_item_drop(Default::default(), item)
|
||||
.with(Pos(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)));
|
||||
if let Some(uid) = maybe_uid {
|
||||
item_drop.with(LootOwner::new(LootOwnerKind::Player(uid)))
|
||||
} else {
|
||||
item_drop
|
||||
}
|
||||
.build();
|
||||
}
|
||||
.build();
|
||||
}
|
||||
|
||||
state.set_block(pos, block.into_vacant());
|
||||
|
@ -8,8 +8,9 @@ use common::{
|
||||
comp::{
|
||||
self,
|
||||
group::members,
|
||||
item::{self, tool::AbilityMap, MaterialStatManifest},
|
||||
item::{self, flatten_counted_items, tool::AbilityMap, MaterialStatManifest},
|
||||
slot::{self, Slot},
|
||||
InventoryUpdate,
|
||||
},
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
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 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 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 let Some(item) = comp::Item::try_reclaim_from_block(block) {
|
||||
// NOTE: We dup the item for message purposes.
|
||||
let item_msg = item.duplicate(
|
||||
&ecs.read_resource::<AbilityMap>(),
|
||||
&ecs.read_resource::<MaterialStatManifest>(),
|
||||
);
|
||||
let event = match inventory.push(item) {
|
||||
Ok(_) => {
|
||||
if let Some(group_id) = ecs.read_storage::<Group>().get(entity) {
|
||||
announce_loot_to_group(
|
||||
group_id,
|
||||
ecs,
|
||||
entity,
|
||||
item_msg.duplicate(
|
||||
&ecs.read_resource::<AbilityMap>(),
|
||||
&ecs.read_resource::<MaterialStatManifest>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
comp::InventoryUpdate::new(InventoryUpdateEvent::Collected(
|
||||
item_msg,
|
||||
))
|
||||
},
|
||||
// The item we created was in some sense "fake" so it's safe to
|
||||
// drop it.
|
||||
Err(_) => {
|
||||
drop_item = Some(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.");
|
||||
// If there are items to be reclaimed from the block, add it to the inventory
|
||||
if let Some(items) = comp::Item::try_reclaim_from_block(block) {
|
||||
let msm = &MaterialStatManifest::load().read();
|
||||
let ability_map = &AbilityMap::load().read();
|
||||
for item in flatten_counted_items(&items, ability_map, msm) {
|
||||
// NOTE: We dup the item for message purposes.
|
||||
let item_msg = item.duplicate(ability_map, msm);
|
||||
match inventory.push(item) {
|
||||
Ok(_) => {
|
||||
if let Some(group_id) = ecs.read_storage::<Group>().get(entity)
|
||||
{
|
||||
announce_loot_to_group(
|
||||
group_id,
|
||||
ecs,
|
||||
entity,
|
||||
item_msg.duplicate(
|
||||
&ecs.read_resource::<AbilityMap>(),
|
||||
&ecs.read_resource::<MaterialStatManifest>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
inventory_update
|
||||
.push(InventoryUpdateEvent::Collected(item_msg));
|
||||
},
|
||||
// The item we created was in some sense "fake" so it's safe to
|
||||
// drop it.
|
||||
Err(_) => {
|
||||
drop_items.push(item_msg);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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(terrain);
|
||||
drop(block_change);
|
||||
if let Some(item) = drop_item {
|
||||
drop(inventory_updates);
|
||||
|
||||
for item in drop_items {
|
||||
state
|
||||
.create_item_drop(Default::default(), item)
|
||||
.with(comp::Pos(
|
||||
|
@ -356,10 +356,10 @@ impl<'a> System<'a> for Sys {
|
||||
// TODO: Sync clients that don't have a position?
|
||||
|
||||
// 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(
|
||||
inventory.clone(),
|
||||
update.event(),
|
||||
update.take_events(),
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -2128,7 +2128,17 @@ impl Hud {
|
||||
.x_y(0.0, 100.0)
|
||||
.position_ingame(over_pos)
|
||||
.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(
|
||||
&item,
|
||||
over_pos,
|
||||
|
@ -321,62 +321,67 @@ impl SessionState {
|
||||
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_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 {
|
||||
InventoryUpdateEvent::Dropped
|
||||
| InventoryUpdateEvent::Swapped
|
||||
| InventoryUpdateEvent::Given
|
||||
| InventoryUpdateEvent::Collected(_)
|
||||
| InventoryUpdateEvent::EntityCollectFailed { .. }
|
||||
| InventoryUpdateEvent::BlockCollectFailed { .. }
|
||||
| InventoryUpdateEvent::Craft => {
|
||||
global_state.audio.emit_ui_sfx(sfx_trigger_item, Some(1.0));
|
||||
},
|
||||
_ => global_state.audio.emit_sfx(
|
||||
sfx_trigger_item,
|
||||
client.position().unwrap_or_default(),
|
||||
Some(1.0),
|
||||
underwater,
|
||||
),
|
||||
}
|
||||
match inv_event {
|
||||
InventoryUpdateEvent::Dropped
|
||||
| InventoryUpdateEvent::Swapped
|
||||
| InventoryUpdateEvent::Given
|
||||
| InventoryUpdateEvent::Collected(_)
|
||||
| InventoryUpdateEvent::EntityCollectFailed { .. }
|
||||
| InventoryUpdateEvent::BlockCollectFailed { .. }
|
||||
| InventoryUpdateEvent::Craft => {
|
||||
global_state.audio.emit_ui_sfx(sfx_trigger_item, Some(1.0));
|
||||
},
|
||||
_ => global_state.audio.emit_sfx(
|
||||
sfx_trigger_item,
|
||||
client.position().unwrap_or_default(),
|
||||
Some(1.0),
|
||||
underwater,
|
||||
),
|
||||
}
|
||||
|
||||
match inv_event {
|
||||
InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {
|
||||
self.hud.add_failed_block_pickup(
|
||||
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,
|
||||
match inv_event {
|
||||
InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {
|
||||
self.hud.add_failed_block_pickup(
|
||||
pos,
|
||||
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(),
|
||||
});
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
},
|
||||
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(
|
||||
&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::DisconnectionNotification(time) => {
|
||||
|
Loading…
Reference in New Issue
Block a user