mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Refactored loadout to have public functions for each slot instead of requiring callers to use the _INDEX consts
This commit is contained in:
parent
9deeefbd1e
commit
aef2637288
@ -5,7 +5,7 @@ rustflags = [
|
||||
|
||||
[alias]
|
||||
generate = "run --package tools --"
|
||||
test-server = "-Zpackage-features run --bin veloren-server-cli --no-default-features"
|
||||
test-server = "-Zpackage-features run --bin veloren-server-cli --no-default-features -- -b"
|
||||
tracy-server = "-Zunstable-options -Zpackage-features run --bin veloren-server-cli --no-default-features --features tracy,simd --profile no_overflow"
|
||||
test-voxygen = "-Zpackage-features run --bin veloren-voxygen --no-default-features --features gl,simd"
|
||||
tracy-voxygen = "-Zunstable-options -Zpackage-features run --bin veloren-voxygen --no-default-features --features tracy,gl,simd --profile no_overflow"
|
||||
|
@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Initial support for alternate style keyboards
|
||||
- Flying birds travel the world
|
||||
- Plugin system now based on Wasmer 1.0.0
|
||||
- Added 4x Bag loadout slots, used for upgrading inventory space
|
||||
- Added an additional Ring loadout slot
|
||||
- The inventory can now be expanded to fill the whole window
|
||||
- Added /dropall admin command (drops all inventory items on the ground)
|
||||
|
||||
### Changed
|
||||
|
||||
@ -26,10 +30,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Glider can now be deployed even when not on the ground
|
||||
- Gliding now has an energy cost for strenuous maneuvers based on lift
|
||||
- Translations are now folders with multiple files instead of a huge single file
|
||||
- Default inventory slots reduced to 18 - existing characters given 3x 6-slot bags as compensation
|
||||
- Protection rating was moved to the top left of the loadout view
|
||||
|
||||
### Removed
|
||||
|
||||
- SSAAx4 option
|
||||
- The Stats button and associated screen were removed
|
||||
|
||||
### Fixed
|
||||
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -6089,6 +6089,7 @@ version = "0.8.0"
|
||||
dependencies = [
|
||||
"authc",
|
||||
"chrono",
|
||||
"const-tweaker",
|
||||
"crossbeam-channel 0.5.0",
|
||||
"diesel",
|
||||
"diesel_migrations",
|
||||
@ -6179,6 +6180,7 @@ dependencies = [
|
||||
"image",
|
||||
"inline_tweak",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"native-dialog",
|
||||
"num 0.3.1",
|
||||
"old_school_gfx_glutin_ext",
|
||||
|
@ -4,7 +4,7 @@
|
||||
(110, Stones),
|
||||
(150, ShortGrass),
|
||||
(120, CaveMushroom),
|
||||
(2, ShinyGem),
|
||||
(2, Chest),
|
||||
(4, ShinyGem),
|
||||
(5, Chest),
|
||||
(15, Crate),
|
||||
]
|
||||
|
@ -9,4 +9,5 @@ ItemDef(
|
||||
)
|
||||
),
|
||||
quality: High,
|
||||
slots: 18,
|
||||
)
|
||||
|
12
assets/common/items/armor/bag/heavy_seabag.ron
Normal file
12
assets/common/items/armor/bag/heavy_seabag.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Heavy Seabag",
|
||||
description: "Commonly used by sailors.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("BluePouch"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Moderate,
|
||||
slots: 14,
|
||||
)
|
12
assets/common/items/armor/bag/knitted_red_pouch.ron
Normal file
12
assets/common/items/armor/bag/knitted_red_pouch.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Knitted Red Pouch",
|
||||
description: "Made from some patches of dyed cloth.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("RedSmall"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Moderate,
|
||||
slots: 9,
|
||||
)
|
12
assets/common/items/armor/bag/liana_kit.ron
Normal file
12
assets/common/items/armor/bag/liana_kit.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Liana Kit",
|
||||
description: "Woven from dried lianas.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("GreenMid"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Moderate,
|
||||
slots: 12,
|
||||
)
|
12
assets/common/items/armor/bag/mindflayer_spellbag.ron
Normal file
12
assets/common/items/armor/bag/mindflayer_spellbag.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Mindflayer Spellbag",
|
||||
description: "You can almost feel the Mindflayer's\nevil presence flowing through the fabric.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("PurpleSkull"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Epic,
|
||||
slots: 27,
|
||||
)
|
12
assets/common/items/armor/bag/reliable_backpack.ron
Normal file
12
assets/common/items/armor/bag/reliable_backpack.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Reliable Backpack",
|
||||
description: "It will never give you up.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("LeatherLarge"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: High,
|
||||
slots: 16,
|
||||
)
|
12
assets/common/items/armor/bag/soulkeeper_cursed.ron
Normal file
12
assets/common/items/armor/bag/soulkeeper_cursed.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Cursed Soulkeeper",
|
||||
description: "WIP",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("RedFace"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Legendary,
|
||||
slots: 36,
|
||||
)
|
12
assets/common/items/armor/bag/soulkeeper_pure.ron
Normal file
12
assets/common/items/armor/bag/soulkeeper_pure.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Purified Soulkeeper",
|
||||
description: "WIP",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("BlueFace"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Legendary,
|
||||
slots: 36,
|
||||
)
|
12
assets/common/items/armor/bag/sturdy_red_backpack.ron
Normal file
12
assets/common/items/armor/bag/sturdy_red_backpack.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Sturdy Red Backpack",
|
||||
description: "Made from some patches of dyed cloth.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("RedLarge"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: High,
|
||||
slots: 18,
|
||||
)
|
12
assets/common/items/armor/bag/tiny_leather_pouch.ron
Normal file
12
assets/common/items/armor/bag/tiny_leather_pouch.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Tiny Leather Pouch",
|
||||
description: "Made from a few patches of leather.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("LeatherSmall"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Common,
|
||||
slots: 6,
|
||||
)
|
12
assets/common/items/armor/bag/tiny_red_pouch.ron
Normal file
12
assets/common/items/armor/bag/tiny_red_pouch.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Tiny Red Pouch",
|
||||
description: "Made from a single patch of dyed cloth.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("RedTiny"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Common,
|
||||
slots: 3,
|
||||
)
|
12
assets/common/items/armor/bag/troll_hide_pack.ron
Normal file
12
assets/common/items/armor/bag/troll_hide_pack.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Trollhide Pack",
|
||||
description: "Trolls were definitely hurt\nin the making of this.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("GreenLarge"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: High,
|
||||
slots: 18,
|
||||
)
|
12
assets/common/items/armor/bag/woven_red_bag.ron
Normal file
12
assets/common/items/armor/bag/woven_red_bag.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Woven Red Bag",
|
||||
description: "Made from some patches of dyed cloth.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("RedMed"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Moderate,
|
||||
slots: 15,
|
||||
)
|
8
assets/common/items/crafting_ing/cloth_scraps_red.ron
Normal file
8
assets/common/items/crafting_ing/cloth_scraps_red.ron
Normal file
@ -0,0 +1,8 @@
|
||||
ItemDef(
|
||||
name: "Red Cloth Scraps",
|
||||
description: "Dyed red with flower pigments.",
|
||||
kind: Ingredient(
|
||||
kind: "ClothScrapsRed",
|
||||
),
|
||||
quality: Common,
|
||||
)
|
8
assets/common/items/crafting_ing/leather_troll.ron
Normal file
8
assets/common/items/crafting_ing/leather_troll.ron
Normal file
@ -0,0 +1,8 @@
|
||||
ItemDef(
|
||||
name: "Troll Hide",
|
||||
description: "Looted from cave trolls.",
|
||||
kind: Ingredient(
|
||||
kind: "TrollLeather",
|
||||
),
|
||||
quality: High,
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
ItemDef(
|
||||
name: "Glowing Remains",
|
||||
description: "Looted from an evil being.\n\nWith some additional work it can surely be\nbrought back to it's former glory...",
|
||||
kind: Ingredient(
|
||||
kind: "FlayerBagDamaged",
|
||||
),
|
||||
quality: Epic,
|
||||
)
|
12
assets/common/items/debug/admin_black_hole.ron
Normal file
12
assets/common/items/debug/admin_black_hole.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Admin's Black Hole",
|
||||
description: "It just works.",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("BrownFace"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: Artifact,
|
||||
slots: 900,
|
||||
)
|
@ -1,8 +1,8 @@
|
||||
ItemDef(
|
||||
name: "Red Flower",
|
||||
description: "Roses are red...",
|
||||
description: "Can be used as a dying ingredient.",
|
||||
kind: Ingredient(
|
||||
kind: "Flower",
|
||||
kind: "FlowerRed",
|
||||
),
|
||||
quality: Common,
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
ItemDef(
|
||||
name: "Coconut",
|
||||
description: "Restores 20 health over 10 seconds\n\nReliable source of water and fat",
|
||||
description: "Restores 20 health over 10 seconds\n\nReliable source of water and fat.\n\nNaturally growing at the top of palm trees.",
|
||||
kind: Consumable(
|
||||
kind: "Coconut",
|
||||
effect: [
|
||||
|
12
assets/common/items/testing/test_bag_18_slot.ron
Normal file
12
assets/common/items/testing/test_bag_18_slot.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Test 18 slot bag",
|
||||
description: "Used for unit tests do not delete",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("TestBag18Slot"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: High,
|
||||
slots: 18,
|
||||
)
|
12
assets/common/items/testing/test_bag_9_slot.ron
Normal file
12
assets/common/items/testing/test_bag_9_slot.ron
Normal file
@ -0,0 +1,12 @@
|
||||
ItemDef(
|
||||
name: "Test 9 slot bag",
|
||||
description: "Used for unit tests do not delete",
|
||||
kind: Armor(
|
||||
(
|
||||
kind: Bag("TestBag9Slot"),
|
||||
stats: (protection: Normal(0.0)),
|
||||
)
|
||||
),
|
||||
quality: High,
|
||||
slots: 9,
|
||||
)
|
@ -6,7 +6,8 @@ ItemDef(
|
||||
kind: Dagger,
|
||||
stats: (
|
||||
equip_time_millis: 0,
|
||||
power: 1.80
|
||||
power: 1.80,
|
||||
speed: 1.0
|
||||
),
|
||||
)
|
||||
),
|
||||
|
@ -6,7 +6,8 @@ ItemDef(
|
||||
kind: Dagger,
|
||||
stats: (
|
||||
equip_time_millis: 0,
|
||||
power: 2.00
|
||||
power: 2.00,
|
||||
speed: 1.5
|
||||
),
|
||||
)
|
||||
),
|
||||
|
@ -11,6 +11,7 @@
|
||||
(1, "common.items.weapons.staff.cultist_staff"),
|
||||
(1, "common.items.weapons.hammer.cultist_purp_2h-0"),
|
||||
(1, "common.items.weapons.sword.cultist_purp_2h-0"),
|
||||
(0.25, "common.items.weapons.crafting_ing.mindflayer_bag_damaged"),
|
||||
// misc
|
||||
(1, "common.items.boss_drops.lantern"),
|
||||
(0.1, "common.items.glider.glider_purp"),
|
||||
|
@ -1,7 +1,7 @@
|
||||
[
|
||||
// Misc
|
||||
(0.25, "common.items.armor.neck.neck_1"),
|
||||
(0.2, "common.items.crafting_ing.cloth_scraps"),
|
||||
(0.5, "common.items.crafting_ing.cloth_scraps"),
|
||||
(1.0, "common.items.crafting_ing.empty_vial"),
|
||||
(0.1, "common.items.glider.glider_blue"),
|
||||
(0.1, "common.items.glider.glider_morpho"),
|
||||
@ -41,7 +41,7 @@
|
||||
// staves
|
||||
(1.00, "common.items.weapons.staff.bone_staff"),
|
||||
(1.00, "common.items.weapons.staff.amethyst_staff"),
|
||||
(0.1, "common.items.weapons.sceptre.sceptre_velorite_0"),
|
||||
(0.05, "common.items.weapons.sceptre.sceptre_velorite_0"),
|
||||
// hammers
|
||||
(0.30, "common.items.weapons.hammer.cobalt_hammer-0"),
|
||||
(0.30, "common.items.weapons.hammer.cobalt_hammer-1"),
|
||||
@ -56,7 +56,7 @@
|
||||
(0.05, "common.items.weapons.hammer.steel_hammer-4"),
|
||||
(0.05, "common.items.weapons.hammer.steel_hammer-5"),
|
||||
// bows
|
||||
(0.1, "common.items.weapons.bow.nature_ore_longbow-0"),
|
||||
(0.05, "common.items.weapons.bow.nature_ore_longbow-0"),
|
||||
|
||||
|
||||
]
|
||||
|
@ -1,7 +1,7 @@
|
||||
[
|
||||
// crafting ingredients
|
||||
(2, "common.items.crafting_ing.leather_scraps"),
|
||||
(2, "common.items.crafting_ing.cloth_scraps"),
|
||||
(4, "common.items.crafting_ing.cloth_scraps"),
|
||||
(1, "common.items.crafting_ing.empty_vial"),
|
||||
(0.10, "common.items.crafting_ing.shiny_gem"),
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
(3, "common.items.food.apple"),
|
||||
(3, "common.items.food.mushroom"),
|
||||
(3, "common.items.food.coconut"),
|
||||
(3, "common.items.crafting_ing.cloth_scraps"),
|
||||
(5, "common.items.crafting_ing.cloth_scraps"),
|
||||
// crafted
|
||||
(0.5, "common.items.food.apple_mushroom_curry"),
|
||||
(0.5, "common.items.food.apple_stick"),
|
||||
@ -65,6 +65,9 @@
|
||||
//gloves
|
||||
(0.50, "common.items.armor.hand.leather_0"),
|
||||
(0.50, "common.items.armor.hand.leather_2"),
|
||||
//backpack
|
||||
(0.001, "common.items.armor.back.backpack_0"),
|
||||
(0.1, "common.items.armor.bag.heavy_seabag"),
|
||||
// Common Weapons
|
||||
// swords
|
||||
(0.4, "common.items.weapons.sword.wood_sword"),
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Crafting Ingredients
|
||||
(2, "common.items.crafting_ing.empty_vial"),
|
||||
(0.10, "common.items.crafting_ing.shiny_gem"),
|
||||
(2, "common.items.crafting_ing.cloth_scraps"),
|
||||
(4, "common.items.crafting_ing.cloth_scraps"),
|
||||
// Consumables
|
||||
(0.2, "common.items.consumable.potion_minor"),
|
||||
// Ring
|
||||
|
5
assets/common/loot_tables/loot_table_maneater.ron
Normal file
5
assets/common/loot_tables/loot_table_maneater.ron
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
(1, "common.items.flowers.red"),
|
||||
(1, "common.items.crafting_ing.twigs"),
|
||||
(0.5, "common.items.food.coconut"),
|
||||
]
|
19
assets/common/loot_tables/loot_table_saurok.ron
Normal file
19
assets/common/loot_tables/loot_table_saurok.ron
Normal file
@ -0,0 +1,19 @@
|
||||
[
|
||||
(2, "common.items.crafting_ing.empty_vial"),
|
||||
(0.01, "common.items.crafting_ing.shiny_gem"),
|
||||
(3, "common.items.crafting_ing.cloth_scraps"),
|
||||
(2, "common.items.crafting_ing.leather_scraps"),
|
||||
// Consumables
|
||||
(0.5, "common.items.consumable.potion_minor"),
|
||||
// Ring
|
||||
(0.2, "common.items.armor.ring.ring_gold_0"),
|
||||
// Utility
|
||||
(0.1, "common.items.utility.collar"),
|
||||
// Bag
|
||||
(0.1, "common.items.armor.bag.liana_kit"),
|
||||
// Food
|
||||
(2.0, "common.items.food.coconut"),
|
||||
(0.3, "common.items.food.apple_mushroom_curry"),
|
||||
(0.6, "common.items.food.apple_stick"),
|
||||
(0.8, "common.items.food.mushroom_stick"),
|
||||
]
|
4
assets/common/loot_tables/loot_table_troll.ron
Normal file
4
assets/common/loot_tables/loot_table_troll.ron
Normal file
@ -0,0 +1,4 @@
|
||||
[
|
||||
(1, "common.items.crafting_ing.leather_troll"),
|
||||
(0.5, "common.items.crafting_ing.leather_scraps"),
|
||||
]
|
@ -25,9 +25,9 @@
|
||||
(0.30, "common.items.weapons.hammer.cobalt_hammer-1"),
|
||||
(0.15, "common.items.weapons.hammer.runic_hammer"),
|
||||
(0.15, "common.items.weapons.hammer.ramshead_hammer"),
|
||||
(0.10, "common.items.weapons.hammer.mjolnir"),
|
||||
(0.01, "common.items.weapons.hammer.mjolnir"),
|
||||
// bows
|
||||
(0.60, "common.items.weapons.bow.horn_longbow-0"),
|
||||
(0.30, "common.items.weapons.bow.iron_longbow-0"),
|
||||
(0.10, "common.items.weapons.bow.rare_longbow"),
|
||||
(0.05, "common.items.weapons.bow.rare_longbow"),
|
||||
]
|
@ -1,42 +1,321 @@
|
||||
{
|
||||
// Tools
|
||||
"crafting_hammer": (("common.items.crafting_tools.craftsman_hammer", 1),[("common.items.crafting_ing.twigs", 6), ("common.items.crafting_ing.stones", 6)]),
|
||||
"mortar_pestle": (("common.items.crafting_tools.mortar_pestle", 1), [("common.items.crafting_ing.stones", 6), ("common.items.food.coconut", 2), ("common.items.crafting_tools.craftsman_hammer", 0)]),
|
||||
"sewing_set": (("common.items.crafting_tools.sewing_set", 1),[("common.items.crafting_ing.leather_scraps", 2), ("common.items.crafting_ing.twigs", 4), ("common.items.crafting_ing.stones", 2), ("common.items.crafting_ing.shiny_gem", 1)]),
|
||||
// Ore and more
|
||||
"velorite_frag": (("common.items.ore.veloritefrag", 2), [("common.items.ore.velorite", 1), ("common.items.crafting_tools.craftsman_hammer", 0)]),
|
||||
//Potions
|
||||
"potion_s": (("common.items.consumable.potion_minor", 1), [("common.items.crafting_ing.empty_vial", 1), ("common.items.food.apple", 4), ("common.items.crafting_ing.honey", 1)]),
|
||||
"potion_m": (("common.items.consumable.potion_med", 1), [("common.items.consumable.potion_minor", 2), ("common.items.ore.veloritefrag", 4)]),
|
||||
"collar_basic": (("common.items.utility.collar", 1), [("common.items.crafting_ing.leather_scraps", 5), ("common.items.crafting_ing.shiny_gem", 1)]),
|
||||
"bomb_coconut": (("common.items.utility.bomb", 1), [("common.items.crafting_ing.stones", 10), ("common.items.food.coconut", 2), ("common.items.ore.veloritefrag", 2), ("common.items.crafting_tools.mortar_pestle", 0)]),
|
||||
// Firework
|
||||
"firework_blue": (("common.items.utility.firework_blue", 1), [("common.items.crafting_ing.twigs", 1), ("common.items.crafting_ing.stones", 1), ("common.items.food.coconut", 1), ("common.items.ore.veloritefrag", 1), ("common.items.crafting_tools.mortar_pestle", 0)]),
|
||||
"firework_green": (("common.items.utility.firework_green", 1), [("common.items.crafting_ing.twigs", 1), ("common.items.crafting_ing.stones", 1), ("common.items.food.coconut", 1), ("common.items.ore.veloritefrag", 1), ("common.items.crafting_tools.mortar_pestle", 0)]),
|
||||
"firework_purple": (("common.items.utility.firework_purple", 1), [("common.items.crafting_ing.twigs", 1), ("common.items.crafting_ing.stones", 1), ("common.items.food.coconut", 1), ("common.items.ore.veloritefrag", 1), ("common.items.crafting_tools.mortar_pestle", 0)]),
|
||||
"firework_red": (("common.items.utility.firework_red", 1), [("common.items.crafting_ing.twigs", 1), ("common.items.crafting_ing.stones", 1), ("common.items.food.coconut", 1), ("common.items.ore.veloritefrag", 1), ("common.items.crafting_tools.mortar_pestle", 0)]),
|
||||
"firework_yellow": (("common.items.utility.firework_yellow", 1), [("common.items.crafting_ing.twigs", 1), ("common.items.crafting_ing.stones", 1), ("common.items.food.coconut", 1), ("common.items.ore.veloritefrag", 1), ("common.items.crafting_tools.mortar_pestle", 0)]),
|
||||
// Food
|
||||
"apple_shroom_curry": (("common.items.food.apple_mushroom_curry", 1), [("common.items.food.mushroom", 8), ("common.items.food.coconut", 1), ("common.items.food.apple", 4), ("common.items.crafting_tools.mortar_pestle", 0)]),
|
||||
"apples_stick": (("common.items.food.apple_stick", 1),[("common.items.crafting_ing.twigs", 2), ("common.items.food.apple", 2)]),
|
||||
"mushroom_stick": (("common.items.food.mushroom_stick", 1),[("common.items.crafting_ing.twigs", 2), ("common.items.food.mushroom", 3)]),
|
||||
"sunflower_icetea": (("common.items.food.sunflower_icetea", 4),[("common.items.crafting_ing.empty_vial", 1), ("common.items.crafting_ing.icy_fang", 1),("common.items.flowers.sunflower", 4), ("common.items.crafting_ing.honey", 1)]),
|
||||
// Gliders
|
||||
"Leaves Glider": (("common.items.glider.glider_leaves", 1),[("common.items.crafting_ing.twigs", 5), ("common.items.crafting_ing.leather_scraps", 5), ("common.items.crafting_ing.cloth_scraps", 5), ("common.items.crafting_ing.shiny_gem", 1), ("common.items.crafting_tools.craftsman_hammer", 0),("common.items.crafting_tools.sewing_set", 0)]),
|
||||
"Sand Raptor Wings": (("common.items.glider.glider_sandraptor", 1),[("common.items.crafting_ing.raptor_feather", 6), ("common.items.crafting_ing.twigs", 5), ("common.items.crafting_ing.leather_scraps", 5), ("common.items.crafting_ing.cloth_scraps", 5), ("common.items.crafting_ing.shiny_gem", 1), ("common.items.crafting_tools.craftsman_hammer", 0),("common.items.crafting_tools.sewing_set", 0)]),
|
||||
"Snow Raptor Wings": (("common.items.glider.glider_snowraptor", 1),[("common.items.crafting_ing.raptor_feather", 6), ("common.items.crafting_ing.twigs", 5), ("common.items.crafting_ing.leather_scraps", 5), ("common.items.crafting_ing.cloth_scraps", 5), ("common.items.crafting_ing.icy_fang", 1), ("common.items.crafting_ing.shiny_gem", 1), ("common.items.crafting_tools.craftsman_hammer", 0),("common.items.crafting_tools.sewing_set", 0)]),
|
||||
"Wood Raptor Wings": (("common.items.glider.glider_woodraptor", 1),[("common.items.crafting_ing.raptor_feather", 6), ("common.items.crafting_ing.twigs", 15), ("common.items.crafting_ing.leather_scraps", 5), ("common.items.crafting_ing.cloth_scraps", 5), ("common.items.crafting_ing.shiny_gem", 1), ("common.items.crafting_tools.craftsman_hammer", 0),("common.items.crafting_tools.sewing_set", 0)]),
|
||||
// Weapons
|
||||
"velorite_sceptre": (("common.items.weapons.sceptre.sceptre_velorite_0", 1),[("common.items.crafting_ing.twigs", 20), ("common.items.ore.veloritefrag", 10), ("common.items.crafting_ing.shiny_gem", 4), ("common.items.crafting_tools.craftsman_hammer", 0)]),
|
||||
// Enhanced starting weapons
|
||||
"better bow": (("common.items.weapons.bow.wood_shortbow-0", 1), [("common.items.crafting_ing.leather_scraps", 8),("common.items.crafting_ing.twigs", 6), ("common.items.crafting_ing.stones", 0)]),
|
||||
"better sword": (("common.items.weapons.sword.wood_sword", 1), [("common.items.crafting_ing.leather_scraps", 4),("common.items.crafting_ing.twigs", 10), ("common.items.ore.veloritefrag", 1), ("common.items.crafting_ing.stones", 0)]),
|
||||
// Adventurer/Beginner Leather Set
|
||||
"adventure back": (("common.items.armor.back.leather_adventurer", 1),[("common.items.crafting_ing.leather_scraps", 4)]),
|
||||
"adventure belt": (("common.items.armor.belt.leather_adventurer", 1),[("common.items.crafting_ing.leather_scraps", 2)]),
|
||||
"adventure chest": (("common.items.armor.chest.leather_adventurer", 1),[("common.items.crafting_ing.leather_scraps", 12)]),
|
||||
"adventure feet": (("common.items.armor.foot.leather_adventurer", 1),[("common.items.crafting_ing.leather_scraps", 3)]),
|
||||
"adventure hands": (("common.items.armor.hand.leather_adventurer", 1),[("common.items.crafting_ing.leather_scraps", 4)]),
|
||||
"adventure pants": (("common.items.armor.pants.leather_adventurer", 1),[("common.items.crafting_ing.leather_scraps", 8)]),
|
||||
"adventure shoulder": (("common.items.armor.shoulder.leather_adventurer", 1),[("common.items.crafting_ing.leather_scraps", 12)]),
|
||||
"crafting_hammer": (
|
||||
("common.items.crafting_tools.craftsman_hammer", 1),
|
||||
[
|
||||
("common.items.crafting_ing.twigs", 6),
|
||||
("common.items.crafting_ing.stones", 6),
|
||||
],
|
||||
),
|
||||
"mortar_pestle": (
|
||||
("common.items.crafting_tools.mortar_pestle", 1),
|
||||
[
|
||||
("common.items.crafting_ing.stones", 6),
|
||||
("common.items.food.coconut", 1),
|
||||
("common.items.crafting_tools.craftsman_hammer", 0),
|
||||
],
|
||||
),
|
||||
"sewing_set": (
|
||||
("common.items.crafting_tools.sewing_set", 1),
|
||||
[
|
||||
("common.items.crafting_ing.leather_scraps", 2),
|
||||
("common.items.crafting_ing.twigs", 4),
|
||||
("common.items.crafting_ing.stones", 2),
|
||||
],
|
||||
),
|
||||
"velorite_frag": (
|
||||
("common.items.ore.veloritefrag", 2),
|
||||
[
|
||||
("common.items.ore.velorite", 1),
|
||||
("common.items.crafting_tools.craftsman_hammer", 0),
|
||||
],
|
||||
),
|
||||
"potion_s": (
|
||||
("common.items.consumable.potion_minor", 1),
|
||||
[
|
||||
("common.items.crafting_ing.empty_vial", 1),
|
||||
("common.items.food.apple", 4),
|
||||
("common.items.crafting_ing.honey", 1),
|
||||
],
|
||||
),
|
||||
"potion_m": (
|
||||
("common.items.consumable.potion_med", 1),
|
||||
[
|
||||
("common.items.consumable.potion_minor", 2),
|
||||
("common.items.ore.veloritefrag", 4),
|
||||
],
|
||||
),
|
||||
"collar_basic": (
|
||||
("common.items.utility.collar", 1),
|
||||
[
|
||||
("common.items.crafting_ing.leather_scraps", 5),
|
||||
("common.items.crafting_ing.shiny_gem", 1),
|
||||
],
|
||||
),
|
||||
"bomb_coconut": (
|
||||
("common.items.utility.bomb", 1),
|
||||
[
|
||||
("common.items.crafting_ing.stones", 10),
|
||||
("common.items.food.coconut", 2),
|
||||
("common.items.ore.veloritefrag", 2),
|
||||
("common.items.crafting_tools.mortar_pestle", 0),
|
||||
],
|
||||
),
|
||||
"firework_blue": (
|
||||
("common.items.utility.firework_blue", 1),
|
||||
[
|
||||
("common.items.crafting_ing.twigs", 1),
|
||||
("common.items.crafting_ing.stones", 1),
|
||||
("common.items.food.coconut", 1),
|
||||
("common.items.ore.veloritefrag", 1),
|
||||
("common.items.crafting_tools.mortar_pestle", 0),
|
||||
],
|
||||
),
|
||||
"firework_green": (
|
||||
("common.items.utility.firework_green", 1),
|
||||
[
|
||||
("common.items.crafting_ing.twigs", 1),
|
||||
("common.items.crafting_ing.stones", 1),
|
||||
("common.items.food.coconut", 1),
|
||||
("common.items.ore.veloritefrag", 1),
|
||||
("common.items.crafting_tools.mortar_pestle", 0),
|
||||
],
|
||||
),
|
||||
"firework_purple": (
|
||||
("common.items.utility.firework_purple", 1),
|
||||
[
|
||||
("common.items.crafting_ing.twigs", 1),
|
||||
("common.items.crafting_ing.stones", 1),
|
||||
("common.items.food.coconut", 1),
|
||||
("common.items.ore.veloritefrag", 1),
|
||||
("common.items.crafting_tools.mortar_pestle", 0),
|
||||
],
|
||||
),
|
||||
"firework_red": (
|
||||
("common.items.utility.firework_red", 1),
|
||||
[
|
||||
("common.items.crafting_ing.twigs", 1),
|
||||
("common.items.crafting_ing.stones", 1),
|
||||
("common.items.food.coconut", 1),
|
||||
("common.items.ore.veloritefrag", 1),
|
||||
("common.items.crafting_tools.mortar_pestle", 0),
|
||||
],
|
||||
),
|
||||
"firework_yellow": (
|
||||
("common.items.utility.firework_yellow", 1),
|
||||
[
|
||||
("common.items.crafting_ing.twigs", 1),
|
||||
("common.items.crafting_ing.stones", 1),
|
||||
("common.items.food.coconut", 1),
|
||||
("common.items.ore.veloritefrag", 1),
|
||||
("common.items.crafting_tools.mortar_pestle", 0),
|
||||
],
|
||||
),
|
||||
"apple_shroom_curry": (
|
||||
("common.items.food.apple_mushroom_curry", 1),
|
||||
[
|
||||
("common.items.food.mushroom", 8),
|
||||
("common.items.food.coconut", 1),
|
||||
("common.items.food.apple", 4),
|
||||
("common.items.crafting_tools.mortar_pestle", 0),
|
||||
],
|
||||
),
|
||||
"apples_stick": (
|
||||
("common.items.food.apple_stick", 1),
|
||||
[("common.items.crafting_ing.twigs", 2), ("common.items.food.apple", 2)],
|
||||
),
|
||||
"mushroom_stick": (
|
||||
("common.items.food.mushroom_stick", 1),
|
||||
[
|
||||
("common.items.crafting_ing.twigs", 2),
|
||||
("common.items.food.mushroom", 3),
|
||||
],
|
||||
),
|
||||
"sunflower_icetea": (
|
||||
("common.items.food.sunflower_icetea", 4),
|
||||
[
|
||||
("common.items.crafting_ing.empty_vial", 1),
|
||||
("common.items.crafting_ing.icy_fang", 1),
|
||||
("common.items.flowers.sunflower", 4),
|
||||
("common.items.crafting_ing.honey", 1),
|
||||
],
|
||||
),
|
||||
"Leaves Glider": (
|
||||
("common.items.glider.glider_leaves", 1),
|
||||
[
|
||||
("common.items.crafting_ing.twigs", 5),
|
||||
("common.items.crafting_ing.leather_scraps", 5),
|
||||
("common.items.crafting_ing.cloth_scraps", 5),
|
||||
("common.items.crafting_ing.shiny_gem", 1),
|
||||
("common.items.crafting_tools.craftsman_hammer", 0),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"Sand Raptor Wings": (
|
||||
("common.items.glider.glider_sandraptor", 1),
|
||||
[
|
||||
("common.items.crafting_ing.raptor_feather", 6),
|
||||
("common.items.crafting_ing.twigs", 5),
|
||||
("common.items.crafting_ing.leather_scraps", 5),
|
||||
("common.items.crafting_ing.cloth_scraps", 5),
|
||||
("common.items.crafting_ing.shiny_gem", 1),
|
||||
("common.items.crafting_tools.craftsman_hammer", 0),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"Snow Raptor Wings": (
|
||||
("common.items.glider.glider_snowraptor", 1),
|
||||
[
|
||||
("common.items.crafting_ing.raptor_feather", 6),
|
||||
("common.items.crafting_ing.twigs", 5),
|
||||
("common.items.crafting_ing.leather_scraps", 5),
|
||||
("common.items.crafting_ing.cloth_scraps", 5),
|
||||
("common.items.crafting_ing.icy_fang", 1),
|
||||
("common.items.crafting_ing.shiny_gem", 1),
|
||||
("common.items.crafting_tools.craftsman_hammer", 0),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"Wood Raptor Wings": (
|
||||
("common.items.glider.glider_woodraptor", 1),
|
||||
[
|
||||
("common.items.crafting_ing.raptor_feather", 6),
|
||||
("common.items.crafting_ing.twigs", 15),
|
||||
("common.items.crafting_ing.leather_scraps", 5),
|
||||
("common.items.crafting_ing.cloth_scraps", 5),
|
||||
("common.items.crafting_ing.shiny_gem", 1),
|
||||
("common.items.crafting_tools.craftsman_hammer", 0),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"velorite_sceptre": (
|
||||
("common.items.weapons.sceptre.sceptre_velorite_0", 1),
|
||||
[
|
||||
("common.items.crafting_ing.twigs", 20),
|
||||
("common.items.ore.veloritefrag", 10),
|
||||
("common.items.crafting_ing.shiny_gem", 4),
|
||||
("common.items.crafting_tools.craftsman_hammer", 0),
|
||||
],
|
||||
),
|
||||
"better bow": (
|
||||
("common.items.weapons.bow.wood_shortbow-0", 1),
|
||||
[
|
||||
("common.items.crafting_ing.leather_scraps", 8),
|
||||
("common.items.crafting_ing.twigs", 6),
|
||||
("common.items.crafting_ing.stones", 0),
|
||||
],
|
||||
),
|
||||
"better sword": (
|
||||
("common.items.weapons.sword.wood_sword", 1),
|
||||
[
|
||||
("common.items.crafting_ing.leather_scraps", 4),
|
||||
("common.items.crafting_ing.twigs", 10),
|
||||
("common.items.ore.veloritefrag", 1),
|
||||
("common.items.crafting_ing.stones", 0),
|
||||
],
|
||||
),
|
||||
"adventure back": (
|
||||
("common.items.armor.back.leather_adventurer", 1),
|
||||
[("common.items.crafting_ing.leather_scraps", 4)],
|
||||
),
|
||||
"adventure belt": (
|
||||
("common.items.armor.belt.leather_adventurer", 1),
|
||||
[("common.items.crafting_ing.leather_scraps", 2)],
|
||||
),
|
||||
"adventure chest": (
|
||||
("common.items.armor.chest.leather_adventurer", 1),
|
||||
[("common.items.crafting_ing.leather_scraps", 12)],
|
||||
),
|
||||
"adventure feet": (
|
||||
("common.items.armor.foot.leather_adventurer", 1),
|
||||
[("common.items.crafting_ing.leather_scraps", 3)],
|
||||
),
|
||||
"adventure hands": (
|
||||
("common.items.armor.hand.leather_adventurer", 1),
|
||||
[("common.items.crafting_ing.leather_scraps", 4)],
|
||||
),
|
||||
"adventure pants": (
|
||||
("common.items.armor.pants.leather_adventurer", 1),
|
||||
[("common.items.crafting_ing.leather_scraps", 8)],
|
||||
),
|
||||
"adventure shoulder": (
|
||||
("common.items.armor.shoulder.leather_adventurer", 1),
|
||||
[("common.items.crafting_ing.leather_scraps", 12)],
|
||||
),
|
||||
"red cloth": (
|
||||
("common.items.crafting_ing.cloth_scraps_red", 1),
|
||||
[
|
||||
("common.items.crafting_ing.cloth_scraps", 1),
|
||||
("common.items.flowers.red", 1),
|
||||
("common.items.crafting_tools.mortar_pestle", 0),
|
||||
],
|
||||
),
|
||||
"tiny red pouch": (
|
||||
("common.items.armor.bag.tiny_red_pouch", 1),
|
||||
[
|
||||
("common.items.crafting_ing.cloth_scraps_red", 3),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"tiny leather pouch": (
|
||||
("common.items.armor.bag.tiny_leather_pouch", 1),
|
||||
[
|
||||
("common.items.crafting_ing.leather_scraps", 6),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"knitted red pouch": (
|
||||
("common.items.armor.bag.knitted_red_pouch", 1),
|
||||
[
|
||||
("common.items.crafting_ing.cloth_scraps_red", 3),
|
||||
("common.items.armor.bag.tiny_red_pouch", 2),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"woven red bag": (
|
||||
("common.items.armor.bag.woven_red_bag", 1),
|
||||
[
|
||||
("common.items.crafting_ing.cloth_scraps_red", 6),
|
||||
("common.items.armor.bag.knitted_red_pouch", 1),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"traveler backpack": (
|
||||
("common.items.armor.back.backpack_0", 1),
|
||||
[
|
||||
("common.items.crafting_ing.shiny_gem", 2),
|
||||
("common.items.crafting_ing.twigs", 2),
|
||||
("common.items.crafting_ing.cloth_scraps", 3),
|
||||
("common.items.crafting_ing.leather_scraps", 3),
|
||||
("common.items.armor.bag.tiny_leather_pouch", 2),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"sturdy red backpack": (
|
||||
("common.items.armor.bag.sturdy_red_backpack", 1),
|
||||
[
|
||||
("common.items.crafting_ing.shiny_gem", 2),
|
||||
("common.items.crafting_ing.cloth_scraps_red", 3),
|
||||
("common.items.armor.bag.woven_red_bag", 1),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"troll hide pack": (
|
||||
("common.items.armor.bag.troll_hide_pack", 1),
|
||||
[
|
||||
("common.items.crafting_ing.leather_troll", 10),
|
||||
("common.items.crafting_ing.leather_scraps", 10),
|
||||
("common.items.crafting_ing.shiny_gem", 1),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
"Mindflayer Spellbag": (
|
||||
("common.items.armor.bag.mindflayer_spellbag", 1),
|
||||
[
|
||||
("common.items.crafting_ing.mindflayer_bag_damaged", 1),
|
||||
("common.items.crafting_ing.leather_scraps", 10),
|
||||
("common.items.crafting_ing.shiny_gem", 4),
|
||||
("common.items.ore.veloritefrag", 10),
|
||||
("common.items.crafting_tools.sewing_set", 0),
|
||||
],
|
||||
),
|
||||
}
|
||||
|
BIN
assets/voxygen/element/bag/bot.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/bag/bot.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/bag/mid.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/bag/mid.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/bag/slot.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/bag/slot.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/bag/top.vox
(Stored with Git LFS)
BIN
assets/voxygen/element/bag/top.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/buttons/inv_collapse.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/inv_collapse.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/inv_collapse_hover.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/inv_collapse_hover.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/inv_collapse_press.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/inv_collapse_press.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/inv_expand.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/inv_expand.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/inv_expand_hover.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/inv_expand_hover.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/inv_expand_press.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/inv_expand_press.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/key_button.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/key_button.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/buttons/key_button_press.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/buttons/key_button_press.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/prompt_dialog_bot.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/prompt_dialog_bot.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/prompt_dialog_mid.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/prompt_dialog_mid.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/prompt_dialog_top.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/prompt_dialog_top.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/bag.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/bag.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/fire_bolt_1.png
(Stored with Git LFS)
BIN
assets/voxygen/element/icons/fire_bolt_1.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/icons/fire_spell_0.png
(Stored with Git LFS)
BIN
assets/voxygen/element/icons/fire_spell_0.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_blue.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_blue.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_blue_face.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_blue_face.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_brown_face.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_brown_face.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_green_large.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_green_large.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_green_mid.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_green_mid.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_large.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_large.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_leather_large.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_leather_large.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_leather_small.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_leather_small.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_med.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_med.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_red_face.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_red_face.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_skull.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_skull.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_small.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_bag_small.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_bag_tiny.png
(Stored with Git LFS)
Executable file
BIN
assets/voxygen/element/icons/item_bag_tiny.png
(Stored with Git LFS)
Executable file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_cloth_red.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_cloth_red.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_flayer_soul.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_flayer_soul.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/item_leather_green.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/item_leather_green.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/skill_charge_2.png
(Stored with Git LFS)
BIN
assets/voxygen/element/icons/skill_charge_2.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/inv_bg.png
(Stored with Git LFS)
BIN
assets/voxygen/element/misc_bg/inv_bg.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/inv_bg_0.png
(Stored with Git LFS)
BIN
assets/voxygen/element/misc_bg/inv_bg_0.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/inv_bg_bag.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/inv_bg_bag.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/inv_frame_bag.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/misc_bg/inv_frame_bag.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/inv_runes.png
(Stored with Git LFS)
BIN
assets/voxygen/element/misc_bg/inv_runes.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/misc_bg/inv_slots.png
(Stored with Git LFS)
BIN
assets/voxygen/element/misc_bg/inv_slots.png
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/element/slider/scrollbar_1.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/slider/scrollbar_1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
0
assets/voxygen/i18n/en.ron
Normal file
0
assets/voxygen/i18n/en.ron
Normal file
@ -59,6 +59,7 @@
|
||||
"You can toggle showing your amount of health on the healthbar in the settings.",
|
||||
"In order to see your stats click the 'Stats' button in the inventory.",
|
||||
"Sit near a campfire (with the 'K' key) to rest - receiving a slow heal-over-time.",
|
||||
"Need more bags or better armor to continue your journey? Press 'C' to open the crafting menu!",
|
||||
],
|
||||
"npc.speech.villager_under_attack": [
|
||||
"Help, I'm under attack!",
|
||||
|
@ -24,6 +24,7 @@
|
||||
"hud.bag.feet": "Feet",
|
||||
"hud.bag.mainhand": "Mainhand",
|
||||
"hud.bag.offhand": "Offhand",
|
||||
"hud.bag.bag": "Bag",
|
||||
},
|
||||
|
||||
|
||||
|
@ -1324,6 +1324,46 @@
|
||||
"voxel.armor.head.assa_mask-0",
|
||||
(0.0, 0.0, 0.0), (-90.0, 180.0, 0.0), 1.0,
|
||||
),
|
||||
// Bags
|
||||
Armor(Bag("RedFace")): Png (
|
||||
"element.icons.item_bag_red_face",
|
||||
),
|
||||
Armor(Bag("BrownFace")): Png (
|
||||
"element.icons.item_bag_brown_face",
|
||||
),
|
||||
Armor(Bag("BlueFace")): Png (
|
||||
"element.icons.item_bag_blue_face",
|
||||
),
|
||||
Armor(Bag("PurpleSkull")): Png (
|
||||
"element.icons.item_bag_skull",
|
||||
),
|
||||
Armor(Bag("GreenLarge")): Png (
|
||||
"element.icons.item_bag_green_large",
|
||||
),
|
||||
Armor(Bag("LeatherLarge")): Png (
|
||||
"element.icons.item_bag_leather_large",
|
||||
),
|
||||
Armor(Bag("GreenMid")): Png (
|
||||
"element.icons.item_bag_green_mid",
|
||||
),
|
||||
Armor(Bag("LeatherSmall")): Png (
|
||||
"element.icons.item_bag_leather_small",
|
||||
),
|
||||
Armor(Bag("RedLarge")): Png (
|
||||
"element.icons.item_bag_large",
|
||||
),
|
||||
Armor(Bag("RedMed")): Png (
|
||||
"element.icons.item_bag_med",
|
||||
),
|
||||
Armor(Bag("RedSmall")): Png (
|
||||
"element.icons.item_bag_small",
|
||||
),
|
||||
Armor(Bag("RedTiny")): Png (
|
||||
"element.icons.item_bag_tiny",
|
||||
),
|
||||
Armor(Bag("BluePouch")): Png (
|
||||
"element.icons.item_bag_blue",
|
||||
),
|
||||
// Consumables
|
||||
Consumable("Apple"): Png(
|
||||
"element.icons.item_apple",
|
||||
@ -1419,6 +1459,10 @@
|
||||
"voxel.sprite.flowers.sunflower_1",
|
||||
(-2.0, -0.5, -1.0), (-60.0, 40.0, 20.0), 1.1,
|
||||
),
|
||||
Ingredient("FlowerRed"): VoxTrans(
|
||||
"voxel.sprite.flowers.flower_red-4",
|
||||
(0.0, 0.5, 0.0), (-70.0, 10.0, 0.0), 0.8,
|
||||
),
|
||||
Ingredient("Sunflower"): VoxTrans(
|
||||
"voxel.sprite.flowers.sunflower_1",
|
||||
(0.0, -1.0, 0.0), (-50.0, 40.0, 20.0), 0.8,
|
||||
@ -1434,6 +1478,9 @@
|
||||
Ingredient("IcyShard"): Png(
|
||||
"element.icons.item_ice_shard",
|
||||
),
|
||||
Ingredient("FlayerBagDamaged"): Png(
|
||||
"element.icons.item_flayer_soul",
|
||||
),
|
||||
Ingredient("RaptorFeather"): Png(
|
||||
"element.icons.item_raptor_feather",
|
||||
),
|
||||
@ -1447,9 +1494,15 @@
|
||||
Ingredient("LeatherScraps"): Png(
|
||||
"element.icons.item_leather0",
|
||||
),
|
||||
Ingredient("TrollLeather"): Png(
|
||||
"element.icons.item_leather_green",
|
||||
),
|
||||
Ingredient("ClothScraps"): Png(
|
||||
"element.icons.item_cloth0",
|
||||
),
|
||||
Ingredient("ClothScrapsRed"): Png(
|
||||
"element.icons.item_cloth_red",
|
||||
),
|
||||
Ingredient("ShinyGem"): Png(
|
||||
"element.icons.gem",
|
||||
),
|
||||
@ -1479,15 +1532,15 @@
|
||||
),
|
||||
Glider("SandRaptor"): VoxTrans(
|
||||
"voxel.glider.glider_sandraptor",
|
||||
(6.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 1.1,
|
||||
(6.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 1.1,
|
||||
),
|
||||
Glider("SnowRaptor"): VoxTrans(
|
||||
"voxel.glider.glider_snowraptor",
|
||||
(6.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 1.1,
|
||||
(6.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 1.1,
|
||||
),
|
||||
Glider("WoodRaptor"): VoxTrans(
|
||||
"voxel.glider.glider_woodraptor",
|
||||
(6.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 1.1,
|
||||
(6.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 1.1,
|
||||
),
|
||||
Glider("Purple0"): VoxTrans(
|
||||
"voxel.glider.glider_cultists",
|
||||
|
@ -933,8 +933,6 @@ impl Client {
|
||||
|
||||
pub fn inventories(&self) -> ReadStorage<comp::Inventory> { self.state.read_storage() }
|
||||
|
||||
pub fn loadouts(&self) -> ReadStorage<comp::Loadout> { self.state.read_storage() }
|
||||
|
||||
/// Send a chat message to the server.
|
||||
pub fn send_chat(&mut self, message: String) {
|
||||
match validate_chat_msg(&message) {
|
||||
@ -1455,11 +1453,10 @@ impl Client {
|
||||
self.presence = None;
|
||||
self.clean_state();
|
||||
},
|
||||
ServerGeneral::InventoryUpdate(mut inventory, event) => {
|
||||
ServerGeneral::InventoryUpdate(inventory, event) => {
|
||||
match event {
|
||||
InventoryUpdateEvent::CollectFailed => {},
|
||||
_ => {
|
||||
inventory.recount_items();
|
||||
// Push the updated inventory component to the client
|
||||
self.state.write_component(self.entity, inventory);
|
||||
},
|
||||
|
@ -19,6 +19,7 @@ sum_type! {
|
||||
Energy(comp::Energy),
|
||||
Health(comp::Health),
|
||||
LightEmitter(comp::LightEmitter),
|
||||
Inventory(comp::Inventory),
|
||||
Item(comp::Item),
|
||||
Scale(comp::Scale),
|
||||
Group(comp::Group),
|
||||
@ -28,7 +29,6 @@ sum_type! {
|
||||
Collider(comp::Collider),
|
||||
Gravity(comp::Gravity),
|
||||
Sticky(comp::Sticky),
|
||||
Loadout(comp::Loadout),
|
||||
CharacterState(comp::CharacterState),
|
||||
Pos(comp::Pos),
|
||||
Vel(comp::Vel),
|
||||
@ -51,6 +51,7 @@ sum_type! {
|
||||
Energy(PhantomData<comp::Energy>),
|
||||
Health(PhantomData<comp::Health>),
|
||||
LightEmitter(PhantomData<comp::LightEmitter>),
|
||||
Inventory(PhantomData<comp::Inventory>),
|
||||
Item(PhantomData<comp::Item>),
|
||||
Scale(PhantomData<comp::Scale>),
|
||||
Group(PhantomData<comp::Group>),
|
||||
@ -60,7 +61,6 @@ sum_type! {
|
||||
Collider(PhantomData<comp::Collider>),
|
||||
Gravity(PhantomData<comp::Gravity>),
|
||||
Sticky(PhantomData<comp::Sticky>),
|
||||
Loadout(PhantomData<comp::Loadout>),
|
||||
CharacterState(PhantomData<comp::CharacterState>),
|
||||
Pos(PhantomData<comp::Pos>),
|
||||
Vel(PhantomData<comp::Vel>),
|
||||
@ -83,6 +83,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Health(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Inventory(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Group(comp) => sync::handle_insert(comp, entity, world),
|
||||
@ -92,7 +93,6 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Collider(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Loadout(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Pos(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world),
|
||||
@ -113,6 +113,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Health(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Inventory(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Group(comp) => sync::handle_modify(comp, entity, world),
|
||||
@ -122,7 +123,6 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Collider(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Loadout(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Pos(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world),
|
||||
@ -145,6 +145,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPhantom::LightEmitter(_) => {
|
||||
sync::handle_remove::<comp::LightEmitter>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Inventory(_) => sync::handle_remove::<comp::Inventory>(entity, world),
|
||||
EcsCompPhantom::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
|
||||
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
|
||||
EcsCompPhantom::Group(_) => sync::handle_remove::<comp::Group>(entity, world),
|
||||
@ -154,7 +155,6 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPhantom::Collider(_) => sync::handle_remove::<comp::Collider>(entity, world),
|
||||
EcsCompPhantom::Gravity(_) => sync::handle_remove::<comp::Gravity>(entity, world),
|
||||
EcsCompPhantom::Sticky(_) => sync::handle_remove::<comp::Sticky>(entity, world),
|
||||
EcsCompPhantom::Loadout(_) => sync::handle_remove::<comp::Loadout>(entity, world),
|
||||
EcsCompPhantom::CharacterState(_) => {
|
||||
sync::handle_remove::<comp::CharacterState>(entity, world)
|
||||
},
|
||||
|
@ -102,6 +102,7 @@ fn get_armor_kind(kind: &ArmorKind) -> String {
|
||||
ArmorKind::Neck(_) => "Neck".to_string(),
|
||||
ArmorKind::Head(_) => "Head".to_string(),
|
||||
ArmorKind::Tabard(_) => "Tabard".to_string(),
|
||||
ArmorKind::Bag(_) => "Bag".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,6 +119,7 @@ fn get_armor_kind_kind(kind: &ArmorKind) -> String {
|
||||
ArmorKind::Neck(x) => x.clone(),
|
||||
ArmorKind::Head(x) => x.clone(),
|
||||
ArmorKind::Tabard(x) => x.clone(),
|
||||
ArmorKind::Bag(x) => x.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Structs representing a playable Character
|
||||
|
||||
use crate::comp;
|
||||
use crate::{comp, comp::inventory::Inventory};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The limit on how many characters that a player can have
|
||||
@ -21,5 +21,5 @@ pub struct CharacterItem {
|
||||
pub character: Character,
|
||||
pub body: comp::Body,
|
||||
pub level: usize,
|
||||
pub loadout: comp::Loadout,
|
||||
pub inventory: Inventory,
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ pub enum ChatCommand {
|
||||
Campfire,
|
||||
Debug,
|
||||
DebugColumn,
|
||||
DropAll,
|
||||
Dummy,
|
||||
Explosion,
|
||||
Faction,
|
||||
@ -94,6 +95,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[
|
||||
ChatCommand::Campfire,
|
||||
ChatCommand::Debug,
|
||||
ChatCommand::DebugColumn,
|
||||
ChatCommand::DropAll,
|
||||
ChatCommand::Dummy,
|
||||
ChatCommand::Explosion,
|
||||
ChatCommand::Faction,
|
||||
@ -230,6 +232,7 @@ impl ChatCommand {
|
||||
"Prints some debug information about a column",
|
||||
NoAdmin,
|
||||
),
|
||||
ChatCommand::DropAll => cmd(vec![], "Drops all your items on the ground", Admin),
|
||||
ChatCommand::Dummy => cmd(vec![], "Spawns a training dummy", Admin),
|
||||
ChatCommand::Explosion => cmd(
|
||||
vec![Float("radius", 5.0, Required)],
|
||||
@ -445,6 +448,7 @@ impl ChatCommand {
|
||||
ChatCommand::Campfire => "campfire",
|
||||
ChatCommand::Debug => "debug",
|
||||
ChatCommand::DebugColumn => "debug_column",
|
||||
ChatCommand::DropAll => "dropall",
|
||||
ChatCommand::Dummy => "dummy",
|
||||
ChatCommand::Explosion => "explosion",
|
||||
ChatCommand::Faction => "faction",
|
||||
@ -533,12 +537,11 @@ impl Display for ChatCommand {
|
||||
impl FromStr for ChatCommand {
|
||||
type Err = ();
|
||||
|
||||
#[allow(clippy::manual_strip)]
|
||||
fn from_str(keyword: &str) -> Result<ChatCommand, ()> {
|
||||
let kwd = if keyword.starts_with('/') {
|
||||
&keyword[1..]
|
||||
let kwd = if let Some(stripped) = keyword.strip_prefix('/') {
|
||||
stripped
|
||||
} else {
|
||||
&keyword[..]
|
||||
&keyword
|
||||
};
|
||||
if keyword.len() == 1 {
|
||||
if let Some(c) = keyword
|
||||
|
@ -1,5 +1,8 @@
|
||||
use crate::{
|
||||
comp::{HealthChange, HealthSource, Loadout},
|
||||
comp::{
|
||||
inventory::item::{armor::Protection, ItemKind},
|
||||
HealthChange, HealthSource, Inventory,
|
||||
},
|
||||
uid::Uid,
|
||||
util::Dir,
|
||||
};
|
||||
@ -31,8 +34,32 @@ pub struct Damage {
|
||||
}
|
||||
|
||||
impl Damage {
|
||||
pub fn modify_damage(self, loadout: Option<&Loadout>, uid: Option<Uid>) -> HealthChange {
|
||||
/// Returns the total damage reduction provided by all equipped items
|
||||
pub fn compute_damage_reduction(inventory: &Inventory) -> f32 {
|
||||
let protection = inventory
|
||||
.equipped_items()
|
||||
.filter_map(|item| {
|
||||
if let ItemKind::Armor(armor) = &item.kind() {
|
||||
Some(armor.get_protection())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|protection| match protection {
|
||||
Protection::Normal(protection) => Some(protection),
|
||||
Protection::Invincible => None,
|
||||
})
|
||||
.sum::<Option<f32>>();
|
||||
match protection {
|
||||
Some(dr) => dr / (60.0 + dr.abs()),
|
||||
None => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modify_damage(self, inventory: Option<&Inventory>, uid: Option<Uid>) -> HealthChange {
|
||||
let mut damage = self.value;
|
||||
let damage_reduction = inventory.map_or(0.0, |inv| Damage::compute_damage_reduction(inv));
|
||||
|
||||
match self.source {
|
||||
DamageSource::Melee => {
|
||||
// Critical hit
|
||||
@ -41,7 +68,6 @@ impl Damage {
|
||||
critdamage = damage * 0.3;
|
||||
}
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
// Critical damage applies after armor for melee
|
||||
@ -63,7 +89,6 @@ impl Damage {
|
||||
damage *= 1.2;
|
||||
}
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
@ -76,7 +101,6 @@ impl Damage {
|
||||
},
|
||||
DamageSource::Explosion => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
@ -89,7 +113,6 @@ impl Damage {
|
||||
},
|
||||
DamageSource::Shockwave => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
@ -102,7 +125,6 @@ impl Damage {
|
||||
},
|
||||
DamageSource::Energy => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
damage *= 1.0 - damage_reduction;
|
||||
|
||||
HealthChange {
|
||||
@ -119,7 +141,6 @@ impl Damage {
|
||||
},
|
||||
DamageSource::Falling => {
|
||||
// Armor
|
||||
let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction());
|
||||
if (damage_reduction - 1.0).abs() < f32::EPSILON {
|
||||
damage = 0.0;
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
use crate::{
|
||||
assets::{self, Asset},
|
||||
comp::{
|
||||
item::{armor::Protection, tool::AbilityMap, Item, ItemKind},
|
||||
projectile::ProjectileConstructor,
|
||||
Body, CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate,
|
||||
projectile::ProjectileConstructor, Body, CharacterState, EnergySource, Gravity,
|
||||
LightEmitter, StateUpdate,
|
||||
},
|
||||
states::{
|
||||
behavior::JoinData,
|
||||
@ -12,10 +11,7 @@ use crate::{
|
||||
},
|
||||
Knockback,
|
||||
};
|
||||
use arraygen::Arraygen;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::time::Duration;
|
||||
use vek::Vec3;
|
||||
|
||||
@ -304,7 +300,7 @@ impl CharacterAbility {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_roll() -> CharacterAbility {
|
||||
pub fn default_roll() -> CharacterAbility {
|
||||
CharacterAbility::Roll {
|
||||
energy_cost: 100,
|
||||
buildup_duration: 100,
|
||||
@ -499,93 +495,6 @@ impl CharacterAbility {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct ItemConfig {
|
||||
pub item: Item,
|
||||
pub ability1: Option<CharacterAbility>,
|
||||
pub ability2: Option<CharacterAbility>,
|
||||
pub ability3: Option<CharacterAbility>,
|
||||
pub block_ability: Option<CharacterAbility>,
|
||||
pub dodge_ability: Option<CharacterAbility>,
|
||||
}
|
||||
|
||||
impl From<(Item, &AbilityMap)> for ItemConfig {
|
||||
fn from((item, map): (Item, &AbilityMap)) -> Self {
|
||||
if let ItemKind::Tool(tool) = &item.kind() {
|
||||
let abilities = tool.get_abilities(map);
|
||||
|
||||
return ItemConfig {
|
||||
item,
|
||||
ability1: Some(abilities.primary),
|
||||
ability2: Some(abilities.secondary),
|
||||
ability3: abilities.skills.get(0).cloned(),
|
||||
block_ability: None,
|
||||
dodge_ability: Some(CharacterAbility::default_roll()),
|
||||
};
|
||||
}
|
||||
|
||||
unimplemented!("ItemConfig is currently only supported for Tools")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Arraygen, Clone, PartialEq, Default, Debug, Serialize, Deserialize)]
|
||||
#[gen_array(pub fn get_armor: &Option<Item>)]
|
||||
pub struct Loadout {
|
||||
pub active_item: Option<ItemConfig>,
|
||||
pub second_item: Option<ItemConfig>,
|
||||
|
||||
pub lantern: Option<Item>,
|
||||
pub glider: Option<Item>,
|
||||
|
||||
#[in_array(get_armor)]
|
||||
pub shoulder: Option<Item>,
|
||||
#[in_array(get_armor)]
|
||||
pub chest: Option<Item>,
|
||||
#[in_array(get_armor)]
|
||||
pub belt: Option<Item>,
|
||||
#[in_array(get_armor)]
|
||||
pub hand: Option<Item>,
|
||||
#[in_array(get_armor)]
|
||||
pub pants: Option<Item>,
|
||||
#[in_array(get_armor)]
|
||||
pub foot: Option<Item>,
|
||||
#[in_array(get_armor)]
|
||||
pub back: Option<Item>,
|
||||
#[in_array(get_armor)]
|
||||
pub ring: Option<Item>,
|
||||
#[in_array(get_armor)]
|
||||
pub neck: Option<Item>,
|
||||
#[in_array(get_armor)]
|
||||
pub head: Option<Item>,
|
||||
#[in_array(get_armor)]
|
||||
pub tabard: Option<Item>,
|
||||
}
|
||||
|
||||
impl Loadout {
|
||||
pub fn get_damage_reduction(&self) -> f32 {
|
||||
let protection = self
|
||||
.get_armor()
|
||||
.iter()
|
||||
.flat_map(|armor| armor.as_ref())
|
||||
.filter_map(|item| {
|
||||
if let ItemKind::Armor(armor) = &item.kind() {
|
||||
Some(armor.get_protection())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|protection| match protection {
|
||||
Protection::Normal(protection) => Some(protection),
|
||||
Protection::Invincible => None,
|
||||
})
|
||||
.sum::<Option<f32>>();
|
||||
match protection {
|
||||
Some(dr) => dr / (60.0 + dr.abs()),
|
||||
None => 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
fn from((ability, key): (&CharacterAbility, AbilityKey)) -> Self {
|
||||
match ability {
|
||||
@ -975,7 +884,3 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Loadout {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ pub enum ArmorKind {
|
||||
Neck(String),
|
||||
Head(String),
|
||||
Tabard(String),
|
||||
Bag(String),
|
||||
}
|
||||
|
||||
impl Armor {
|
||||
@ -43,4 +44,12 @@ pub struct Armor {
|
||||
|
||||
impl Armor {
|
||||
pub fn get_protection(&self) -> Protection { self.stats.protection }
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_armor(kind: ArmorKind, protection: Protection) -> Armor {
|
||||
Armor {
|
||||
kind,
|
||||
stats: Stats { protection },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,15 @@ pub use tool::{AbilitySet, Hands, Tool, ToolKind, UniqueKind};
|
||||
|
||||
use crate::{
|
||||
assets::{self, AssetExt, Error},
|
||||
comp::{
|
||||
inventory::{item::tool::AbilityMap, InvSlot},
|
||||
Body, CharacterAbility,
|
||||
},
|
||||
effect::Effect,
|
||||
lottery::Lottery,
|
||||
terrain::{Block, SpriteKind},
|
||||
};
|
||||
use core::mem;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use rand::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -94,6 +99,15 @@ pub enum ItemKind {
|
||||
},
|
||||
}
|
||||
|
||||
impl ItemKind {
|
||||
pub fn is_equippable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Glider(_) | ItemKind::Lantern(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub type ItemId = AtomicCell<Option<NonZeroU64>>;
|
||||
|
||||
/* /// The only way to access an item id outside this module is to mutably, atomically update it using
|
||||
@ -101,11 +115,7 @@ pub type ItemId = AtomicCell<Option<NonZeroU64>>;
|
||||
/// only if it's not already set.
|
||||
pub struct CreateDatabaseItemId {
|
||||
item_id: Arc<ItemId>,
|
||||
}
|
||||
|
||||
pub struct CreateDatabaseItemId {
|
||||
item_id: Arc<ItemId>,
|
||||
} */
|
||||
}*/
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Item {
|
||||
@ -124,22 +134,54 @@ pub struct Item {
|
||||
/// amount is hidden because it needs to maintain the invariant that only
|
||||
/// stackable items can have > 1 amounts.
|
||||
amount: NonZeroU32,
|
||||
/// The slots for items that this item has
|
||||
slots: Vec<InvSlot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ItemDef {
|
||||
#[serde(default)]
|
||||
item_definition_id: String,
|
||||
pub item_config: Option<ItemConfig>,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub kind: ItemKind,
|
||||
pub quality: Quality,
|
||||
#[serde(default)]
|
||||
pub slots: u16,
|
||||
}
|
||||
|
||||
impl PartialEq for ItemDef {
|
||||
fn eq(&self, other: &Self) -> bool { self.item_definition_id == other.item_definition_id }
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct ItemConfig {
|
||||
pub ability1: Option<CharacterAbility>,
|
||||
pub ability2: Option<CharacterAbility>,
|
||||
pub ability3: Option<CharacterAbility>,
|
||||
pub block_ability: Option<CharacterAbility>,
|
||||
pub dodge_ability: Option<CharacterAbility>,
|
||||
}
|
||||
|
||||
impl From<(&ItemKind, &AbilityMap)> for ItemConfig {
|
||||
fn from((item_kind, map): (&ItemKind, &AbilityMap)) -> Self {
|
||||
if let ItemKind::Tool(tool) = item_kind {
|
||||
let abilities = tool.get_abilities(map);
|
||||
|
||||
return ItemConfig {
|
||||
ability1: Some(abilities.primary),
|
||||
ability2: Some(abilities.secondary),
|
||||
ability3: abilities.skills.get(0).cloned(),
|
||||
block_ability: None,
|
||||
dodge_ability: Some(CharacterAbility::default_roll()),
|
||||
};
|
||||
}
|
||||
|
||||
unimplemented!("ItemConfig is currently only supported for Tools")
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemDef {
|
||||
pub fn is_stackable(&self) -> bool {
|
||||
matches!(
|
||||
@ -150,6 +192,25 @@ impl ItemDef {
|
||||
| ItemKind::Utility { .. }
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_test(
|
||||
item_definition_id: String,
|
||||
item_config: Option<ItemConfig>,
|
||||
kind: ItemKind,
|
||||
quality: Quality,
|
||||
slots: u16,
|
||||
) -> Self {
|
||||
Self {
|
||||
item_definition_id,
|
||||
item_config,
|
||||
name: "test item name".to_owned(),
|
||||
description: "test item description".to_owned(),
|
||||
kind,
|
||||
quality,
|
||||
slots,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Item {
|
||||
@ -170,8 +231,19 @@ impl assets::Compound for ItemDef {
|
||||
description,
|
||||
kind,
|
||||
quality,
|
||||
slots,
|
||||
} = raw;
|
||||
|
||||
let item_config = if let ItemKind::Tool(_) = kind {
|
||||
let ability_map_handle =
|
||||
cache.load::<AbilityMap>("common.abilities.weapon_ability_manifest")?;
|
||||
let ability_map = &*ability_map_handle.read();
|
||||
|
||||
Some(ItemConfig::from((&kind, ability_map)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Some commands like /give_item provide the asset specifier separated with \
|
||||
// instead of .
|
||||
//
|
||||
@ -180,10 +252,12 @@ impl assets::Compound for ItemDef {
|
||||
|
||||
Ok(ItemDef {
|
||||
item_definition_id,
|
||||
item_config,
|
||||
name,
|
||||
description,
|
||||
kind,
|
||||
quality,
|
||||
slots,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -195,6 +269,8 @@ struct RawItemDef {
|
||||
description: String,
|
||||
kind: ItemKind,
|
||||
quality: Quality,
|
||||
#[serde(default)]
|
||||
slots: u16,
|
||||
}
|
||||
|
||||
impl assets::Asset for RawItemDef {
|
||||
@ -229,11 +305,12 @@ impl Item {
|
||||
// loadout when no weapon is present
|
||||
pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") }
|
||||
|
||||
pub fn new(inner_item: Arc<ItemDef>) -> Self {
|
||||
pub fn new_from_item_def(inner_item: Arc<ItemDef>) -> Self {
|
||||
Item {
|
||||
item_id: Arc::new(AtomicCell::new(None)),
|
||||
item_def: inner_item,
|
||||
amount: NonZeroU32::new(1).unwrap(),
|
||||
slots: vec![None; inner_item.slots as usize],
|
||||
item_def: inner_item,
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,7 +318,7 @@ impl Item {
|
||||
/// Panics if the asset does not exist.
|
||||
pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
|
||||
let inner_item = Arc::<ItemDef>::load_expect_cloned(asset_specifier);
|
||||
Item::new(inner_item)
|
||||
Item::new_from_item_def(inner_item)
|
||||
}
|
||||
|
||||
/// Creates a Vec containing one of each item that matches the provided
|
||||
@ -254,11 +331,43 @@ impl Item {
|
||||
/// it exists
|
||||
pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
|
||||
let inner_item = Arc::<ItemDef>::load_cloned(asset)?;
|
||||
Ok(Item::new(inner_item))
|
||||
Ok(Item::new_from_item_def(inner_item))
|
||||
}
|
||||
|
||||
pub fn new_default_for_body(body: &Body) -> Self {
|
||||
let mut item = Item::new_from_asset_expect("common.items.weapons.empty.empty");
|
||||
|
||||
let empty_def = &*item.item_def;
|
||||
item.item_def = Arc::new(ItemDef {
|
||||
slots: empty_def.slots,
|
||||
name: empty_def.name.clone(),
|
||||
kind: empty_def.kind.clone(),
|
||||
description: empty_def.description.clone(),
|
||||
item_definition_id: empty_def.item_definition_id.clone(),
|
||||
quality: empty_def.quality,
|
||||
item_config: Some(ItemConfig {
|
||||
ability1: Some(CharacterAbility::BasicMelee {
|
||||
energy_cost: 10,
|
||||
buildup_duration: 500,
|
||||
swing_duration: 100,
|
||||
recover_duration: 100,
|
||||
base_damage: body.base_dmg(),
|
||||
knockback: 0.0,
|
||||
range: body.base_range(),
|
||||
max_angle: 20.0,
|
||||
}),
|
||||
ability2: None,
|
||||
ability3: None,
|
||||
block_ability: None,
|
||||
dodge_ability: None,
|
||||
}),
|
||||
});
|
||||
|
||||
item
|
||||
}
|
||||
|
||||
/// Duplicates an item, creating an exact copy but with a new item ID
|
||||
pub fn duplicate(&self) -> Self { Item::new(Arc::clone(&self.item_def)) }
|
||||
pub fn duplicate(&self) -> Self { Item::new_from_item_def(Arc::clone(&self.item_def)) }
|
||||
|
||||
/// 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
|
||||
@ -320,6 +429,11 @@ impl Item {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator that drains items contained within the item's slots
|
||||
pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
|
||||
self.slots.iter_mut().filter_map(|x| mem::take(x))
|
||||
}
|
||||
|
||||
pub fn item_definition_id(&self) -> &str { &self.item_def.item_definition_id }
|
||||
|
||||
pub fn is_same_item_def(&self, item_def: &ItemDef) -> bool {
|
||||
@ -338,6 +452,26 @@ impl Item {
|
||||
|
||||
pub fn quality(&self) -> Quality { self.item_def.quality }
|
||||
|
||||
pub fn slots(&self) -> &[InvSlot] { &self.slots }
|
||||
|
||||
pub fn slots_mut(&mut self) -> &mut [InvSlot] { &mut self.slots }
|
||||
|
||||
pub fn item_config_expect(&self) -> &ItemConfig {
|
||||
&self
|
||||
.item_def
|
||||
.item_config
|
||||
.as_ref()
|
||||
.expect("Item was expected to have an ItemConfig")
|
||||
}
|
||||
|
||||
pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() }
|
||||
|
||||
pub fn populated_slots(&self) -> usize { self.slots().len().saturating_sub(self.free_slots()) }
|
||||
|
||||
pub fn slot(&self, slot: usize) -> Option<&InvSlot> { self.slots.get(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> {
|
||||
let chosen;
|
||||
let mut rng = rand::thread_rng();
|
||||
@ -416,6 +550,7 @@ pub trait ItemDesc {
|
||||
fn name(&self) -> &str;
|
||||
fn kind(&self) -> &ItemKind;
|
||||
fn quality(&self) -> &Quality;
|
||||
fn num_slots(&self) -> u16;
|
||||
fn item_definition_id(&self) -> &str;
|
||||
}
|
||||
|
||||
@ -428,6 +563,8 @@ impl ItemDesc for Item {
|
||||
|
||||
fn quality(&self) -> &Quality { &self.item_def.quality }
|
||||
|
||||
fn num_slots(&self) -> u16 { self.item_def.slots }
|
||||
|
||||
fn item_definition_id(&self) -> &str { &self.item_def.item_definition_id }
|
||||
}
|
||||
|
||||
@ -440,6 +577,8 @@ impl ItemDesc for ItemDef {
|
||||
|
||||
fn quality(&self) -> &Quality { &self.quality }
|
||||
|
||||
fn num_slots(&self) -> u16 { self.slots }
|
||||
|
||||
fn item_definition_id(&self) -> &str { &self.item_definition_id }
|
||||
}
|
||||
|
||||
|
363
common/src/comp/inventory/loadout.rs
Normal file
363
common/src/comp/inventory/loadout.rs
Normal file
@ -0,0 +1,363 @@
|
||||
use crate::comp::{
|
||||
inventory::{
|
||||
item::ItemKind,
|
||||
slot::{ArmorSlot, EquipSlot},
|
||||
InvSlot,
|
||||
},
|
||||
Item,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Range;
|
||||
use tracing::warn;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct Loadout {
|
||||
slots: Vec<LoadoutSlot>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct LoadoutSlot {
|
||||
/// The EquipSlot that this slot represents
|
||||
pub(super) equip_slot: EquipSlot,
|
||||
/// The contents of the slot
|
||||
slot: InvSlot,
|
||||
/// The unique string that represents this loadout slot in the database (not
|
||||
/// synced to clients)
|
||||
#[serde(skip)]
|
||||
persistence_key: String,
|
||||
}
|
||||
|
||||
impl LoadoutSlot {
|
||||
fn new(equip_slot: EquipSlot, persistence_key: String) -> LoadoutSlot {
|
||||
LoadoutSlot {
|
||||
equip_slot,
|
||||
slot: None,
|
||||
persistence_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct LoadoutSlotId {
|
||||
// The index of the loadout item that provides this inventory slot.
|
||||
pub loadout_idx: usize,
|
||||
// The index of the slot within its container
|
||||
pub slot_idx: usize,
|
||||
}
|
||||
|
||||
pub enum LoadoutError {
|
||||
InvalidPersistenceKey,
|
||||
}
|
||||
|
||||
impl Loadout {
|
||||
pub(super) fn new_empty() -> Self {
|
||||
Self {
|
||||
slots: vec![
|
||||
(EquipSlot::Lantern, "lantern".to_string()),
|
||||
(EquipSlot::Glider, "glider".to_string()),
|
||||
(
|
||||
EquipSlot::Armor(ArmorSlot::Shoulders),
|
||||
"shoulder".to_string(),
|
||||
),
|
||||
(EquipSlot::Armor(ArmorSlot::Chest), "chest".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Belt), "belt".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Hands), "hand".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Legs), "pants".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Feet), "foot".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Back), "back".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Ring1), "ring1".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Ring2), "ring2".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Neck), "neck".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Head), "head".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Tabard), "tabard".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Bag1), "bag1".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Bag2), "bag2".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Bag3), "bag3".to_string()),
|
||||
(EquipSlot::Armor(ArmorSlot::Bag4), "bag4".to_string()),
|
||||
(EquipSlot::Mainhand, "active_item".to_string()),
|
||||
(EquipSlot::Offhand, "second_item".to_string()),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(equip_slot, persistence_key)| LoadoutSlot::new(equip_slot, persistence_key))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the item in the Loadout slot that corresponds to the given
|
||||
/// EquipSlot and returns the previous item if any
|
||||
pub(super) fn swap(&mut self, equip_slot: EquipSlot, item: Option<Item>) -> Option<Item> {
|
||||
self.slots
|
||||
.iter_mut()
|
||||
.find(|x| x.equip_slot == equip_slot)
|
||||
.and_then(|x| core::mem::replace(&mut x.slot, item))
|
||||
}
|
||||
|
||||
/// Returns a reference to the item (if any) equipped in the given EquipSlot
|
||||
pub(super) fn equipped(&self, equip_slot: EquipSlot) -> Option<&Item> {
|
||||
self.slot(equip_slot).and_then(|x| x.slot.as_ref())
|
||||
}
|
||||
|
||||
fn slot(&self, equip_slot: EquipSlot) -> Option<&LoadoutSlot> {
|
||||
self.slots
|
||||
.iter()
|
||||
.find(|loadout_slot| loadout_slot.equip_slot == equip_slot)
|
||||
}
|
||||
|
||||
pub(super) fn loadout_idx_for_equip_slot(&self, equip_slot: EquipSlot) -> Option<usize> {
|
||||
self.slots
|
||||
.iter()
|
||||
.position(|loadout_slot| loadout_slot.equip_slot == equip_slot)
|
||||
}
|
||||
|
||||
/// Returns all loadout items paired with their persistence key
|
||||
pub(super) fn items_with_persistence_key(&self) -> impl Iterator<Item = (&str, Option<&Item>)> {
|
||||
self.slots
|
||||
.iter()
|
||||
.map(|x| (x.persistence_key.as_str(), x.slot.as_ref()))
|
||||
}
|
||||
|
||||
/// Sets a loadout item in the correct slot using its persistence key. Any
|
||||
/// item that already exists in the slot is lost.
|
||||
pub fn set_item_at_slot_using_persistence_key(
|
||||
&mut self,
|
||||
persistence_key: &str,
|
||||
item: Item,
|
||||
) -> Result<(), LoadoutError> {
|
||||
if let Some(slot) = self
|
||||
.slots
|
||||
.iter_mut()
|
||||
.find(|x| x.persistence_key == persistence_key)
|
||||
{
|
||||
slot.slot = Some(item);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(LoadoutError::InvalidPersistenceKey)
|
||||
}
|
||||
}
|
||||
|
||||
/// Swaps the contents of two loadout slots
|
||||
pub(super) fn swap_slots(&mut self, equip_slot_a: EquipSlot, equip_slot_b: EquipSlot) {
|
||||
if self.slot(equip_slot_b).is_none() || self.slot(equip_slot_b).is_none() {
|
||||
// Currently all loadouts contain slots for all EquipSlots so this can never
|
||||
// happen, but if loadouts with alternate slot combinations are
|
||||
// introduced then it could.
|
||||
warn!("Cannot swap slots for non-existent equip slot");
|
||||
return;
|
||||
}
|
||||
|
||||
let item_a = self.swap(equip_slot_a, None);
|
||||
let item_b = self.swap(equip_slot_b, None);
|
||||
|
||||
// Check if items can go in the other slots
|
||||
if item_a
|
||||
.as_ref()
|
||||
.map_or(true, |i| equip_slot_b.can_hold(&i.kind()))
|
||||
&& item_b
|
||||
.as_ref()
|
||||
.map_or(true, |i| equip_slot_a.can_hold(&i.kind()))
|
||||
{
|
||||
// Swap
|
||||
self.swap(equip_slot_b, item_a).unwrap_none();
|
||||
self.swap(equip_slot_a, item_b).unwrap_none();
|
||||
} else {
|
||||
// Otherwise put the items back
|
||||
self.swap(equip_slot_a, item_a).unwrap_none();
|
||||
self.swap(equip_slot_b, item_b).unwrap_none();
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a slot that an item of a particular `ItemKind` can be equipped
|
||||
/// into. The first empty slot compatible with the item will be
|
||||
/// returned, or if there are no free slots then the first occupied slot
|
||||
/// will be returned. The bool part of the tuple indicates whether an item
|
||||
/// is already equipped in the slot.
|
||||
pub(super) fn get_slot_to_equip_into(&self, item_kind: &ItemKind) -> Option<EquipSlot> {
|
||||
let mut suitable_slots = self
|
||||
.slots
|
||||
.iter()
|
||||
.filter(|s| s.equip_slot.can_hold(item_kind));
|
||||
|
||||
let first = suitable_slots.next();
|
||||
|
||||
first
|
||||
.into_iter()
|
||||
.chain(suitable_slots)
|
||||
.find(|loadout_slot| loadout_slot.slot.is_none())
|
||||
.map(|x| x.equip_slot)
|
||||
.or_else(|| first.map(|x| x.equip_slot))
|
||||
}
|
||||
|
||||
/// Returns the `InvSlot` for a given `LoadoutSlotId`
|
||||
pub(super) fn inv_slot(&self, loadout_slot_id: LoadoutSlotId) -> Option<&InvSlot> {
|
||||
self.slots
|
||||
.get(loadout_slot_id.loadout_idx)
|
||||
.and_then(|loadout_slot| loadout_slot.slot.as_ref())
|
||||
.and_then(|item| item.slot(loadout_slot_id.slot_idx))
|
||||
}
|
||||
|
||||
/// Returns the `InvSlot` for a given `LoadoutSlotId`
|
||||
pub(super) fn inv_slot_mut(&mut self, loadout_slot_id: LoadoutSlotId) -> Option<&mut InvSlot> {
|
||||
self.slots
|
||||
.get_mut(loadout_slot_id.loadout_idx)
|
||||
.and_then(|loadout_slot| loadout_slot.slot.as_mut())
|
||||
.and_then(|item| item.slot_mut(loadout_slot_id.slot_idx))
|
||||
}
|
||||
|
||||
/// Returns all inventory slots provided by equipped loadout items, along
|
||||
/// with their `LoadoutSlotId`
|
||||
pub(super) fn inv_slots_with_id(&self) -> impl Iterator<Item = (LoadoutSlotId, &InvSlot)> {
|
||||
self.slots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, loadout_slot)| {
|
||||
loadout_slot.slot.as_ref().map(|item| (i, item.slots()))
|
||||
})
|
||||
.flat_map(|(loadout_slot_index, loadout_slots)| {
|
||||
loadout_slots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(move |(item_slot_index, inv_slot)| {
|
||||
(
|
||||
LoadoutSlotId {
|
||||
loadout_idx: loadout_slot_index,
|
||||
slot_idx: item_slot_index,
|
||||
},
|
||||
inv_slot,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns all inventory slots provided by equipped loadout items
|
||||
pub(super) fn inv_slots_mut(&mut self) -> impl Iterator<Item = &mut InvSlot> {
|
||||
self.slots.iter_mut()
|
||||
.filter_map(|x| x.slot.as_mut().map(|item| item.slots_mut())) // Discard loadout items that have no slots of their own
|
||||
.flat_map(|loadout_slots| loadout_slots.iter_mut()) //Collapse iter of Vec<InvSlot> to iter of InvSlot
|
||||
}
|
||||
|
||||
/// Gets the range of loadout-provided inventory slot indexes that are
|
||||
/// provided by the item in the given `EquipSlot`
|
||||
pub(super) fn slot_range_for_equip_slot(&self, equip_slot: EquipSlot) -> Option<Range<usize>> {
|
||||
self.slots
|
||||
.iter()
|
||||
.map(|loadout_slot| {
|
||||
(
|
||||
loadout_slot.equip_slot,
|
||||
loadout_slot
|
||||
.slot
|
||||
.as_ref()
|
||||
.map_or(0, |item| item.slots().len()),
|
||||
)
|
||||
})
|
||||
.scan(0, |acc_len, (equip_slot, len)| {
|
||||
let res = Some((equip_slot, len, *acc_len));
|
||||
*acc_len += len;
|
||||
res
|
||||
})
|
||||
.find(|(e, len, _)| *e == equip_slot && len > &0)
|
||||
.map(|(_, slot_len, start)| start..start + slot_len)
|
||||
}
|
||||
|
||||
/// Attempts to equip the item into a compatible, unpopulated loadout slot.
|
||||
/// If no slot is available the item is returned.
|
||||
#[must_use = "Returned item will be lost if not used"]
|
||||
pub(super) fn try_equip(&mut self, item: Item) -> Result<(), Item> {
|
||||
if let Some(loadout_slot) = self
|
||||
.slots
|
||||
.iter_mut()
|
||||
.find(|s| s.slot.is_none() && s.equip_slot.can_hold(item.kind()))
|
||||
{
|
||||
loadout_slot.slot = Some(item);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(item)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn items(&self) -> impl Iterator<Item = &Item> {
|
||||
self.slots.iter().filter_map(|x| x.slot.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::comp::{
|
||||
inventory::{
|
||||
item::{
|
||||
armor::{Armor, ArmorKind, Protection},
|
||||
ItemKind,
|
||||
},
|
||||
loadout::Loadout,
|
||||
slot::{ArmorSlot, EquipSlot},
|
||||
test_helpers::get_test_bag,
|
||||
},
|
||||
Item,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_slot_range_for_equip_slot() {
|
||||
let mut loadout = Loadout::new_empty();
|
||||
|
||||
let bag1_slot = EquipSlot::Armor(ArmorSlot::Bag1);
|
||||
let bag = get_test_bag(18);
|
||||
loadout.swap(bag1_slot, Some(bag));
|
||||
|
||||
let result = loadout.slot_range_for_equip_slot(bag1_slot).unwrap();
|
||||
|
||||
assert_eq!(0..18, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slot_range_for_equip_slot_no_item() {
|
||||
let loadout = Loadout::new_empty();
|
||||
let result = loadout.slot_range_for_equip_slot(EquipSlot::Armor(ArmorSlot::Bag1));
|
||||
|
||||
assert_eq!(None, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slot_range_for_equip_slot_item_without_slots() {
|
||||
let mut loadout = Loadout::new_empty();
|
||||
|
||||
let feet_slot = EquipSlot::Armor(ArmorSlot::Feet);
|
||||
let boots = Item::new_from_asset_expect("common.items.testing.test_boots");
|
||||
loadout.swap(feet_slot, Some(boots));
|
||||
let result = loadout.slot_range_for_equip_slot(feet_slot);
|
||||
|
||||
assert_eq!(None, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_slot_to_equip_into_second_bag_slot_free() {
|
||||
let mut loadout = Loadout::new_empty();
|
||||
|
||||
loadout.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(get_test_bag(1)));
|
||||
|
||||
let result = loadout
|
||||
.get_slot_to_equip_into(&ItemKind::Armor(Armor::test_armor(
|
||||
ArmorKind::Bag("test".to_string()),
|
||||
Protection::Normal(0.0),
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(EquipSlot::Armor(ArmorSlot::Bag2), result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_slot_to_equip_into_no_bag_slots_free() {
|
||||
let mut loadout = Loadout::new_empty();
|
||||
|
||||
loadout.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(get_test_bag(1)));
|
||||
loadout.swap(EquipSlot::Armor(ArmorSlot::Bag2), Some(get_test_bag(1)));
|
||||
loadout.swap(EquipSlot::Armor(ArmorSlot::Bag3), Some(get_test_bag(1)));
|
||||
loadout.swap(EquipSlot::Armor(ArmorSlot::Bag4), Some(get_test_bag(1)));
|
||||
|
||||
let result = loadout
|
||||
.get_slot_to_equip_into(&ItemKind::Armor(Armor::test_armor(
|
||||
ArmorKind::Bag("test".to_string()),
|
||||
Protection::Normal(0.0),
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(EquipSlot::Armor(ArmorSlot::Bag1), result);
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
use crate::comp::{
|
||||
biped_large, golem,
|
||||
item::{tool::AbilityMap, Item, ItemKind},
|
||||
quadruped_low, quadruped_medium, theropod, Body, CharacterAbility, ItemConfig, Loadout,
|
||||
inventory::{
|
||||
loadout::Loadout,
|
||||
slot::{ArmorSlot, EquipSlot},
|
||||
},
|
||||
item::{Item, ItemKind},
|
||||
quadruped_low, quadruped_medium, theropod, Body,
|
||||
};
|
||||
use rand::Rng;
|
||||
|
||||
@ -13,19 +17,18 @@ use rand::Rng;
|
||||
/// use veloren_common::{
|
||||
/// assets::AssetExt,
|
||||
/// comp::item::tool::AbilityMap,
|
||||
/// comp::Item,
|
||||
/// LoadoutBuilder,
|
||||
/// };
|
||||
///
|
||||
/// let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
///
|
||||
/// // Build a loadout with character starter defaults and a specific sword with default sword abilities
|
||||
/// let loadout = LoadoutBuilder::new()
|
||||
/// .defaults()
|
||||
/// .active_item(Some(LoadoutBuilder::default_item_config_from_str(
|
||||
/// "common.items.weapons.sword.zweihander_sword_0", &map
|
||||
/// )))
|
||||
/// .active_item(Some(Item::new_from_asset_expect("common.items.weapons.sword.zweihander_sword_0")))
|
||||
/// .build();
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct LoadoutBuilder(Loadout);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum LoadoutConfig {
|
||||
@ -40,29 +43,9 @@ pub enum LoadoutConfig {
|
||||
Warlock,
|
||||
}
|
||||
|
||||
pub struct LoadoutBuilder(Loadout);
|
||||
|
||||
impl LoadoutBuilder {
|
||||
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
|
||||
pub fn new() -> Self {
|
||||
Self(Loadout {
|
||||
active_item: None,
|
||||
second_item: None,
|
||||
shoulder: None,
|
||||
chest: None,
|
||||
belt: None,
|
||||
hand: None,
|
||||
pants: None,
|
||||
foot: None,
|
||||
back: None,
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: None,
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
})
|
||||
}
|
||||
pub fn new() -> Self { Self(Loadout::new_empty()) }
|
||||
|
||||
/// Set default armor items for the loadout. This may vary with game
|
||||
/// updates, but should be safe defaults for a new character.
|
||||
@ -73,7 +56,7 @@ impl LoadoutBuilder {
|
||||
.pants(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.starter.rugged_pants",
|
||||
)))
|
||||
.foot(Some(Item::new_from_asset_expect(
|
||||
.feet(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.starter.sandals_0",
|
||||
)))
|
||||
.lantern(Some(Item::new_from_asset_expect(
|
||||
@ -89,7 +72,6 @@ impl LoadoutBuilder {
|
||||
pub fn build_loadout(
|
||||
body: Body,
|
||||
mut main_tool: Option<Item>,
|
||||
map: &AbilityMap,
|
||||
config: Option<LoadoutConfig>,
|
||||
) -> Self {
|
||||
// If no main tool is passed in, checks if species has a default main tool
|
||||
@ -251,284 +233,230 @@ impl LoadoutBuilder {
|
||||
|
||||
// Constructs ItemConfig from Item
|
||||
let active_item = if let Some(ItemKind::Tool(_)) = main_tool.as_ref().map(|i| i.kind()) {
|
||||
main_tool.map(|item| ItemConfig::from((item, map)))
|
||||
main_tool
|
||||
} else {
|
||||
Some(LoadoutBuilder::animal(body))
|
||||
Some(Item::new_default_for_body(&body))
|
||||
};
|
||||
|
||||
// Creates rest of loadout
|
||||
let loadout = if let Some(config) = config {
|
||||
use LoadoutConfig::*;
|
||||
match config {
|
||||
Guard => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: Some(Item::new_from_asset_expect(
|
||||
Guard => LoadoutBuilder::new()
|
||||
.active_item(active_item)
|
||||
.shoulder(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.shoulder.steel_0",
|
||||
)),
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.chest(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.chest.steel_0",
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.belt(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.steel_0",
|
||||
)),
|
||||
hand: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.hands(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.hand.steel_0",
|
||||
)),
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.pants(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.steel_0",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.feet(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.foot.steel_0",
|
||||
)),
|
||||
back: None,
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: match rand::thread_rng().gen_range(0, 3) {
|
||||
)))
|
||||
.lantern(match rand::thread_rng().gen_range(0, 3) {
|
||||
0 => Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
||||
_ => None,
|
||||
},
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
Outcast => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: Some(Item::new_from_asset_expect(
|
||||
})
|
||||
.build(),
|
||||
Outcast => LoadoutBuilder::new()
|
||||
.active_item(active_item)
|
||||
.shoulder(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.shoulder.cloth_purple_0",
|
||||
)),
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.chest(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.chest.cloth_purple_0",
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.belt(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.cloth_purple_0",
|
||||
)),
|
||||
hand: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.hands(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.hand.cloth_purple_0",
|
||||
)),
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.pants(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.cloth_purple_0",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.feet(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.foot.cloth_purple_0",
|
||||
)),
|
||||
back: None,
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: match rand::thread_rng().gen_range(0, 3) {
|
||||
)))
|
||||
.lantern(match rand::thread_rng().gen_range(0, 3) {
|
||||
0 => Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
||||
_ => None,
|
||||
},
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
Highwayman => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: Some(Item::new_from_asset_expect(
|
||||
})
|
||||
.build(),
|
||||
Highwayman => LoadoutBuilder::new()
|
||||
.active_item(active_item)
|
||||
.shoulder(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.shoulder.leather_0",
|
||||
)),
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.chest(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.chest.leather_0",
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.belt(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.leather_0",
|
||||
)),
|
||||
hand: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.hands(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.hand.leather_0",
|
||||
)),
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.pants(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.leather_0",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.feet(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.foot.leather_0",
|
||||
)),
|
||||
back: None,
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: match rand::thread_rng().gen_range(0, 3) {
|
||||
)))
|
||||
.lantern(match rand::thread_rng().gen_range(0, 3) {
|
||||
0 => Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
||||
_ => None,
|
||||
},
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
Bandit => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: Some(Item::new_from_asset_expect(
|
||||
})
|
||||
.build(),
|
||||
Bandit => LoadoutBuilder::new()
|
||||
.active_item(active_item)
|
||||
.shoulder(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.shoulder.assassin",
|
||||
)),
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.chest(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.chest.assassin",
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.belt(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.assassin",
|
||||
)),
|
||||
hand: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.hands(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.hand.assassin",
|
||||
)),
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.pants(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.assassin",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.feet(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.foot.assassin",
|
||||
)),
|
||||
back: None,
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: match rand::thread_rng().gen_range(0, 3) {
|
||||
)))
|
||||
.lantern(match rand::thread_rng().gen_range(0, 3) {
|
||||
0 => Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
||||
_ => None,
|
||||
},
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
CultistNovice => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: Some(Item::new_from_asset_expect(
|
||||
})
|
||||
.build(),
|
||||
CultistNovice => LoadoutBuilder::new()
|
||||
.active_item(active_item)
|
||||
.shoulder(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.shoulder.steel_0",
|
||||
)),
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.chest(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.chest.steel_0",
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.belt(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.steel_0",
|
||||
)),
|
||||
hand: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.hands(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.hand.steel_0",
|
||||
)),
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.pants(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.steel_0",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.feet(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.foot.steel_0",
|
||||
)),
|
||||
back: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.back(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.back.dungeon_purple-0",
|
||||
)),
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: match rand::thread_rng().gen_range(0, 3) {
|
||||
)))
|
||||
.lantern(match rand::thread_rng().gen_range(0, 3) {
|
||||
0 => Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
||||
_ => None,
|
||||
},
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
CultistAcolyte => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: Some(Item::new_from_asset_expect(
|
||||
})
|
||||
.build(),
|
||||
CultistAcolyte => LoadoutBuilder::new()
|
||||
.active_item(active_item)
|
||||
.shoulder(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.shoulder.cultist_shoulder_purple",
|
||||
)),
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.chest(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.chest.cultist_chest_purple",
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.belt(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.cultist_belt",
|
||||
)),
|
||||
hand: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.hands(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.hand.cultist_hands_purple",
|
||||
)),
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.pants(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.cultist_legs_purple",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.feet(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.foot.cultist_boots",
|
||||
)),
|
||||
back: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.back(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.back.dungeon_purple-0",
|
||||
)),
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: match rand::thread_rng().gen_range(0, 3) {
|
||||
)))
|
||||
.lantern(match rand::thread_rng().gen_range(0, 3) {
|
||||
0 => Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
||||
_ => None,
|
||||
},
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
Warlord => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: Some(Item::new_from_asset_expect(
|
||||
})
|
||||
.build(),
|
||||
Warlord => LoadoutBuilder::new()
|
||||
.active_item(active_item)
|
||||
.shoulder(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.shoulder.warlord",
|
||||
)),
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.chest(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.chest.warlord",
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.belt(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.warlord",
|
||||
)),
|
||||
hand: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.hands(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.hand.warlord",
|
||||
)),
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.pants(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.warlord",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.feet(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.foot.warlord",
|
||||
)),
|
||||
back: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.back(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.back.warlord",
|
||||
)),
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: match rand::thread_rng().gen_range(0, 3) {
|
||||
)))
|
||||
.lantern(match rand::thread_rng().gen_range(0, 3) {
|
||||
0 => Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
||||
_ => None,
|
||||
},
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
Warlock => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: Some(Item::new_from_asset_expect(
|
||||
})
|
||||
.build(),
|
||||
Warlock => LoadoutBuilder::new()
|
||||
.active_item(active_item)
|
||||
.shoulder(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.shoulder.warlock",
|
||||
)),
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.chest(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.chest.warlock",
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.belt(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.warlock",
|
||||
)),
|
||||
hand: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.hands(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.hand.warlock",
|
||||
)),
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.pants(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.warlock",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.feet(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.foot.warlock",
|
||||
)),
|
||||
back: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.back(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.back.warlock",
|
||||
)),
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: match rand::thread_rng().gen_range(0, 3) {
|
||||
)))
|
||||
.lantern(match rand::thread_rng().gen_range(0, 3) {
|
||||
0 => Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
||||
_ => None,
|
||||
},
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
Villager => Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: None,
|
||||
chest: Some(Item::new_from_asset_expect(
|
||||
})
|
||||
.build(),
|
||||
Villager => LoadoutBuilder::new()
|
||||
.active_item(active_item)
|
||||
.chest(Some(Item::new_from_asset_expect(
|
||||
match rand::thread_rng().gen_range(0, 10) {
|
||||
0 => "common.items.armor.chest.worker_green_0",
|
||||
1 => "common.items.armor.chest.worker_green_1",
|
||||
@ -541,167 +469,105 @@ impl LoadoutBuilder {
|
||||
8 => "common.items.armor.chest.worker_orange_0",
|
||||
_ => "common.items.armor.chest.worker_orange_1",
|
||||
},
|
||||
)),
|
||||
belt: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.belt(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.belt.leather_0",
|
||||
)),
|
||||
hand: None,
|
||||
pants: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.pants(Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.pants.worker_blue_0",
|
||||
)),
|
||||
foot: Some(Item::new_from_asset_expect(
|
||||
)))
|
||||
.feet(Some(Item::new_from_asset_expect(
|
||||
match rand::thread_rng().gen_range(0, 2) {
|
||||
0 => "common.items.armor.foot.leather_0",
|
||||
_ => "common.items.armor.starter.sandals_0",
|
||||
},
|
||||
)),
|
||||
back: None,
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: None,
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
)))
|
||||
.build(),
|
||||
}
|
||||
} else {
|
||||
Loadout {
|
||||
active_item,
|
||||
second_item: None,
|
||||
shoulder: None,
|
||||
chest: None,
|
||||
belt: None,
|
||||
hand: None,
|
||||
pants: None,
|
||||
foot: None,
|
||||
back: None,
|
||||
ring: None,
|
||||
neck: None,
|
||||
lantern: None,
|
||||
glider: None,
|
||||
head: None,
|
||||
tabard: None,
|
||||
}
|
||||
LoadoutBuilder::new().active_item(active_item).build()
|
||||
};
|
||||
|
||||
Self(loadout)
|
||||
}
|
||||
|
||||
/// Default animal configuration
|
||||
pub fn animal(body: Body) -> ItemConfig {
|
||||
ItemConfig {
|
||||
item: Item::new_from_asset_expect("common.items.weapons.empty.empty"),
|
||||
ability1: Some(CharacterAbility::BasicMelee {
|
||||
energy_cost: 10,
|
||||
buildup_duration: 500,
|
||||
swing_duration: 100,
|
||||
recover_duration: 100,
|
||||
base_damage: body.base_dmg(),
|
||||
knockback: 0.0,
|
||||
range: body.base_range(),
|
||||
max_angle: 20.0,
|
||||
}),
|
||||
ability2: None,
|
||||
ability3: None,
|
||||
block_ability: None,
|
||||
dodge_ability: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the default [ItemConfig](../comp/struct.ItemConfig.html) for a tool
|
||||
/// (weapon). This information is required for the `active` and `second`
|
||||
/// weapon items in a loadout. If some customisation to the item's
|
||||
/// abilities or their timings is desired, you should create and provide
|
||||
/// the item config directly to the [active_item](#method.active_item)
|
||||
/// method
|
||||
pub fn default_item_config_from_item(item: Item, map: &AbilityMap) -> ItemConfig {
|
||||
ItemConfig::from((item, map))
|
||||
}
|
||||
|
||||
/// Get an item's (weapon's) default
|
||||
/// [ItemConfig](../comp/struct.ItemConfig.html)
|
||||
/// by string reference. This will first attempt to load the Item, then
|
||||
/// the default abilities for that item via the
|
||||
/// [default_item_config_from_item](#method.default_item_config_from_item)
|
||||
/// function
|
||||
pub fn default_item_config_from_str(item_ref: &str, map: &AbilityMap) -> ItemConfig {
|
||||
Self::default_item_config_from_item(Item::new_from_asset_expect(item_ref), map)
|
||||
}
|
||||
|
||||
pub fn active_item(mut self, item: Option<ItemConfig>) -> Self {
|
||||
self.0.active_item = item;
|
||||
|
||||
pub fn active_item(mut self, item: Option<Item>) -> Self {
|
||||
self.0.swap(EquipSlot::Mainhand, item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn second_item(mut self, item: Option<ItemConfig>) -> Self {
|
||||
self.0.second_item = item;
|
||||
|
||||
pub fn second_item(mut self, item: Option<Item>) -> Self {
|
||||
self.0.swap(EquipSlot::Offhand, item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn shoulder(mut self, item: Option<Item>) -> Self {
|
||||
self.0.shoulder = item;
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Shoulders), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn chest(mut self, item: Option<Item>) -> Self {
|
||||
self.0.chest = item;
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Chest), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn belt(mut self, item: Option<Item>) -> Self {
|
||||
self.0.belt = item;
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Belt), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn hand(mut self, item: Option<Item>) -> Self {
|
||||
self.0.hand = item;
|
||||
pub fn hands(mut self, item: Option<Item>) -> Self {
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Hands), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn pants(mut self, item: Option<Item>) -> Self {
|
||||
self.0.pants = item;
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Legs), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn foot(mut self, item: Option<Item>) -> Self {
|
||||
self.0.foot = item;
|
||||
pub fn feet(mut self, item: Option<Item>) -> Self {
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Feet), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn back(mut self, item: Option<Item>) -> Self {
|
||||
self.0.back = item;
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Back), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ring(mut self, item: Option<Item>) -> Self {
|
||||
self.0.ring = item;
|
||||
pub fn ring1(mut self, item: Option<Item>) -> Self {
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Ring1), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ring2(mut self, item: Option<Item>) -> Self {
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Ring2), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn neck(mut self, item: Option<Item>) -> Self {
|
||||
self.0.neck = item;
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Neck), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn lantern(mut self, item: Option<Item>) -> Self {
|
||||
self.0.lantern = item;
|
||||
self.0.swap(EquipSlot::Lantern, item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn glider(mut self, item: Option<Item>) -> Self {
|
||||
self.0.glider = item;
|
||||
self.0.swap(EquipSlot::Glider, item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn head(mut self, item: Option<Item>) -> Self {
|
||||
self.0.head = item;
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Head), item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tabard(mut self, item: Option<Item>) -> Self {
|
||||
self.0.tabard = item;
|
||||
self.0.swap(EquipSlot::Armor(ArmorSlot::Tabard), item);
|
||||
self
|
||||
}
|
||||
|
@ -1,17 +1,41 @@
|
||||
pub mod item;
|
||||
pub mod slot;
|
||||
|
||||
use crate::{comp::inventory::item::ItemDef, recipe::Recipe};
|
||||
use core::ops::Not;
|
||||
use item::Item;
|
||||
use std::{collections::HashMap, convert::TryFrom, iter::once, mem, ops::Range};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, HashMapStorage};
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::{
|
||||
comp::{
|
||||
inventory::{
|
||||
item::ItemDef,
|
||||
loadout::Loadout,
|
||||
slot::{EquipSlot, Slot, SlotError},
|
||||
},
|
||||
slot::{InvSlotId, SlotId},
|
||||
Item,
|
||||
},
|
||||
recipe::Recipe,
|
||||
LoadoutBuilder,
|
||||
};
|
||||
|
||||
pub mod item;
|
||||
pub mod loadout;
|
||||
pub mod loadout_builder;
|
||||
pub mod slot;
|
||||
#[cfg(test)] mod test;
|
||||
#[cfg(test)] mod test_helpers;
|
||||
|
||||
pub type InvSlot = Option<Item>;
|
||||
const DEFAULT_INVENTORY_SLOTS: usize = 18;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Inventory {
|
||||
slots: Vec<Option<Item>>,
|
||||
amount: u32,
|
||||
loadout: Loadout,
|
||||
/// The "built-in" slots belonging to the inventory itself, all other slots
|
||||
/// are provided by equipped items
|
||||
slots: Vec<InvSlot>,
|
||||
}
|
||||
|
||||
/// Errors which the methods on `Inventory` produce
|
||||
@ -22,24 +46,55 @@ pub enum Error {
|
||||
Full(Vec<Item>),
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)] // TODO: Pending review in #587
|
||||
/// Represents the Inventory of an entity. The inventory has 18 "built-in"
|
||||
/// slots, with further slots being provided by items equipped in the Loadout
|
||||
/// sub-struct. Inventory slots are indexed by `InvSlotId` which is
|
||||
/// comprised of `loadout_idx` - the index of the loadout item that provides the
|
||||
/// slot, 0 being the built-in inventory slots, and `slot_idx` - the index of
|
||||
/// the slot within that loadout item.
|
||||
///
|
||||
/// Currently, it is not supported for inventories to contain items that have
|
||||
/// items inside them. This is due to both game balance purposes, and the lack
|
||||
/// of a UI to show such items. Because of this, any action that would result in
|
||||
/// such an item being put into the inventory (item pickup, unequipping an item
|
||||
/// that contains items etc) must first ensure items are unloaded from the item.
|
||||
/// This is handled in `inventory\slot.rs`
|
||||
impl Inventory {
|
||||
pub fn new_empty() -> Inventory {
|
||||
pub fn new_empty() -> Inventory { Self::new_with_loadout(LoadoutBuilder::new().build()) }
|
||||
|
||||
pub fn new_with_loadout(loadout: Loadout) -> Inventory {
|
||||
Inventory {
|
||||
slots: vec![None; 36],
|
||||
amount: 0,
|
||||
loadout,
|
||||
slots: vec![None; DEFAULT_INVENTORY_SLOTS],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slots(&self) -> &[Option<Item>] { &self.slots }
|
||||
/// Total number of slots in in the inventory.
|
||||
pub fn capacity(&self) -> usize { self.slots().count() }
|
||||
|
||||
pub fn len(&self) -> usize { self.slots.len() }
|
||||
/// An iterator of all inventory slots
|
||||
pub fn slots(&self) -> impl Iterator<Item = &InvSlot> {
|
||||
self.slots
|
||||
.iter()
|
||||
.chain(self.loadout.inv_slots_with_id().map(|(_, slot)| slot))
|
||||
}
|
||||
|
||||
/// Total number of occupied slots in the inventory.
|
||||
pub fn amount(&self) -> u32 { self.amount }
|
||||
/// A mutable iterator of all inventory slots
|
||||
fn slots_mut(&mut self) -> impl Iterator<Item = &mut InvSlot> {
|
||||
self.slots.iter_mut().chain(self.loadout.inv_slots_mut())
|
||||
}
|
||||
|
||||
pub fn recount_items(&mut self) {
|
||||
self.amount = self.slots.iter().filter(|i| i.is_some()).count() as u32;
|
||||
/// An iterator of all inventory slots and their position
|
||||
pub fn slots_with_id(&self) -> impl Iterator<Item = (InvSlotId, &InvSlot)> {
|
||||
self.slots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, slot)| ((InvSlotId::new(0, u16::try_from(i).unwrap())), slot))
|
||||
.chain(
|
||||
self.loadout
|
||||
.inv_slots_with_id()
|
||||
.map(|(loadout_slot_id, inv_slot)| (loadout_slot_id.into(), inv_slot)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Adds a new item to the first fitting group of the inventory or starts a
|
||||
@ -47,8 +102,7 @@ impl Inventory {
|
||||
pub fn push(&mut self, item: Item) -> Option<Item> {
|
||||
if item.is_stackable() {
|
||||
if let Some(slot_item) = self
|
||||
.slots
|
||||
.iter_mut()
|
||||
.slots_mut()
|
||||
.filter_map(Option::as_mut)
|
||||
.find(|s| *s == &item)
|
||||
{
|
||||
@ -61,21 +115,7 @@ impl Inventory {
|
||||
|
||||
// No existing item to stack with or item not stackable, put the item in a new
|
||||
// slot
|
||||
self.add_to_first_empty(item)
|
||||
}
|
||||
|
||||
/// Adds a new item to the first empty slot of the inventory. Returns the
|
||||
/// item again if no free slot was found.
|
||||
fn add_to_first_empty(&mut self, item: Item) -> Option<Item> {
|
||||
let item = match self.slots.iter_mut().find(|slot| slot.is_none()) {
|
||||
Some(slot) => {
|
||||
*slot = Some(item);
|
||||
None
|
||||
},
|
||||
None => Some(item),
|
||||
};
|
||||
self.recount_items();
|
||||
item
|
||||
self.insert(item)
|
||||
}
|
||||
|
||||
/// Add a series of items to inventory, returning any which do not fit as an
|
||||
@ -120,24 +160,22 @@ impl Inventory {
|
||||
|
||||
/// Replaces an item in a specific slot of the inventory. Returns the old
|
||||
/// item or the same item again if that slot was not found.
|
||||
pub fn insert(&mut self, cell: usize, item: Item) -> Result<Option<Item>, Item> {
|
||||
match self.slots.get_mut(cell) {
|
||||
Some(slot) => {
|
||||
let old = core::mem::replace(slot, Some(item));
|
||||
if old.is_none() {
|
||||
self.recount_items();
|
||||
}
|
||||
Ok(old)
|
||||
},
|
||||
pub fn insert_at(&mut self, inv_slot_id: InvSlotId, item: Item) -> Result<Option<Item>, Item> {
|
||||
match self.slot_mut(inv_slot_id) {
|
||||
Some(slot) => Ok(core::mem::replace(slot, Some(item))),
|
||||
None => Err(item),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if inserting item exists in given cell. Inserts an item if it
|
||||
/// exists.
|
||||
pub fn insert_or_stack(&mut self, cell: usize, item: Item) -> Result<Option<Item>, Item> {
|
||||
pub fn insert_or_stack_at(
|
||||
&mut self,
|
||||
inv_slot_id: InvSlotId,
|
||||
item: Item,
|
||||
) -> Result<Option<Item>, Item> {
|
||||
if item.is_stackable() {
|
||||
match self.slots.get_mut(cell) {
|
||||
match self.slot_mut(inv_slot_id) {
|
||||
Some(Some(slot_item)) => {
|
||||
Ok(if slot_item == &item {
|
||||
slot_item
|
||||
@ -150,46 +188,77 @@ impl Inventory {
|
||||
Some(old_item)
|
||||
})
|
||||
},
|
||||
Some(None) => self.insert(cell, item),
|
||||
Some(None) => self.insert_at(inv_slot_id, item),
|
||||
None => Err(item),
|
||||
}
|
||||
} else {
|
||||
self.insert(cell, item)
|
||||
self.insert_at(inv_slot_id, item)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_full(&self) -> bool { self.slots.iter().all(|slot| slot.is_some()) }
|
||||
/// Attempts to equip the item into a compatible, unpopulated loadout slot.
|
||||
/// If no slot is available the item is returned.
|
||||
#[must_use = "Returned item will be lost if not used"]
|
||||
pub fn try_equip(&mut self, item: Item) -> Result<(), Item> { self.loadout.try_equip(item) }
|
||||
|
||||
/// O(n) count the number of items in this inventory.
|
||||
pub fn count(&self) -> usize { self.slots.iter().filter_map(|slot| slot.as_ref()).count() }
|
||||
pub fn populated_slots(&self) -> usize { self.slots().filter_map(|slot| slot.as_ref()).count() }
|
||||
|
||||
/// O(n) check if an item is in this inventory.
|
||||
fn free_slots(&self) -> usize { self.slots().filter(|slot| slot.is_none()).count() }
|
||||
|
||||
/// Check if an item is in this inventory.
|
||||
pub fn contains(&self, item: &Item) -> bool {
|
||||
self.slots.iter().any(|slot| slot.as_ref() == Some(item))
|
||||
self.slots().any(|slot| slot.as_ref() == Some(item))
|
||||
}
|
||||
|
||||
/// Get content of a slot
|
||||
pub fn get(&self, cell: usize) -> Option<&Item> {
|
||||
self.slots.get(cell).and_then(Option::as_ref)
|
||||
pub fn get(&self, inv_slot_id: InvSlotId) -> Option<&Item> {
|
||||
self.slot(inv_slot_id).and_then(Option::as_ref)
|
||||
}
|
||||
|
||||
/// Returns a reference to the item (if any) equipped in the given EquipSlot
|
||||
pub fn equipped(&self, equip_slot: EquipSlot) -> Option<&Item> {
|
||||
self.loadout.equipped(equip_slot)
|
||||
}
|
||||
|
||||
pub fn loadout_items_with_persistence_key(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (&str, Option<&Item>)> {
|
||||
self.loadout.items_with_persistence_key()
|
||||
}
|
||||
|
||||
/// Returns the range of inventory slot indexes that a particular equipped
|
||||
/// item provides (used for UI highlighting of inventory slots when hovering
|
||||
/// over a loadout item)
|
||||
pub fn get_slot_range_for_equip_slot(&self, equip_slot: EquipSlot) -> Option<Range<usize>> {
|
||||
// The slot range returned from `Loadout` must be offset by the number of slots
|
||||
// that the inventory itself provides.
|
||||
let offset = self.slots.len();
|
||||
self.loadout
|
||||
.slot_range_for_equip_slot(equip_slot)
|
||||
.map(|loadout_range| (loadout_range.start + offset)..(loadout_range.end + offset))
|
||||
}
|
||||
|
||||
/// Swap the items inside of two slots
|
||||
pub fn swap_slots(&mut self, a: usize, b: usize) {
|
||||
if a.max(b) < self.slots.len() {
|
||||
self.slots.swap(a, b);
|
||||
pub fn swap_slots(&mut self, a: InvSlotId, b: InvSlotId) {
|
||||
if self.slot(a).is_none() || self.slot(b).is_none() {
|
||||
warn!("swap_slots called with non-existent inventory slot(s)");
|
||||
return;
|
||||
}
|
||||
|
||||
let slot_a = mem::take(self.slot_mut(a).unwrap());
|
||||
let slot_b = mem::take(self.slot_mut(b).unwrap());
|
||||
*self.slot_mut(a).unwrap() = slot_b;
|
||||
*self.slot_mut(b).unwrap() = slot_a;
|
||||
}
|
||||
|
||||
/// Remove an item from the slot
|
||||
pub fn remove(&mut self, cell: usize) -> Option<Item> {
|
||||
let item = self.slots.get_mut(cell).and_then(|item| item.take());
|
||||
self.recount_items();
|
||||
item
|
||||
pub fn remove(&mut self, inv_slot_id: InvSlotId) -> Option<Item> {
|
||||
self.slot_mut(inv_slot_id).and_then(|item| item.take())
|
||||
}
|
||||
|
||||
/// Remove just one item from the slot
|
||||
pub fn take(&mut self, cell: usize) -> Option<Item> {
|
||||
if let Some(Some(item)) = self.slots.get_mut(cell) {
|
||||
pub fn take(&mut self, inv_slot_id: InvSlotId) -> Option<Item> {
|
||||
if let Some(Some(item)) = self.slot_mut(inv_slot_id) {
|
||||
let mut return_item = item.duplicate();
|
||||
|
||||
if item.is_stackable() && item.amount() > 1 {
|
||||
@ -197,20 +266,25 @@ impl Inventory {
|
||||
return_item
|
||||
.set_amount(1)
|
||||
.expect("Items duplicated from a stackable item must be stackable.");
|
||||
self.recount_items();
|
||||
Some(return_item)
|
||||
} else {
|
||||
self.remove(cell)
|
||||
self.remove(inv_slot_id)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes all items from the inventory
|
||||
pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
|
||||
self.slots_mut()
|
||||
.filter(|x| x.is_some())
|
||||
.filter_map(mem::take)
|
||||
}
|
||||
|
||||
/// Determine how many of a particular item there is in the inventory.
|
||||
pub fn item_count(&self, item_def: &ItemDef) -> u64 {
|
||||
self.slots()
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|it| it.is_same_item_def(item_def))
|
||||
.map(|it| u64::from(it.amount()))
|
||||
@ -225,17 +299,18 @@ impl Inventory {
|
||||
pub fn contains_ingredients<'a>(
|
||||
&self,
|
||||
recipe: &'a Recipe,
|
||||
) -> Result<Vec<u32>, Vec<(&'a ItemDef, u32)>> {
|
||||
let mut slot_claims = vec![0; self.slots.len()];
|
||||
) -> Result<HashMap<InvSlotId, u32>, Vec<(&'a ItemDef, u32)>> {
|
||||
let mut slot_claims = HashMap::<InvSlotId, u32>::new();
|
||||
let mut missing = Vec::<(&ItemDef, u32)>::new();
|
||||
|
||||
for (input, mut needed) in recipe.inputs() {
|
||||
let mut contains_any = false;
|
||||
|
||||
for (i, slot) in self.slots().iter().enumerate() {
|
||||
for (inv_slot_id, slot) in self.slots_with_id() {
|
||||
if let Some(item) = slot.as_ref().filter(|item| item.is_same_item_def(&*input)) {
|
||||
let can_claim = (item.amount() - slot_claims[i]).min(needed);
|
||||
slot_claims[i] += can_claim;
|
||||
let claim = slot_claims.entry(inv_slot_id).or_insert(0);
|
||||
let can_claim = (item.amount() - *claim).min(needed);
|
||||
*claim += can_claim;
|
||||
needed -= can_claim;
|
||||
contains_any = true;
|
||||
}
|
||||
@ -252,24 +327,312 @@ impl Inventory {
|
||||
Err(missing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Inventory {
|
||||
fn default() -> Inventory {
|
||||
let mut inventory = Inventory {
|
||||
slots: vec![None; 36],
|
||||
amount: 0,
|
||||
};
|
||||
inventory.push(Item::new_from_asset_expect(
|
||||
"common.items.consumable.potion_minor",
|
||||
));
|
||||
inventory.push(Item::new_from_asset_expect("common.items.food.cheese"));
|
||||
inventory
|
||||
/// Adds a new item to the first empty slot of the inventory. Returns the
|
||||
/// item again if no free slot was found.
|
||||
fn insert(&mut self, item: Item) -> Option<Item> {
|
||||
match self.slots_mut().find(|slot| slot.is_none()) {
|
||||
Some(slot) => {
|
||||
*slot = Some(item);
|
||||
None
|
||||
},
|
||||
None => Some(item),
|
||||
}
|
||||
}
|
||||
|
||||
fn slot(&self, inv_slot_id: InvSlotId) -> Option<&InvSlot> {
|
||||
match SlotId::from(inv_slot_id) {
|
||||
SlotId::Inventory(slot_idx) => self.slots.get(slot_idx),
|
||||
SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot(loadout_slot_id),
|
||||
}
|
||||
}
|
||||
|
||||
fn slot_mut(&mut self, inv_slot_id: InvSlotId) -> Option<&mut InvSlot> {
|
||||
match SlotId::from(inv_slot_id) {
|
||||
SlotId::Inventory(slot_idx) => self.slots.get_mut(slot_idx),
|
||||
SlotId::Loadout(loadout_slot_id) => self.loadout.inv_slot_mut(loadout_slot_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of free slots in the inventory ignoring any slots
|
||||
/// granted by the item (if any) equipped in the provided EquipSlot.
|
||||
pub fn free_slots_minus_equipped_item(&self, equip_slot: EquipSlot) -> usize {
|
||||
if let Some(mut equip_slot_idx) = self.loadout.loadout_idx_for_equip_slot(equip_slot) {
|
||||
// Offset due to index 0 representing built-in inventory slots
|
||||
equip_slot_idx += 1;
|
||||
|
||||
self.slots_with_id()
|
||||
.filter(|(inv_slot_id, slot)| {
|
||||
inv_slot_id.loadout_idx() != equip_slot_idx && slot.is_none()
|
||||
})
|
||||
.count()
|
||||
} else {
|
||||
// TODO: return Option<usize> and evaluate to None here
|
||||
warn!(
|
||||
"Attempted to fetch loadout index for non-existent EquipSlot: {:?}",
|
||||
equip_slot
|
||||
);
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn equipped_items(&self) -> impl Iterator<Item = &Item> { self.loadout.items() }
|
||||
|
||||
/// Replaces the loadout item (if any) in the given EquipSlot with the
|
||||
/// provided item, returning the item that was previously in the slot.
|
||||
pub fn replace_loadout_item(
|
||||
&mut self,
|
||||
equip_slot: EquipSlot,
|
||||
replacement_item: Option<Item>,
|
||||
) -> Option<Item> {
|
||||
self.loadout.swap(equip_slot, replacement_item)
|
||||
}
|
||||
|
||||
/// Equip an item from a slot in inventory. The currently equipped item will
|
||||
/// go into inventory. If the item is going to mainhand, put mainhand in
|
||||
/// offhand and place offhand into inventory.
|
||||
#[must_use = "Returned items will be lost if not used"]
|
||||
pub fn equip(&mut self, inv_slot: InvSlotId) -> Option<Vec<Item>> {
|
||||
let mut leftover_items = None;
|
||||
self.get(inv_slot)
|
||||
.map(|x| x.kind().clone())
|
||||
.map(|item_kind| {
|
||||
self.loadout
|
||||
.get_slot_to_equip_into(&item_kind)
|
||||
.map(|equip_slot| {
|
||||
// Special case when equipping into main hand - swap with offhand first
|
||||
if equip_slot == EquipSlot::Mainhand {
|
||||
self.loadout
|
||||
.swap_slots(EquipSlot::Mainhand, EquipSlot::Offhand);
|
||||
}
|
||||
|
||||
leftover_items = self.swap_inventory_loadout(inv_slot, equip_slot);
|
||||
})
|
||||
});
|
||||
|
||||
leftover_items
|
||||
}
|
||||
|
||||
/// Determines how many free inventory slots will be left after equipping an
|
||||
/// item (because it could be swapped with an already equipped item that
|
||||
/// provides more inventory slots than the item being equipped)
|
||||
pub fn free_after_equip(&self, inv_slot: InvSlotId) -> i32 {
|
||||
let (inv_slot_for_equipped, slots_from_equipped) = self
|
||||
.get(inv_slot)
|
||||
.map(|x| x.kind().clone())
|
||||
.and_then(|item_kind| self.loadout.get_slot_to_equip_into(&item_kind))
|
||||
.and_then(|equip_slot| self.equipped(equip_slot))
|
||||
.map_or((1, 0), |item| (0, item.slots().len()));
|
||||
|
||||
let slots_from_inv = self
|
||||
.get(inv_slot)
|
||||
.map(|item| item.slots().len())
|
||||
.unwrap_or(0);
|
||||
|
||||
i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
|
||||
- i32::try_from(slots_from_equipped)
|
||||
.expect("Equipped item with more than i32::MAX slots")
|
||||
+ i32::try_from(slots_from_inv).expect("Inventory item with more than i32::MAX slots")
|
||||
- i32::try_from(self.populated_slots())
|
||||
.expect("Inventory item with more than i32::MAX used slots")
|
||||
+ inv_slot_for_equipped // If there is no item already in the equip slot we gain 1 slot
|
||||
}
|
||||
|
||||
/// Handles picking up an item, unloading any items inside the item being
|
||||
/// picked up and pushing them to the inventory to ensure that items
|
||||
/// containing items aren't inserted into the inventory as this is not
|
||||
/// currently supported.
|
||||
pub fn pickup_item(&mut self, mut item: Item) -> Result<(), Item> {
|
||||
if self.free_slots() < item.populated_slots() + 1 {
|
||||
return Err(item);
|
||||
}
|
||||
|
||||
// Unload any items contained within the item, and push those items and the item
|
||||
// itself into the inventory. We already know that there are enough free slots
|
||||
// so push will never give us an item back.
|
||||
item.drain()
|
||||
.collect::<Vec<Item>>()
|
||||
.into_iter()
|
||||
.chain(once(item))
|
||||
.for_each(|item| {
|
||||
self.push(item).unwrap_none();
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unequip an item from slot and place into inventory. Will leave the item
|
||||
/// equipped if inventory has no slots available.
|
||||
#[must_use = "Returned items will be lost if not used"]
|
||||
pub fn unequip(&mut self, equip_slot: EquipSlot) -> Result<Option<Vec<Item>>, SlotError> {
|
||||
// Ensure there is enough space in the inventory to place the unequipped item
|
||||
if self.free_slots_minus_equipped_item(equip_slot) == 0 {
|
||||
return Err(SlotError::InventoryFull);
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.loadout
|
||||
.swap(equip_slot, None)
|
||||
.and_then(|mut unequipped_item| {
|
||||
let unloaded_items: Vec<Item> = unequipped_item.drain().collect();
|
||||
self.push(unequipped_item)
|
||||
.expect_none("Failed to push item to inventory, precondition failed?");
|
||||
|
||||
// Unload any items that were inside the equipped item into the inventory, with
|
||||
// any that don't fit to be to be dropped on the floor by the caller
|
||||
match self.push_all(unloaded_items.into_iter()) {
|
||||
Err(Error::Full(leftovers)) => Some(leftovers),
|
||||
Ok(_) => None,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// Determines how many free inventory slots will be left after unequipping
|
||||
/// an item
|
||||
pub fn free_after_unequip(&self, equip_slot: EquipSlot) -> i32 {
|
||||
let (inv_slot_for_unequipped, slots_from_equipped) = self
|
||||
.equipped(equip_slot)
|
||||
.map_or((0, 0), |item| (1, item.slots().len()));
|
||||
|
||||
i32::try_from(self.capacity()).expect("Inventory with more than i32::MAX slots")
|
||||
- i32::try_from(slots_from_equipped)
|
||||
.expect("Equipped item with more than i32::MAX slots")
|
||||
- i32::try_from(self.populated_slots())
|
||||
.expect("Inventory item with more than i32::MAX used slots")
|
||||
- inv_slot_for_unequipped // If there is an item being unequipped we lose 1 slot
|
||||
}
|
||||
|
||||
/// Swaps items from two slots, regardless of if either is inventory or
|
||||
/// loadout.
|
||||
#[must_use = "Returned items will be lost if not used"]
|
||||
pub fn swap(&mut self, slot_a: Slot, slot_b: Slot) -> Option<Vec<Item>> {
|
||||
match (slot_a, slot_b) {
|
||||
(Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => {
|
||||
self.swap_slots(slot_a, slot_b);
|
||||
None
|
||||
},
|
||||
(Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
|
||||
| (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
|
||||
self.swap_inventory_loadout(inv_slot, equip_slot)
|
||||
},
|
||||
(Slot::Equip(slot_a), Slot::Equip(slot_b)) => {
|
||||
self.loadout.swap_slots(slot_a, slot_b);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines how many free inventory slots will be left after swapping two
|
||||
/// item slots
|
||||
pub fn free_after_swap(&self, equip_slot: EquipSlot, inv_slot: InvSlotId) -> i32 {
|
||||
let (inv_slot_for_equipped, slots_from_equipped) = self
|
||||
.equipped(equip_slot)
|
||||
.map_or((0, 0), |item| (1, item.slots().len()));
|
||||
let (inv_slot_for_inv_item, slots_from_inv_item) = self
|
||||
.get(inv_slot)
|
||||
.map_or((0, 0), |item| (1, item.slots().len()));
|
||||
|
||||
// Return the number of inventory slots that will be free once this slot swap is
|
||||
// performed
|
||||
i32::try_from(self.capacity())
|
||||
.expect("inventory with more than i32::MAX slots")
|
||||
- i32::try_from(slots_from_equipped)
|
||||
.expect("equipped item with more than i32::MAX slots")
|
||||
+ i32::try_from(slots_from_inv_item)
|
||||
.expect("inventory item with more than i32::MAX slots")
|
||||
- i32::try_from(self.populated_slots())
|
||||
.expect("inventory with more than i32::MAX used slots")
|
||||
- inv_slot_for_equipped // +1 inventory slot required if an item was unequipped
|
||||
+ inv_slot_for_inv_item // -1 inventory slot required if an item was equipped
|
||||
}
|
||||
|
||||
/// Swap item in an inventory slot with one in a loadout slot.
|
||||
#[must_use = "Returned items will be lost if not used"]
|
||||
pub fn swap_inventory_loadout(
|
||||
&mut self,
|
||||
inv_slot_id: InvSlotId,
|
||||
equip_slot: EquipSlot,
|
||||
) -> Option<Vec<Item>> {
|
||||
if !self.can_swap(inv_slot_id, equip_slot) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut unloaded_items = None;
|
||||
|
||||
// Take the item from the inventory
|
||||
let from_inv = self.remove(inv_slot_id);
|
||||
|
||||
// Swap the equipped item for the item from the inventory
|
||||
let from_equip = self.loadout.swap(equip_slot, from_inv);
|
||||
if let Some(mut from_equip) = from_equip {
|
||||
// Unload any items held inside the previously equipped item
|
||||
let items: Vec<Item> = from_equip.drain().collect();
|
||||
if items.iter().len() > 0 {
|
||||
unloaded_items = Some(items);
|
||||
}
|
||||
|
||||
// Attempt to put the unequipped item in the same slot that the inventory item
|
||||
// was in - if that slot no longer exists (because a large container was
|
||||
// swapped for a smaller one) then push the item to the first free
|
||||
// inventory slot instead.
|
||||
if let Err(returned) = self.insert_at(inv_slot_id, from_equip) {
|
||||
self.push(returned)
|
||||
.expect_none("Unable to push to inventory, no slots (bug in can_swap()?)");
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to put any items unloaded from the unequipped item into empty
|
||||
// inventory slots and return any that don't fit to the caller where they
|
||||
// will be dropped on the ground
|
||||
if let Some(unloaded_items) = unloaded_items {
|
||||
let leftovers = match self.push_all(unloaded_items.into_iter()) {
|
||||
Err(Error::Full(leftovers)) => leftovers,
|
||||
Ok(_) => vec![],
|
||||
};
|
||||
return Some(leftovers);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Determines if an inventory and loadout slot can be swapped, taking into
|
||||
/// account whether there will be free space in the inventory for the
|
||||
/// loadout item once any slots that were provided by it have been
|
||||
/// removed.
|
||||
pub fn can_swap(&self, inv_slot_id: InvSlotId, equip_slot: EquipSlot) -> bool {
|
||||
// Check if loadout slot can hold item
|
||||
if !self
|
||||
.get(inv_slot_id)
|
||||
.map_or(true, |item| equip_slot.can_hold(&item.kind()))
|
||||
{
|
||||
trace!("can_swap = false, equip slot can't hold item");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we're swapping an equipped item with an empty inventory slot, make
|
||||
// sure that there will be enough space in the inventory after any
|
||||
// slots granted by the item being unequipped have been removed.
|
||||
if let Some(inv_slot) = self.slot(inv_slot_id) {
|
||||
if inv_slot.is_none() && self.free_slots_minus_equipped_item(equip_slot) == 0 {
|
||||
// No free inventory slots after slots provided by the equipped
|
||||
//item are discounted
|
||||
trace!("can_swap = false, no free slots minus item");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"can_swap = false, tried to swap into non-existent inventory slot: {:?}",
|
||||
inv_slot_id
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Inventory {
|
||||
type Storage = HashMapStorage<Self>;
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -306,5 +669,3 @@ impl InventoryUpdate {
|
||||
impl Component for InventoryUpdate {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
||||
#[cfg(test)] mod test;
|
||||
|
@ -1,21 +1,84 @@
|
||||
use crate::{
|
||||
comp,
|
||||
comp::{
|
||||
item::{self, armor, tool::AbilityMap},
|
||||
ItemConfig,
|
||||
},
|
||||
};
|
||||
use comp::{Inventory, Loadout};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
use std::{cmp::Ordering, convert::TryFrom};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum Slot {
|
||||
Inventory(usize),
|
||||
Equip(EquipSlot),
|
||||
use crate::comp::{
|
||||
inventory::{
|
||||
item::{armor, armor::ArmorKind, ItemKind},
|
||||
loadout::LoadoutSlotId,
|
||||
},
|
||||
item,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum SlotError {
|
||||
InventoryFull,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum Slot {
|
||||
Inventory(InvSlotId),
|
||||
Equip(EquipSlot),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct InvSlotId {
|
||||
// The index of the loadout item that provides this inventory slot. 0 represents
|
||||
// built-in inventory slots
|
||||
loadout_idx: u16,
|
||||
// The index of the slot within its container
|
||||
slot_idx: u16,
|
||||
}
|
||||
|
||||
impl InvSlotId {
|
||||
pub const fn new(loadout_idx: u16, slot_idx: u16) -> Self {
|
||||
Self {
|
||||
loadout_idx,
|
||||
slot_idx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idx(&self) -> u32 { (u32::from(self.loadout_idx) << 16) | u32::from(self.slot_idx) }
|
||||
|
||||
pub fn loadout_idx(&self) -> usize { usize::from(self.loadout_idx) }
|
||||
|
||||
pub fn slot_idx(&self) -> usize { usize::from(self.slot_idx) }
|
||||
}
|
||||
|
||||
impl From<LoadoutSlotId> for InvSlotId {
|
||||
fn from(loadout_slot_id: LoadoutSlotId) -> Self {
|
||||
Self {
|
||||
loadout_idx: u16::try_from(loadout_slot_id.loadout_idx + 1).unwrap(),
|
||||
slot_idx: u16::try_from(loadout_slot_id.slot_idx).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for InvSlotId {
|
||||
fn partial_cmp(&self, other: &InvSlotId) -> Option<Ordering> { Some(self.cmp(other)) }
|
||||
}
|
||||
|
||||
impl Ord for InvSlotId {
|
||||
fn cmp(&self, other: &InvSlotId) -> Ordering { self.idx().cmp(&other.idx()) }
|
||||
}
|
||||
|
||||
pub(super) enum SlotId {
|
||||
Inventory(usize),
|
||||
Loadout(LoadoutSlotId),
|
||||
}
|
||||
|
||||
impl From<InvSlotId> for SlotId {
|
||||
fn from(inv_slot_id: InvSlotId) -> Self {
|
||||
match inv_slot_id.loadout_idx {
|
||||
0 => SlotId::Inventory(inv_slot_id.slot_idx()),
|
||||
_ => SlotId::Loadout(LoadoutSlotId {
|
||||
loadout_idx: inv_slot_id.loadout_idx() - 1,
|
||||
slot_idx: inv_slot_id.slot_idx(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum EquipSlot {
|
||||
Armor(ArmorSlot),
|
||||
Mainhand,
|
||||
@ -24,25 +87,26 @@ pub enum EquipSlot {
|
||||
Glider,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum ArmorSlot {
|
||||
Head,
|
||||
Neck,
|
||||
Shoulders,
|
||||
Chest,
|
||||
Hands,
|
||||
Ring,
|
||||
Ring1,
|
||||
Ring2,
|
||||
Back,
|
||||
Belt,
|
||||
Legs,
|
||||
Feet,
|
||||
Tabard,
|
||||
Bag1,
|
||||
Bag2,
|
||||
Bag3,
|
||||
Bag4,
|
||||
}
|
||||
|
||||
//const ALL_ARMOR_SLOTS: [ArmorSlot; 11] = [
|
||||
// Head, Neck, Shoulders, Chest, Hands, Ring, Back, Belt, Legs, Feet, Tabard,
|
||||
//];
|
||||
|
||||
impl Slot {
|
||||
pub fn can_hold(self, item_kind: &item::ItemKind) -> bool {
|
||||
match (self, item_kind) {
|
||||
@ -53,11 +117,9 @@ impl Slot {
|
||||
}
|
||||
|
||||
impl EquipSlot {
|
||||
fn can_hold(self, item_kind: &item::ItemKind) -> bool {
|
||||
use armor::Armor;
|
||||
use item::ItemKind;
|
||||
pub fn can_hold(self, item_kind: &item::ItemKind) -> bool {
|
||||
match (self, item_kind) {
|
||||
(Self::Armor(slot), ItemKind::Armor(Armor { kind, .. })) => slot.can_hold(kind),
|
||||
(Self::Armor(slot), ItemKind::Armor(armor::Armor { kind, .. })) => slot.can_hold(kind),
|
||||
(Self::Mainhand, ItemKind::Tool(_)) => true,
|
||||
(Self::Offhand, ItemKind::Tool(_)) => true,
|
||||
(Self::Lantern, ItemKind::Lantern(_)) => true,
|
||||
@ -69,7 +131,6 @@ impl EquipSlot {
|
||||
|
||||
impl ArmorSlot {
|
||||
fn can_hold(self, armor: &item::armor::ArmorKind) -> bool {
|
||||
use item::armor::ArmorKind;
|
||||
matches!(
|
||||
(self, armor),
|
||||
(Self::Head, ArmorKind::Head(_))
|
||||
@ -77,414 +138,17 @@ impl ArmorSlot {
|
||||
| (Self::Shoulders, ArmorKind::Shoulder(_))
|
||||
| (Self::Chest, ArmorKind::Chest(_))
|
||||
| (Self::Hands, ArmorKind::Hand(_))
|
||||
| (Self::Ring, ArmorKind::Ring(_))
|
||||
| (Self::Ring1, ArmorKind::Ring(_))
|
||||
| (Self::Ring2, ArmorKind::Ring(_))
|
||||
| (Self::Back, ArmorKind::Back(_))
|
||||
| (Self::Belt, ArmorKind::Belt(_))
|
||||
| (Self::Legs, ArmorKind::Pants(_))
|
||||
| (Self::Feet, ArmorKind::Foot(_))
|
||||
| (Self::Tabard, ArmorKind::Tabard(_))
|
||||
| (Self::Bag1, ArmorKind::Bag(_))
|
||||
| (Self::Bag2, ArmorKind::Bag(_))
|
||||
| (Self::Bag3, ArmorKind::Bag(_))
|
||||
| (Self::Bag4, ArmorKind::Bag(_))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace an equipment slot with an item. Return the item that was in the
|
||||
/// slot, if any. Doesn't update the inventory.
|
||||
fn loadout_replace(
|
||||
equip_slot: EquipSlot,
|
||||
item: Option<item::Item>,
|
||||
loadout: &mut Loadout,
|
||||
map: &AbilityMap,
|
||||
) -> Option<item::Item> {
|
||||
use std::mem::replace;
|
||||
match equip_slot {
|
||||
EquipSlot::Armor(ArmorSlot::Head) => replace(&mut loadout.head, item),
|
||||
EquipSlot::Armor(ArmorSlot::Neck) => replace(&mut loadout.neck, item),
|
||||
EquipSlot::Armor(ArmorSlot::Shoulders) => replace(&mut loadout.shoulder, item),
|
||||
EquipSlot::Armor(ArmorSlot::Chest) => replace(&mut loadout.chest, item),
|
||||
EquipSlot::Armor(ArmorSlot::Hands) => replace(&mut loadout.hand, item),
|
||||
EquipSlot::Armor(ArmorSlot::Ring) => replace(&mut loadout.ring, item),
|
||||
EquipSlot::Armor(ArmorSlot::Back) => replace(&mut loadout.back, item),
|
||||
EquipSlot::Armor(ArmorSlot::Belt) => replace(&mut loadout.belt, item),
|
||||
EquipSlot::Armor(ArmorSlot::Legs) => replace(&mut loadout.pants, item),
|
||||
EquipSlot::Armor(ArmorSlot::Feet) => replace(&mut loadout.foot, item),
|
||||
EquipSlot::Armor(ArmorSlot::Tabard) => replace(&mut loadout.tabard, item),
|
||||
EquipSlot::Lantern => replace(&mut loadout.lantern, item),
|
||||
EquipSlot::Glider => replace(&mut loadout.glider, item),
|
||||
EquipSlot::Mainhand => replace(
|
||||
&mut loadout.active_item,
|
||||
item.map(|item| ItemConfig::from((item, map))),
|
||||
)
|
||||
.map(|i| i.item),
|
||||
EquipSlot::Offhand => replace(
|
||||
&mut loadout.second_item,
|
||||
item.map(|item| ItemConfig::from((item, map))),
|
||||
)
|
||||
.map(|i| i.item),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert an item into a loadout. If the specified slot is already occupied
|
||||
/// the old item is returned.
|
||||
#[must_use]
|
||||
fn loadout_insert(
|
||||
equip_slot: EquipSlot,
|
||||
item: item::Item,
|
||||
loadout: &mut Loadout,
|
||||
map: &AbilityMap,
|
||||
) -> Option<item::Item> {
|
||||
loadout_replace(equip_slot, Some(item), loadout, map)
|
||||
}
|
||||
|
||||
/// Remove an item from a loadout.
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::{
|
||||
/// assets::AssetExt,
|
||||
/// comp::{
|
||||
/// item::tool::AbilityMap,
|
||||
/// slot::{loadout_remove, EquipSlot},
|
||||
/// Inventory,
|
||||
/// },
|
||||
/// LoadoutBuilder,
|
||||
/// };
|
||||
///
|
||||
/// let mut inv = Inventory::new_empty();
|
||||
///
|
||||
/// let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
///
|
||||
/// let mut loadout = LoadoutBuilder::new()
|
||||
/// .defaults()
|
||||
/// .active_item(Some(LoadoutBuilder::default_item_config_from_str(
|
||||
/// "common.items.weapons.sword.zweihander_sword_0",
|
||||
/// &map,
|
||||
/// )))
|
||||
/// .build();
|
||||
///
|
||||
/// let slot = EquipSlot::Mainhand;
|
||||
///
|
||||
/// loadout_remove(slot, &mut loadout, &map);
|
||||
/// assert_eq!(None, loadout.active_item);
|
||||
/// ```
|
||||
pub fn loadout_remove(
|
||||
equip_slot: EquipSlot,
|
||||
loadout: &mut Loadout,
|
||||
map: &AbilityMap,
|
||||
) -> Option<item::Item> {
|
||||
loadout_replace(equip_slot, None, loadout, map)
|
||||
}
|
||||
|
||||
/// Swap item in an inventory slot with one in a loadout slot.
|
||||
fn swap_inventory_loadout(
|
||||
inventory_slot: usize,
|
||||
equip_slot: EquipSlot,
|
||||
inventory: &mut Inventory,
|
||||
loadout: &mut Loadout,
|
||||
map: &AbilityMap,
|
||||
) {
|
||||
// Check if loadout slot can hold item
|
||||
if inventory
|
||||
.get(inventory_slot)
|
||||
.map_or(true, |item| equip_slot.can_hold(&item.kind()))
|
||||
{
|
||||
// Take item from loadout
|
||||
let from_equip = loadout_remove(equip_slot, loadout, map);
|
||||
// Swap with item in the inventory
|
||||
let from_inv = if let Some(item) = from_equip {
|
||||
// If this fails and we get item back as an err it will just be put back in the
|
||||
// loadout
|
||||
inventory.insert(inventory_slot, item).unwrap_or_else(Some)
|
||||
} else {
|
||||
inventory.remove(inventory_slot)
|
||||
};
|
||||
// Put item from the inventory in loadout
|
||||
if let Some(item) = from_inv {
|
||||
loadout_insert(equip_slot, item, loadout, map).unwrap_none(); // Can never fail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Swap items in loadout. Does nothing if items are not compatible with their
|
||||
/// new slots.
|
||||
fn swap_loadout(slot_a: EquipSlot, slot_b: EquipSlot, loadout: &mut Loadout, map: &AbilityMap) {
|
||||
// Ensure that the slots are not the same
|
||||
if slot_a == slot_b {
|
||||
warn!("Tried to swap equip slot with itself");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get items from the slots
|
||||
let item_a = loadout_remove(slot_a, loadout, map);
|
||||
let item_b = loadout_remove(slot_b, loadout, map);
|
||||
// Check if items can go in the other slots
|
||||
if item_a.as_ref().map_or(true, |i| slot_b.can_hold(&i.kind()))
|
||||
&& item_b.as_ref().map_or(true, |i| slot_a.can_hold(&i.kind()))
|
||||
{
|
||||
// Swap
|
||||
loadout_replace(slot_b, item_a, loadout, map).unwrap_none();
|
||||
loadout_replace(slot_a, item_b, loadout, map).unwrap_none();
|
||||
} else {
|
||||
// Otherwise put the items back
|
||||
loadout_replace(slot_a, item_a, loadout, map).unwrap_none();
|
||||
loadout_replace(slot_b, item_b, loadout, map).unwrap_none();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Should this report if a change actually occurred? (might be useful when
|
||||
// minimizing network use)
|
||||
|
||||
/// Swap items from two slots, regardless of if either is inventory or loadout.
|
||||
pub fn swap(
|
||||
slot_a: Slot,
|
||||
slot_b: Slot,
|
||||
inventory: Option<&mut Inventory>,
|
||||
loadout: Option<&mut Loadout>,
|
||||
map: &AbilityMap,
|
||||
) {
|
||||
match (slot_a, slot_b) {
|
||||
(Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => {
|
||||
inventory.map(|i| i.swap_slots(slot_a, slot_b));
|
||||
},
|
||||
(Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
|
||||
| (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
|
||||
if let Some((inventory, loadout)) = loadout.and_then(|l| inventory.map(|i| (i, l))) {
|
||||
swap_inventory_loadout(inv_slot, equip_slot, inventory, loadout, map);
|
||||
}
|
||||
},
|
||||
|
||||
(Slot::Equip(slot_a), Slot::Equip(slot_b)) => {
|
||||
loadout.map(|l| swap_loadout(slot_a, slot_b, l, map));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Equip an item from a slot in inventory. The currently equipped item will go
|
||||
/// into inventory. If the item is going to mainhand, put mainhand in
|
||||
/// offhand and place offhand into inventory.
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::{
|
||||
/// assets::AssetExt,
|
||||
/// comp::{
|
||||
/// item::tool::AbilityMap,
|
||||
/// slot::{equip, EquipSlot},
|
||||
/// Inventory, Item,
|
||||
/// },
|
||||
/// LoadoutBuilder,
|
||||
/// };
|
||||
///
|
||||
/// let boots = Item::new_from_asset_expect("common.items.testing.test_boots");
|
||||
///
|
||||
/// let mut inv = Inventory::new_empty();
|
||||
/// inv.push(boots.duplicate());
|
||||
///
|
||||
/// let mut loadout = LoadoutBuilder::new().defaults().build();
|
||||
///
|
||||
/// let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
///
|
||||
/// equip(0, &mut inv, &mut loadout, &map);
|
||||
/// assert_eq!(Some(boots), loadout.foot);
|
||||
/// ```
|
||||
pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout, map: &AbilityMap) {
|
||||
use armor::Armor;
|
||||
use item::{armor::ArmorKind, ItemKind};
|
||||
|
||||
let equip_slot = inventory.get(slot).and_then(|i| match &i.kind() {
|
||||
ItemKind::Tool(_) => Some(EquipSlot::Mainhand),
|
||||
ItemKind::Armor(Armor { kind, .. }) => Some(EquipSlot::Armor(match kind {
|
||||
ArmorKind::Head(_) => ArmorSlot::Head,
|
||||
ArmorKind::Neck(_) => ArmorSlot::Neck,
|
||||
ArmorKind::Shoulder(_) => ArmorSlot::Shoulders,
|
||||
ArmorKind::Chest(_) => ArmorSlot::Chest,
|
||||
ArmorKind::Hand(_) => ArmorSlot::Hands,
|
||||
ArmorKind::Ring(_) => ArmorSlot::Ring,
|
||||
ArmorKind::Back(_) => ArmorSlot::Back,
|
||||
ArmorKind::Belt(_) => ArmorSlot::Belt,
|
||||
ArmorKind::Pants(_) => ArmorSlot::Legs,
|
||||
ArmorKind::Foot(_) => ArmorSlot::Feet,
|
||||
ArmorKind::Tabard(_) => ArmorSlot::Tabard,
|
||||
})),
|
||||
ItemKind::Lantern(_) => Some(EquipSlot::Lantern),
|
||||
ItemKind::Glider(_) => Some(EquipSlot::Glider),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if let Some(equip_slot) = equip_slot {
|
||||
// If item is going to mainhand, put mainhand in offhand and place offhand in
|
||||
// inventory
|
||||
if let EquipSlot::Mainhand = equip_slot {
|
||||
swap_loadout(EquipSlot::Mainhand, EquipSlot::Offhand, loadout, map);
|
||||
}
|
||||
|
||||
swap_inventory_loadout(slot, equip_slot, inventory, loadout, map);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unequip an item from slot and place into inventory. Will leave the item
|
||||
/// equipped if inventory has no slots available.
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::{
|
||||
/// assets::AssetExt,
|
||||
/// comp::{
|
||||
/// item::tool::AbilityMap,
|
||||
/// slot::{unequip, EquipSlot},
|
||||
/// Inventory,
|
||||
/// },
|
||||
/// LoadoutBuilder,
|
||||
/// };
|
||||
///
|
||||
/// let mut inv = Inventory::new_empty();
|
||||
///
|
||||
/// let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
///
|
||||
/// let mut loadout = LoadoutBuilder::new()
|
||||
/// .defaults()
|
||||
/// .active_item(Some(LoadoutBuilder::default_item_config_from_str(
|
||||
/// "common.items.weapons.sword.zweihander_sword_0",
|
||||
/// &map,
|
||||
/// )))
|
||||
/// .build();
|
||||
///
|
||||
/// let slot = EquipSlot::Mainhand;
|
||||
///
|
||||
/// unequip(slot, &mut inv, &mut loadout, &map);
|
||||
/// assert_eq!(None, loadout.active_item);
|
||||
/// ```
|
||||
pub fn unequip(
|
||||
slot: EquipSlot,
|
||||
inventory: &mut Inventory,
|
||||
loadout: &mut Loadout,
|
||||
map: &AbilityMap,
|
||||
) {
|
||||
loadout_remove(slot, loadout, map) // Remove item from loadout
|
||||
.and_then(|i| inventory.push(i)) // Insert into inventory
|
||||
.and_then(|i| loadout_insert(slot, i, loadout, map)) // If that fails put back in loadout
|
||||
.unwrap_none(); // Never fails
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{assets::AssetExt, comp::Item, LoadoutBuilder};
|
||||
|
||||
#[test]
|
||||
fn test_unequip_items_both_hands() {
|
||||
let mut inv = Inventory {
|
||||
slots: vec![None],
|
||||
amount: 0,
|
||||
};
|
||||
|
||||
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
|
||||
let sword = LoadoutBuilder::default_item_config_from_str(
|
||||
"common.items.weapons.sword.zweihander_sword_0",
|
||||
&map,
|
||||
);
|
||||
|
||||
let mut loadout = LoadoutBuilder::new()
|
||||
.defaults()
|
||||
.active_item(Some(sword.clone()))
|
||||
.second_item(Some(sword.clone()))
|
||||
.build();
|
||||
|
||||
assert_eq!(Some(sword.clone()), loadout.active_item);
|
||||
unequip(EquipSlot::Mainhand, &mut inv, &mut loadout, &map);
|
||||
// We have space in the inventory, so this should have unequipped
|
||||
assert_eq!(None, loadout.active_item);
|
||||
|
||||
unequip(EquipSlot::Offhand, &mut inv, &mut loadout, &map);
|
||||
// There is no more space in the inventory, so this should still be equipped
|
||||
assert_eq!(Some(sword.clone()), loadout.second_item);
|
||||
|
||||
// Verify inventory
|
||||
assert_eq!(inv.slots[0], Some(sword.item));
|
||||
assert_eq!(inv.slots.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_equip_item() {
|
||||
let boots: Option<comp::Item> = Some(Item::new_from_asset_expect(
|
||||
"common.items.testing.test_boots",
|
||||
));
|
||||
|
||||
let starting_sandles: Option<comp::Item> = Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.starter.sandals_0",
|
||||
));
|
||||
|
||||
let mut inv = Inventory {
|
||||
slots: vec![boots.clone()],
|
||||
amount: 1,
|
||||
};
|
||||
|
||||
let mut loadout = LoadoutBuilder::new().defaults().build();
|
||||
|
||||
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
|
||||
// We should start with the starting sandles
|
||||
assert_eq!(starting_sandles, loadout.foot);
|
||||
equip(0, &mut inv, &mut loadout, &map);
|
||||
|
||||
// We should now have the testing boots equiped
|
||||
assert_eq!(boots, loadout.foot);
|
||||
|
||||
// Verify inventory
|
||||
assert_eq!(inv.slots[0], starting_sandles);
|
||||
assert_eq!(inv.slots.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_loadout_replace() {
|
||||
let boots: Option<comp::Item> = Some(Item::new_from_asset_expect(
|
||||
"common.items.testing.test_boots",
|
||||
));
|
||||
|
||||
let starting_sandles: Option<comp::Item> = Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.starter.sandals_0",
|
||||
));
|
||||
|
||||
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
|
||||
let mut loadout = LoadoutBuilder::new().defaults().build();
|
||||
|
||||
// We should start with the starting sandles
|
||||
assert_eq!(starting_sandles, loadout.foot);
|
||||
|
||||
// The swap should return the sandles
|
||||
assert_eq!(
|
||||
starting_sandles,
|
||||
loadout_replace(
|
||||
EquipSlot::Armor(ArmorSlot::Feet),
|
||||
boots.clone(),
|
||||
&mut loadout,
|
||||
&map,
|
||||
)
|
||||
);
|
||||
|
||||
// We should now have the testing boots equiped
|
||||
assert_eq!(boots, loadout.foot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_loadout_remove() {
|
||||
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
|
||||
|
||||
let sword = LoadoutBuilder::default_item_config_from_str(
|
||||
"common.items.weapons.sword.zweihander_sword_0",
|
||||
&map,
|
||||
);
|
||||
|
||||
let mut loadout = LoadoutBuilder::new()
|
||||
.defaults()
|
||||
.active_item(Some(sword.clone()))
|
||||
.build();
|
||||
|
||||
// The swap should return the sword
|
||||
assert_eq!(
|
||||
Some(sword.item),
|
||||
loadout_remove(EquipSlot::Mainhand, &mut loadout, &map)
|
||||
);
|
||||
|
||||
// We should now have nothing equiped
|
||||
assert_eq!(None, loadout.active_item);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use crate::comp::{
|
||||
inventory::{slot::ArmorSlot, test_helpers::get_test_bag},
|
||||
Item,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
lazy_static! {
|
||||
static ref TEST_ITEMS: Vec<Item> = vec![
|
||||
@ -12,7 +16,7 @@ lazy_static! {
|
||||
fn push_full() {
|
||||
let mut inv = Inventory {
|
||||
slots: TEST_ITEMS.iter().map(|a| Some(a.clone())).collect(),
|
||||
amount: 0,
|
||||
loadout: LoadoutBuilder::new().build(),
|
||||
};
|
||||
assert_eq!(
|
||||
inv.push(TEST_ITEMS[0].clone()).unwrap(),
|
||||
@ -25,7 +29,7 @@ fn push_full() {
|
||||
fn push_all_full() {
|
||||
let mut inv = Inventory {
|
||||
slots: TEST_ITEMS.iter().map(|a| Some(a.clone())).collect(),
|
||||
amount: 0,
|
||||
loadout: LoadoutBuilder::new().build(),
|
||||
};
|
||||
let Error::Full(leftovers) = inv
|
||||
.push_all(TEST_ITEMS.iter().cloned())
|
||||
@ -39,7 +43,7 @@ fn push_all_full() {
|
||||
fn push_unique_all_full() {
|
||||
let mut inv = Inventory {
|
||||
slots: TEST_ITEMS.iter().map(|a| Some(a.clone())).collect(),
|
||||
amount: 0,
|
||||
loadout: LoadoutBuilder::new().build(),
|
||||
};
|
||||
inv.push_all_unique(TEST_ITEMS.iter().cloned())
|
||||
.expect("Pushing unique items into an inventory that already contains them didn't work!");
|
||||
@ -51,7 +55,7 @@ fn push_unique_all_full() {
|
||||
fn push_all_empty() {
|
||||
let mut inv = Inventory {
|
||||
slots: vec![None, None],
|
||||
amount: 0,
|
||||
loadout: LoadoutBuilder::new().build(),
|
||||
};
|
||||
inv.push_all(TEST_ITEMS.iter().cloned())
|
||||
.expect("Pushing items into an empty inventory didn't work!");
|
||||
@ -63,9 +67,324 @@ fn push_all_empty() {
|
||||
fn push_all_unique_empty() {
|
||||
let mut inv = Inventory {
|
||||
slots: vec![None, None],
|
||||
amount: 0,
|
||||
loadout: LoadoutBuilder::new().build(),
|
||||
};
|
||||
inv.push_all_unique(TEST_ITEMS.iter().cloned()).expect(
|
||||
"Pushing unique items into an empty inventory that didn't contain them didn't work!",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_slots_minus_equipped_item_items_only_present_in_equipped_bag_slots() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
let bag = get_test_bag(18);
|
||||
let bag1_slot = EquipSlot::Armor(ArmorSlot::Bag1);
|
||||
inv.loadout.swap(bag1_slot, Some(bag.clone()));
|
||||
|
||||
inv.insert_at(InvSlotId::new(15, 0), bag)
|
||||
.unwrap()
|
||||
.unwrap_none();
|
||||
|
||||
let result = inv.free_slots_minus_equipped_item(bag1_slot);
|
||||
|
||||
// All of the base inventory slots are empty and the equipped bag slots are
|
||||
// ignored
|
||||
assert_eq!(18, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_slots_minus_equipped_item() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
let bag = get_test_bag(18);
|
||||
let bag1_slot = EquipSlot::Armor(ArmorSlot::Bag1);
|
||||
inv.loadout.swap(bag1_slot, Some(bag.clone()));
|
||||
inv.loadout
|
||||
.swap(EquipSlot::Armor(ArmorSlot::Bag2), Some(bag.clone()));
|
||||
|
||||
inv.insert_at(InvSlotId::new(16, 0), bag)
|
||||
.unwrap()
|
||||
.unwrap_none();
|
||||
|
||||
let result = inv.free_slots_minus_equipped_item(bag1_slot);
|
||||
|
||||
// All of the base 18 inventory slots are empty, the first equipped bag is
|
||||
// ignored, and the second equipped bag has 17 free slots
|
||||
assert_eq!(35, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_slot_range_for_equip_slot() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
let bag = get_test_bag(18);
|
||||
let bag1_slot = EquipSlot::Armor(ArmorSlot::Bag1);
|
||||
inv.loadout.swap(bag1_slot, Some(bag));
|
||||
|
||||
let result = inv.get_slot_range_for_equip_slot(bag1_slot).unwrap();
|
||||
|
||||
assert_eq!(18..36, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_swap_equipped_bag_into_empty_inv_slot_1_free_slot() {
|
||||
can_swap_equipped_bag_into_empty_inv_slot(1, InvSlotId::new(0, 17), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_swap_equipped_bag_into_empty_inv_slot_0_free_slots() {
|
||||
can_swap_equipped_bag_into_empty_inv_slot(0, InvSlotId::new(0, 17), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_swap_equipped_bag_into_empty_inv_slot_provided_by_equipped_bag() {
|
||||
can_swap_equipped_bag_into_empty_inv_slot(1, InvSlotId::new(15, 0), true);
|
||||
}
|
||||
|
||||
fn can_swap_equipped_bag_into_empty_inv_slot(
|
||||
free_slots: u16,
|
||||
inv_slot_id: InvSlotId,
|
||||
expected_result: bool,
|
||||
) {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
inv.replace_loadout_item(EquipSlot::Armor(ArmorSlot::Bag1), Some(get_test_bag(18)));
|
||||
|
||||
fill_inv_slots(&mut inv, 18 - free_slots);
|
||||
|
||||
let result = inv.can_swap(inv_slot_id, EquipSlot::Armor(ArmorSlot::Bag1));
|
||||
|
||||
assert_eq!(expected_result, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_swap_equipped_bag_into_only_empty_slot_provided_by_itself_should_return_false() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
inv.replace_loadout_item(EquipSlot::Armor(ArmorSlot::Bag1), Some(get_test_bag(18)));
|
||||
|
||||
fill_inv_slots(&mut inv, 35);
|
||||
|
||||
let result = inv.can_swap(InvSlotId::new(15, 17), EquipSlot::Armor(ArmorSlot::Bag1));
|
||||
|
||||
assert_eq!(false, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unequip_items_both_hands() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
let sword = Item::new_from_asset_expect("common.items.weapons.sword.zweihander_sword_0");
|
||||
|
||||
inv.replace_loadout_item(EquipSlot::Mainhand, Some(sword.clone()));
|
||||
inv.replace_loadout_item(EquipSlot::Offhand, Some(sword.clone()));
|
||||
|
||||
// Fill all inventory slots except one
|
||||
fill_inv_slots(&mut inv, 17);
|
||||
|
||||
let result = inv.unequip(EquipSlot::Mainhand);
|
||||
// We have space in the inventory, so this should have unequipped
|
||||
assert_eq!(None, inv.equipped(EquipSlot::Mainhand));
|
||||
assert_eq!(18, inv.populated_slots());
|
||||
assert_eq!(true, result.is_ok());
|
||||
|
||||
let result = inv.unequip(EquipSlot::Offhand).unwrap_err();
|
||||
assert_eq!(SlotError::InventoryFull, result);
|
||||
|
||||
// There is no more space in the inventory, so this should still be equipped
|
||||
assert_eq!(&sword, inv.equipped(EquipSlot::Offhand).unwrap());
|
||||
|
||||
// Verify inventory
|
||||
assert_eq!(inv.slots[17], Some(sword));
|
||||
assert_eq!(inv.free_slots(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equip_replace_already_equipped_item() {
|
||||
let boots = Item::new_from_asset_expect("common.items.testing.test_boots");
|
||||
|
||||
let starting_sandles = Some(Item::new_from_asset_expect(
|
||||
"common.items.armor.starter.sandals_0",
|
||||
));
|
||||
|
||||
let mut inv = Inventory::new_empty();
|
||||
inv.push(boots.clone());
|
||||
inv.replace_loadout_item(EquipSlot::Armor(ArmorSlot::Feet), starting_sandles.clone());
|
||||
|
||||
inv.equip(InvSlotId::new(0, 0)).unwrap_none();
|
||||
|
||||
// We should now have the testing boots equipped
|
||||
assert_eq!(
|
||||
&boots,
|
||||
inv.equipped(EquipSlot::Armor(ArmorSlot::Feet)).unwrap()
|
||||
);
|
||||
|
||||
// Verify inventory
|
||||
assert_eq!(
|
||||
inv.slots[0].as_ref().unwrap().item_definition_id(),
|
||||
starting_sandles.unwrap().item_definition_id()
|
||||
);
|
||||
assert_eq!(inv.populated_slots(), 1);
|
||||
}
|
||||
|
||||
/// Regression test for a panic that occurred when swapping an equipped bag
|
||||
/// for a bag that exists in an inventory slot that will no longer exist
|
||||
/// after equipping it (because the equipped bag is larger)
|
||||
#[test]
|
||||
fn equip_equipping_smaller_bag_from_last_slot_of_big_bag() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
const LARGE_BAG_ID: &str = "common.items.testing.test_bag_18_slot";
|
||||
let small_bag = get_test_bag(9);
|
||||
let large_bag = Item::new_from_asset_expect(LARGE_BAG_ID);
|
||||
|
||||
inv.loadout
|
||||
.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(large_bag))
|
||||
.unwrap_none();
|
||||
|
||||
inv.insert_at(InvSlotId::new(15, 15), small_bag).unwrap();
|
||||
|
||||
let result = inv.swap(
|
||||
Slot::Equip(EquipSlot::Armor(ArmorSlot::Bag1)),
|
||||
Slot::Inventory(InvSlotId::new(15, 15)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
inv.get(InvSlotId::new(0, 0)).unwrap().item_definition_id(),
|
||||
LARGE_BAG_ID
|
||||
);
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unequip_unequipping_bag_into_its_own_slot_with_no_other_free_slots() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
let bag = get_test_bag(9);
|
||||
|
||||
inv.loadout
|
||||
.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag))
|
||||
.unwrap_none();
|
||||
|
||||
// Fill all inventory built-in slots
|
||||
fill_inv_slots(&mut inv, 18);
|
||||
|
||||
inv.swap_inventory_loadout(InvSlotId::new(15, 0), EquipSlot::Armor(ArmorSlot::Bag1))
|
||||
.unwrap_none();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equip_one_bag_equipped_equip_second_bag() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
let bag = get_test_bag(9);
|
||||
inv.loadout
|
||||
.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag.clone()))
|
||||
.unwrap_none();
|
||||
|
||||
inv.push(bag);
|
||||
|
||||
inv.equip(InvSlotId::new(0, 0)).unwrap_none();
|
||||
|
||||
assert_eq!(
|
||||
true,
|
||||
inv.equipped(EquipSlot::Armor(ArmorSlot::Bag2)).is_some()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_after_swap_equipped_item_has_more_slots() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
let bag = get_test_bag(18);
|
||||
inv.loadout
|
||||
.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag))
|
||||
.unwrap_none();
|
||||
|
||||
let small_bag = get_test_bag(9);
|
||||
inv.push(small_bag);
|
||||
|
||||
// Fill all remaining slots
|
||||
fill_inv_slots(&mut inv, 35);
|
||||
|
||||
let result = inv.free_after_swap(EquipSlot::Armor(ArmorSlot::Bag1), InvSlotId::new(0, 0));
|
||||
|
||||
// 18 inv slots + 9 bag slots - 36 used slots -
|
||||
assert_eq!(-9, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_after_swap_equipped_item_has_less_slots() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
let bag = get_test_bag(9);
|
||||
inv.loadout
|
||||
.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag))
|
||||
.unwrap_none();
|
||||
|
||||
let small_bag = get_test_bag(18);
|
||||
inv.push(small_bag);
|
||||
|
||||
// Fill all slots except the last one
|
||||
fill_inv_slots(&mut inv, 27);
|
||||
|
||||
let result = inv.free_after_swap(EquipSlot::Armor(ArmorSlot::Bag1), InvSlotId::new(0, 0));
|
||||
|
||||
// 18 inv slots + 18 bag slots - 27 used slots
|
||||
assert_eq!(9, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_after_swap_equipped_item_with_slots_swapped_with_empty_inv_slot() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
let bag = get_test_bag(9);
|
||||
inv.loadout
|
||||
.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(bag))
|
||||
.unwrap_none();
|
||||
|
||||
// Add 5 items to the inventory
|
||||
fill_inv_slots(&mut inv, 5);
|
||||
|
||||
let result = inv.free_after_swap(EquipSlot::Armor(ArmorSlot::Bag1), InvSlotId::new(0, 10));
|
||||
|
||||
// 18 inv slots - 5 used slots - 1 slot for unequipped item
|
||||
assert_eq!(12, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_after_swap_inv_item_with_slots_swapped_with_empty_equip_slot() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
inv.push(get_test_bag(9));
|
||||
|
||||
// Add 5 items to the inventory
|
||||
fill_inv_slots(&mut inv, 5);
|
||||
|
||||
let result = inv.free_after_swap(EquipSlot::Armor(ArmorSlot::Bag1), InvSlotId::new(0, 0));
|
||||
|
||||
// 18 inv slots + 9 bag slots - 5 used slots
|
||||
assert_eq!(22, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_after_swap_inv_item_without_slots_swapped_with_empty_equip_slot() {
|
||||
let mut inv = Inventory::new_empty();
|
||||
|
||||
let boots = Item::new_from_asset_expect("common.items.testing.test_boots");
|
||||
inv.push(boots);
|
||||
|
||||
// Add 5 items to the inventory
|
||||
fill_inv_slots(&mut inv, 5);
|
||||
|
||||
let result = inv.free_after_swap(EquipSlot::Armor(ArmorSlot::Feet), InvSlotId::new(0, 0));
|
||||
|
||||
// 18 inv slots - 5 used slots
|
||||
assert_eq!(13, result);
|
||||
}
|
||||
|
||||
fn fill_inv_slots(inv: &mut Inventory, items: u16) {
|
||||
let boots = Item::new_from_asset_expect("common.items.testing.test_boots");
|
||||
for _ in 0..items {
|
||||
inv.push(boots.clone());
|
||||
}
|
||||
}
|
||||
|
24
common/src/comp/inventory/test_helpers.rs
Normal file
24
common/src/comp/inventory/test_helpers.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use crate::comp::{
|
||||
inventory::item::{
|
||||
armor,
|
||||
armor::{ArmorKind, Protection},
|
||||
ItemDef, ItemKind, Quality,
|
||||
},
|
||||
Item,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(super) fn get_test_bag(slots: u16) -> Item {
|
||||
let item_def = ItemDef::new_test(
|
||||
"common.items.testing.test_bag".to_string(),
|
||||
None,
|
||||
ItemKind::Armor(armor::Armor::test_armor(
|
||||
ArmorKind::Bag("Test Bag".to_string()),
|
||||
Protection::Normal(0.0),
|
||||
)),
|
||||
Quality::Common,
|
||||
slots,
|
||||
);
|
||||
|
||||
Item::new_from_item_def(Arc::new(item_def))
|
||||
}
|
@ -13,7 +13,7 @@ pub mod group;
|
||||
mod health;
|
||||
pub mod home_chunk;
|
||||
mod inputs;
|
||||
mod inventory;
|
||||
pub mod inventory;
|
||||
mod last;
|
||||
mod location;
|
||||
mod misc;
|
||||
@ -26,7 +26,7 @@ mod stats;
|
||||
pub mod visual;
|
||||
|
||||
// Reexports
|
||||
pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout};
|
||||
pub use ability::{CharacterAbility, CharacterAbilityType};
|
||||
pub use admin::Admin;
|
||||
pub use agent::{Agent, Alignment};
|
||||
pub use aura::{Aura, AuraChange, AuraKind, Auras};
|
||||
@ -54,7 +54,7 @@ pub use home_chunk::HomeChunk;
|
||||
pub use inputs::CanBuild;
|
||||
pub use inventory::{
|
||||
item,
|
||||
item::{Item, ItemDrop},
|
||||
item::{Item, ItemConfig, ItemDrop},
|
||||
slot, Inventory, InventoryUpdate, InventoryUpdateEvent,
|
||||
};
|
||||
pub use last::Last;
|
||||
|
@ -88,7 +88,6 @@ pub enum ServerEvent {
|
||||
comp::Body,
|
||||
comp::Stats,
|
||||
comp::Inventory,
|
||||
comp::Loadout,
|
||||
Option<comp::Waypoint>,
|
||||
),
|
||||
},
|
||||
@ -100,7 +99,7 @@ pub enum ServerEvent {
|
||||
pos: comp::Pos,
|
||||
stats: comp::Stats,
|
||||
health: comp::Health,
|
||||
loadout: comp::Loadout,
|
||||
loadout: comp::inventory::loadout::Loadout,
|
||||
body: comp::Body,
|
||||
agent: Option<comp::Agent>,
|
||||
alignment: comp::Alignment,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user