diff --git a/Cargo.lock b/Cargo.lock index ffd6f77b0c..adc7000c1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,6 +428,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "bstr" version = "0.2.17" @@ -962,6 +971,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + [[package]] name = "cranelift-bforest" version = "0.74.0" @@ -1381,6 +1399,15 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2c9736e15e7df1638a7f6eee92a6511615c738246a052af5ba86f039b65aede" +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "directories-next" version = "2.0.0" @@ -3804,6 +3831,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl-probe" version = "0.1.4" @@ -5122,6 +5155,19 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + [[package]] name = "shaderc" version = "0.6.3" @@ -6169,7 +6215,9 @@ dependencies = [ "ron 0.7.0", "roots", "serde", + "serde_json", "serde_repr", + "sha2", "slab", "slotmap 1.0.6", "specs", diff --git a/assets/common/skill_trees/skill_max_levels.ron b/assets/common/skill_trees/skill_max_levels.ron index 4febf73b1e..cae7e5b56b 100644 --- a/assets/common/skill_trees/skill_max_levels.ron +++ b/assets/common/skill_trees/skill_max_levels.ron @@ -1,83 +1,83 @@ ({ - General(HealthIncrease): Some(10), - General(EnergyIncrease): Some(5), - Sword(TsDamage): Some(3), - Sword(TsRegen): Some(2), - Sword(TsSpeed): Some(3), - Sword(DCost): Some(2), - Sword(DDrain): Some(2), - Sword(DDamage): Some(2), - Sword(DScaling): Some(3), - Sword(SDamage): Some(2), - Sword(SSpeed): Some(2), - Sword(SCost): Some(2), - Sword(SSpins): Some(2), - Axe(DsDamage): Some(3), - Axe(DsRegen): Some(2), - Axe(DsSpeed): Some(3), - Axe(SDamage): Some(3), - Axe(SSpeed): Some(2), - Axe(SCost): Some(2), - Axe(LDamage): Some(2), - Axe(LKnockback): Some(2), - Axe(LCost): Some(2), - Axe(LDistance): Some(2), - Hammer(SsKnockback): Some(2), - Hammer(SsDamage): Some(3), - Hammer(SsRegen): Some(2), - Hammer(SsSpeed): Some(3), - Hammer(CDamage): Some(3), - Hammer(CKnockback): Some(3), - Hammer(CDrain): Some(2), - Hammer(CSpeed): Some(2), - Hammer(LDamage): Some(2), - Hammer(LCost): Some(2), - Hammer(LDistance): Some(2), - Hammer(LKnockback): Some(2), - Hammer(LRange): Some(2), - Bow(ProjSpeed): Some(2), - Bow(CDamage): Some(3), - Bow(CRegen): Some(2), - Bow(CKnockback): Some(2), - Bow(CSpeed): Some(2), - Bow(CMove): Some(2), - Bow(RDamage): Some(3), - Bow(RCost): Some(2), - Bow(RSpeed): Some(2), - Bow(SDamage): Some(2), - Bow(SCost): Some(2), - Bow(SArrows): Some(2), - Bow(SSpread): Some(2), - Staff(BDamage): Some(3), - Staff(BRegen): Some(2), - Staff(BRadius): Some(3), - Staff(FRange): Some(2), - Staff(FDamage): Some(3), - Staff(FDrain): Some(2), - Staff(FVelocity): Some(2), - Staff(SDamage): Some(2), - Staff(SKnockback): Some(2), - Staff(SRange): Some(2), - Staff(SCost): Some(2), - Sceptre(LDamage): Some(3), - Sceptre(LRange): Some(2), - Sceptre(LLifesteal): Some(3), - Sceptre(LRegen): Some(2), - Sceptre(HHeal): Some(3), - Sceptre(HRange): Some(2), - Sceptre(HDuration): Some(2), - Sceptre(HCost): Some(2), - Sceptre(AStrength): Some(2), - Sceptre(ADuration): Some(2), - Sceptre(ARange): Some(2), - Sceptre(ACost): Some(2), - Roll(Cost): Some(2), - Roll(Strength): Some(2), - Roll(Duration): Some(2), - Climb(Cost): Some(2), - Climb(Speed): Some(2), - Swim(Speed): Some(2), - Pick(Speed): Some(3), - Pick(OreGain): Some(3), - Pick(GemGain): Some(3), + General(HealthIncrease): 10, + General(EnergyIncrease): 5, + Sword(TsDamage): 3, + Sword(TsRegen): 2, + Sword(TsSpeed): 3, + Sword(DCost): 2, + Sword(DDrain): 2, + Sword(DDamage): 2, + Sword(DScaling): 3, + Sword(SDamage): 2, + Sword(SSpeed): 2, + Sword(SCost): 2, + Sword(SSpins): 2, + Axe(DsDamage): 3, + Axe(DsRegen): 2, + Axe(DsSpeed): 3, + Axe(SDamage): 3, + Axe(SSpeed): 2, + Axe(SCost): 2, + Axe(LDamage): 2, + Axe(LKnockback): 2, + Axe(LCost): 2, + Axe(LDistance): 2, + Hammer(SsKnockback): 2, + Hammer(SsDamage): 3, + Hammer(SsRegen): 2, + Hammer(SsSpeed): 3, + Hammer(CDamage): 3, + Hammer(CKnockback): 3, + Hammer(CDrain): 2, + Hammer(CSpeed): 2, + Hammer(LDamage): 2, + Hammer(LCost): 2, + Hammer(LDistance): 2, + Hammer(LKnockback): 2, + Hammer(LRange): 2, + Bow(ProjSpeed): 2, + Bow(CDamage): 3, + Bow(CRegen): 2, + Bow(CKnockback): 2, + Bow(CSpeed): 2, + Bow(CMove): 2, + Bow(RDamage): 3, + Bow(RCost): 2, + Bow(RSpeed): 2, + Bow(SDamage): 2, + Bow(SCost): 2, + Bow(SArrows): 2, + Bow(SSpread): 2, + Staff(BDamage): 3, + Staff(BRegen): 2, + Staff(BRadius): 3, + Staff(FRange): 2, + Staff(FDamage): 3, + Staff(FDrain): 2, + Staff(FVelocity): 2, + Staff(SDamage): 2, + Staff(SKnockback): 2, + Staff(SRange): 2, + Staff(SCost): 2, + Sceptre(LDamage): 3, + Sceptre(LRange): 2, + Sceptre(LLifesteal): 3, + Sceptre(LRegen): 2, + Sceptre(HHeal): 3, + Sceptre(HRange): 2, + Sceptre(HDuration): 2, + Sceptre(HCost): 2, + Sceptre(AStrength): 2, + Sceptre(ADuration): 2, + Sceptre(ARange): 2, + Sceptre(ACost): 2, + Roll(Cost): 2, + Roll(Strength): 2, + Roll(Duration): 2, + Climb(Cost): 2, + Climb(Speed): 2, + Swim(Speed): 2, + Pick(Speed): 3, + Pick(OreGain): 3, + Pick(GemGain): 3, }) diff --git a/assets/common/skill_trees/skill_prerequisites.ron b/assets/common/skill_trees/skill_prerequisites.ron index 764f217c89..3a0c77a7e8 100644 --- a/assets/common/skill_trees/skill_prerequisites.ron +++ b/assets/common/skill_trees/skill_prerequisites.ron @@ -1,27 +1,27 @@ ({ - Sword(SDamage): {Sword(UnlockSpin): None}, - Sword(SSpeed): {Sword(UnlockSpin): None}, - Sword(SCost): {Sword(UnlockSpin): None}, - Sword(SSpins): {Sword(UnlockSpin): None}, - Axe(LDamage): {Axe(UnlockLeap): None}, - Axe(LKnockback): {Axe(UnlockLeap): None}, - Axe(LCost): {Axe(UnlockLeap): None}, - Axe(LDistance): {Axe(UnlockLeap): None}, - Hammer(LDamage): {Hammer(UnlockLeap): None}, - Hammer(LCost): {Hammer(UnlockLeap): None}, - Hammer(LDistance): {Hammer(UnlockLeap): None}, - Hammer(LKnockback): {Hammer(UnlockLeap): None}, - Hammer(LRange): {Hammer(UnlockLeap): None}, - Bow(SDamage): {Bow(UnlockShotgun): None}, - Bow(SCost): {Bow(UnlockShotgun): None}, - Bow(SArrows): {Bow(UnlockShotgun): None}, - Bow(SSpread): {Bow(UnlockShotgun): None}, - Staff(SDamage): {Staff(UnlockShockwave): None}, - Staff(SKnockback): {Staff(UnlockShockwave): None}, - Staff(SRange): {Staff(UnlockShockwave): None}, - Staff(SCost): {Staff(UnlockShockwave): None}, - Sceptre(AStrength): {Sceptre(UnlockAura): None}, - Sceptre(ADuration): {Sceptre(UnlockAura): None}, - Sceptre(ARange): {Sceptre(UnlockAura): None}, - Sceptre(ACost): {Sceptre(UnlockAura): None}, + Sword(SDamage): {Sword(UnlockSpin): 1}, + Sword(SSpeed): {Sword(UnlockSpin): 1}, + Sword(SCost): {Sword(UnlockSpin): 1}, + Sword(SSpins): {Sword(UnlockSpin): 1}, + Axe(LDamage): {Axe(UnlockLeap): 1}, + Axe(LKnockback): {Axe(UnlockLeap): 1}, + Axe(LCost): {Axe(UnlockLeap): 1}, + Axe(LDistance): {Axe(UnlockLeap): 1}, + Hammer(LDamage): {Hammer(UnlockLeap): 1}, + Hammer(LCost): {Hammer(UnlockLeap): 1}, + Hammer(LDistance): {Hammer(UnlockLeap): 1}, + Hammer(LKnockback): {Hammer(UnlockLeap): 1}, + Hammer(LRange): {Hammer(UnlockLeap): 1}, + Bow(SDamage): {Bow(UnlockShotgun): 1}, + Bow(SCost): {Bow(UnlockShotgun): 1}, + Bow(SArrows): {Bow(UnlockShotgun): 1}, + Bow(SSpread): {Bow(UnlockShotgun): 1}, + Staff(SDamage): {Staff(UnlockShockwave): 1}, + Staff(SKnockback): {Staff(UnlockShockwave): 1}, + Staff(SRange): {Staff(UnlockShockwave): 1}, + Staff(SCost): {Staff(UnlockShockwave): 1}, + Sceptre(AStrength): {Sceptre(UnlockAura): 1}, + Sceptre(ADuration): {Sceptre(UnlockAura): 1}, + Sceptre(ARange): {Sceptre(UnlockAura): 1}, + Sceptre(ACost): {Sceptre(UnlockAura): 1}, }) \ No newline at end of file diff --git a/assets/common/skill_trees/skills_skill-groups_manifest.ron b/assets/common/skill_trees/skills_skill-groups_manifest.ron index 557eb2ea24..ee32be2124 100644 --- a/assets/common/skill_trees/skills_skill-groups_manifest.ron +++ b/assets/common/skill_trees/skills_skill-groups_manifest.ron @@ -26,7 +26,7 @@ Sword(DDamage), Sword(DScaling), Sword(DSpeed), - Sword(DInfinite), + Sword(DChargeThrough), Sword(UnlockSpin), Sword(SDamage), Sword(SSpeed), diff --git a/assets/common/skillset/preset/rank1/axe.ron b/assets/common/skillset/preset/rank1/axe.ron index b311c0a8fb..3e88edd3ec 100644 --- a/assets/common/skillset/preset/rank1/axe.ron +++ b/assets/common/skillset/preset/rank1/axe.ron @@ -2,9 +2,9 @@ Group(Weapon(Axe)), // DoubleStrike - Skill((Axe(DsDamage), Some(1))), + Skill((Axe(DsDamage), 1)), // Spin - Skill((Axe(SInfinite), None)), - Skill((Axe(SDamage), Some(1))), + Skill((Axe(SInfinite), 1)), + Skill((Axe(SDamage), 1)), ]) diff --git a/assets/common/skillset/preset/rank1/bow.ron b/assets/common/skillset/preset/rank1/bow.ron index 654828bf17..7db2f35af8 100644 --- a/assets/common/skillset/preset/rank1/bow.ron +++ b/assets/common/skillset/preset/rank1/bow.ron @@ -2,13 +2,13 @@ Group(Weapon(Bow)), // Projectile Speed - Skill((Bow(ProjSpeed), Some(1))), + Skill((Bow(ProjSpeed), 1)), // Charged - Skill((Bow(CDamage), Some(1))), - Skill((Bow(CKnockback), Some(1))), + Skill((Bow(CDamage), 1)), + Skill((Bow(CKnockback), 1)), // Repeater - Skill((Bow(RDamage), Some(1))), + Skill((Bow(RDamage), 1)), ]) diff --git a/assets/common/skillset/preset/rank1/general.ron b/assets/common/skillset/preset/rank1/general.ron index 66c8d5ebd0..df015df295 100644 --- a/assets/common/skillset/preset/rank1/general.ron +++ b/assets/common/skillset/preset/rank1/general.ron @@ -1,4 +1,4 @@ ([ - Skill((General(HealthIncrease), Some(2))), - Skill((General(EnergyIncrease), Some(1))), + Skill((General(HealthIncrease), 2)), + Skill((General(EnergyIncrease), 1)), ]) diff --git a/assets/common/skillset/preset/rank1/hammer.ron b/assets/common/skillset/preset/rank1/hammer.ron index 28f71d0fcb..8bd38bc888 100644 --- a/assets/common/skillset/preset/rank1/hammer.ron +++ b/assets/common/skillset/preset/rank1/hammer.ron @@ -2,11 +2,11 @@ Group(Weapon(Hammer)), // Single Strike, as single as you are - Skill((Hammer(SsKnockback), Some(1))), - Skill((Hammer(SsDamage), Some(1))), + Skill((Hammer(SsKnockback), 1)), + Skill((Hammer(SsDamage), 1)), // Charged - Skill((Hammer(CDamage), Some(1))), - Skill((Hammer(CKnockback), Some(1))), + Skill((Hammer(CDamage), 1)), + Skill((Hammer(CKnockback), 1)), ]) diff --git a/assets/common/skillset/preset/rank1/sceptre.ron b/assets/common/skillset/preset/rank1/sceptre.ron index cadf369907..2e100d875a 100644 --- a/assets/common/skillset/preset/rank1/sceptre.ron +++ b/assets/common/skillset/preset/rank1/sceptre.ron @@ -2,11 +2,11 @@ Group(Weapon(Sceptre)), // Beam - Skill((Sceptre(LDamage), Some(1))), - Skill((Sceptre(LRange), Some(1))), + Skill((Sceptre(LDamage), 1)), + Skill((Sceptre(LRange), 1)), // Heal - Skill((Sceptre(HHeal), Some(1))), - Skill((Sceptre(HDuration), Some(1))), + Skill((Sceptre(HHeal), 1)), + Skill((Sceptre(HDuration), 1)), ]) diff --git a/assets/common/skillset/preset/rank1/staff.ron b/assets/common/skillset/preset/rank1/staff.ron index 144cd98a5c..032774bc3d 100644 --- a/assets/common/skillset/preset/rank1/staff.ron +++ b/assets/common/skillset/preset/rank1/staff.ron @@ -2,11 +2,11 @@ Group(Weapon(Staff)), // Fireball - Skill((Staff(BDamage), Some(2))), - Skill((Staff(BRegen), Some(2))), - Skill((Staff(BRadius), Some(1))), + Skill((Staff(BDamage), 2)), + Skill((Staff(BRegen), 2)), + Skill((Staff(BRadius), 1)), // Flamethrower - Skill((Staff(FRange), Some(1))), + Skill((Staff(FRange), 1)), ]) \ No newline at end of file diff --git a/assets/common/skillset/preset/rank1/sword.ron b/assets/common/skillset/preset/rank1/sword.ron index 1a8ac2670b..995ccafb16 100644 --- a/assets/common/skillset/preset/rank1/sword.ron +++ b/assets/common/skillset/preset/rank1/sword.ron @@ -1,14 +1,14 @@ ([ Group(Weapon(Sword)), - Skill((Sword(InterruptingAttacks), None)), + Skill((Sword(InterruptingAttacks), 1)), // TripleStrike - Skill((Sword(TsCombo), None)), - Skill((Sword(TsDamage), Some(1))), - Skill((Sword(TsRegen), Some(1))), + Skill((Sword(TsCombo), 1)), + Skill((Sword(TsDamage), 1)), + Skill((Sword(TsRegen), 1)), // Dash - Skill((Sword(DCost), Some(1))), + Skill((Sword(DCost), 1)), ]) diff --git a/assets/common/skillset/preset/rank2/axe.ron b/assets/common/skillset/preset/rank2/axe.ron index 1de7ffe695..b5308f3565 100644 --- a/assets/common/skillset/preset/rank2/axe.ron +++ b/assets/common/skillset/preset/rank2/axe.ron @@ -2,13 +2,13 @@ Group(Weapon(Axe)), // DoubleStrike - Skill((Axe(DsCombo), None)), - Skill((Axe(DsDamage), Some(1))), - Skill((Axe(DsRegen), Some(1))), + Skill((Axe(DsCombo), 1)), + Skill((Axe(DsDamage), 1)), + Skill((Axe(DsRegen), 1)), // Spin - Skill((Axe(SInfinite), None)), - Skill((Axe(SHelicopter), None)), - Skill((Axe(SDamage), Some(1))), - Skill((Axe(SSpeed), Some(1))), + Skill((Axe(SInfinite), 1)), + Skill((Axe(SHelicopter), 1)), + Skill((Axe(SDamage), 1)), + Skill((Axe(SSpeed), 1)), ]) diff --git a/assets/common/skillset/preset/rank2/bow.ron b/assets/common/skillset/preset/rank2/bow.ron index 9909e277d1..c2a9182d9f 100644 --- a/assets/common/skillset/preset/rank2/bow.ron +++ b/assets/common/skillset/preset/rank2/bow.ron @@ -2,16 +2,16 @@ Group(Weapon(Bow)), // Projectile Speed - Skill((Bow(ProjSpeed), Some(1))), + Skill((Bow(ProjSpeed), 1)), // Charged - Skill((Bow(CDamage), Some(1))), - Skill((Bow(CKnockback), Some(1))), - Skill((Bow(CSpeed), Some(1))), - Skill((Bow(CRegen), Some(1))), + Skill((Bow(CDamage), 1)), + Skill((Bow(CKnockback), 1)), + Skill((Bow(CSpeed), 1)), + Skill((Bow(CRegen), 1)), // Repeater - Skill((Bow(RDamage), Some(1))), - Skill((Bow(RCost), Some(1))), + Skill((Bow(RDamage), 1)), + Skill((Bow(RCost), 1)), ]) diff --git a/assets/common/skillset/preset/rank2/general.ron b/assets/common/skillset/preset/rank2/general.ron index 8849b057e9..1cca4b26c8 100644 --- a/assets/common/skillset/preset/rank2/general.ron +++ b/assets/common/skillset/preset/rank2/general.ron @@ -1,4 +1,4 @@ ([ - Skill((General(HealthIncrease), Some(4))), - Skill((General(EnergyIncrease), Some(2))), + Skill((General(HealthIncrease), 4)), + Skill((General(EnergyIncrease), 2)), ]) diff --git a/assets/common/skillset/preset/rank2/hammer.ron b/assets/common/skillset/preset/rank2/hammer.ron index 5a1904baa4..9cb93a115e 100644 --- a/assets/common/skillset/preset/rank2/hammer.ron +++ b/assets/common/skillset/preset/rank2/hammer.ron @@ -2,15 +2,15 @@ Group(Weapon(Hammer)), // Single Strike, as single as you are - Skill((Hammer(SsKnockback), Some(1))), - Skill((Hammer(SsDamage), Some(2))), - Skill((Hammer(SsRegen), Some(1))), - Skill((Hammer(SsSpeed), Some(1))), + Skill((Hammer(SsKnockback), 1)), + Skill((Hammer(SsDamage), 2)), + Skill((Hammer(SsRegen), 1)), + Skill((Hammer(SsSpeed), 1)), // Charged - Skill((Hammer(CDamage), Some(1))), - Skill((Hammer(CKnockback), Some(1))), - Skill((Hammer(CDrain), Some(1))), - Skill((Hammer(CSpeed), Some(1))), + Skill((Hammer(CDamage), 1)), + Skill((Hammer(CKnockback), 1)), + Skill((Hammer(CDrain), 1)), + Skill((Hammer(CSpeed), 1)), ]) diff --git a/assets/common/skillset/preset/rank2/sceptre.ron b/assets/common/skillset/preset/rank2/sceptre.ron index a935e378f9..876ca6db12 100644 --- a/assets/common/skillset/preset/rank2/sceptre.ron +++ b/assets/common/skillset/preset/rank2/sceptre.ron @@ -2,15 +2,15 @@ Group(Weapon(Sceptre)), // Beam - Skill((Sceptre(LDamage), Some(1))), - Skill((Sceptre(LRange), Some(1))), - Skill((Sceptre(LLifesteal), Some(1))), - Skill((Sceptre(LRegen), Some(1))), + Skill((Sceptre(LDamage), 1)), + Skill((Sceptre(LRange), 1)), + Skill((Sceptre(LLifesteal), 1)), + Skill((Sceptre(LRegen), 1)), // Heal - Skill((Sceptre(HHeal), Some(1))), - Skill((Sceptre(HDuration), Some(1))), - Skill((Sceptre(HRange), Some(1))), - Skill((Sceptre(HCost), Some(1))), + Skill((Sceptre(HHeal), 1)), + Skill((Sceptre(HDuration), 1)), + Skill((Sceptre(HRange), 1)), + Skill((Sceptre(HCost), 1)), ]) diff --git a/assets/common/skillset/preset/rank2/staff.ron b/assets/common/skillset/preset/rank2/staff.ron index 6316e9e71b..5cc3a75954 100644 --- a/assets/common/skillset/preset/rank2/staff.ron +++ b/assets/common/skillset/preset/rank2/staff.ron @@ -2,12 +2,12 @@ Group(Weapon(Staff)), // Fireball - Skill((Staff(BDamage), Some(2))), - Skill((Staff(BRegen), Some(2))), - Skill((Staff(BRadius), Some(1))), + Skill((Staff(BDamage), 2)), + Skill((Staff(BRegen), 2)), + Skill((Staff(BRadius), 1)), // Flamethrower - Skill((Staff(FRange), Some(2))), - Skill((Staff(FDamage), Some(2))), + Skill((Staff(FRange), 2)), + Skill((Staff(FDamage), 2)), ]) diff --git a/assets/common/skillset/preset/rank2/sword.ron b/assets/common/skillset/preset/rank2/sword.ron index bd7fdb164c..f325def41b 100644 --- a/assets/common/skillset/preset/rank2/sword.ron +++ b/assets/common/skillset/preset/rank2/sword.ron @@ -1,17 +1,17 @@ ([ Group(Weapon(Sword)), - Skill((Sword(InterruptingAttacks), None)), + Skill((Sword(InterruptingAttacks), 1)), // TripleStrike - Skill((Sword(TsCombo), None)), - Skill((Sword(TsDamage), Some(1))), - Skill((Sword(TsRegen), Some(1))), - Skill((Sword(TsSpeed), Some(1))), + Skill((Sword(TsCombo), 1)), + Skill((Sword(TsDamage), 1)), + Skill((Sword(TsRegen), 1)), + Skill((Sword(TsSpeed), 1)), // Dash - Skill((Sword(DCost), Some(1))), - Skill((Sword(DDrain), Some(1))), - Skill((Sword(DDamage), Some(1))), + Skill((Sword(DCost), 1)), + Skill((Sword(DDrain), 1)), + Skill((Sword(DDamage), 1)), ]) diff --git a/assets/common/skillset/preset/rank3/axe.ron b/assets/common/skillset/preset/rank3/axe.ron index 752888e1f9..28851292b3 100644 --- a/assets/common/skillset/preset/rank3/axe.ron +++ b/assets/common/skillset/preset/rank3/axe.ron @@ -2,19 +2,19 @@ Group(Weapon(Axe)), // DoubleStrike - Skill((Axe(DsCombo), None)), - Skill((Axe(DsDamage), Some(1))), - Skill((Axe(DsRegen), Some(1))), - Skill((Axe(DsSpeed), Some(1))), + Skill((Axe(DsCombo), 1)), + Skill((Axe(DsDamage), 1)), + Skill((Axe(DsRegen), 1)), + Skill((Axe(DsSpeed), 1)), // Spin - Skill((Axe(SInfinite), None)), - Skill((Axe(SHelicopter), None)), - Skill((Axe(SDamage), Some(1))), - Skill((Axe(SSpeed), Some(1))), - Skill((Axe(SCost), Some(1))), + Skill((Axe(SInfinite), 1)), + Skill((Axe(SHelicopter), 1)), + Skill((Axe(SDamage), 1)), + Skill((Axe(SSpeed), 1)), + Skill((Axe(SCost), 1)), // Leap - Skill((Axe(UnlockLeap), None)), - Skill((Axe(LDamage), Some(1))), + Skill((Axe(UnlockLeap), 1)), + Skill((Axe(LDamage), 1)), ]) diff --git a/assets/common/skillset/preset/rank3/bow.ron b/assets/common/skillset/preset/rank3/bow.ron index 3c90d864e7..76d5b3b26f 100644 --- a/assets/common/skillset/preset/rank3/bow.ron +++ b/assets/common/skillset/preset/rank3/bow.ron @@ -2,22 +2,22 @@ Group(Weapon(Bow)), // Projectile Speed - Skill((Bow(ProjSpeed), Some(2))), + Skill((Bow(ProjSpeed), 2)), // Charged - Skill((Bow(CDamage), Some(1))), - Skill((Bow(CKnockback), Some(1))), - Skill((Bow(CSpeed), Some(1))), - Skill((Bow(CRegen), Some(1))), - Skill((Bow(CMove), Some(1))), + Skill((Bow(CDamage), 1)), + Skill((Bow(CKnockback), 1)), + Skill((Bow(CSpeed), 1)), + Skill((Bow(CRegen), 1)), + Skill((Bow(CMove), 1)), // Repeater - Skill((Bow(RDamage), Some(1))), - Skill((Bow(RCost), Some(1))), - Skill((Bow(RSpeed), Some(1))), + Skill((Bow(RDamage), 1)), + Skill((Bow(RCost), 1)), + Skill((Bow(RSpeed), 1)), // Shotgun - Skill((Bow(UnlockShotgun), None)), - Skill((Bow(SDamage), Some(1))), - Skill((Bow(SCost), Some(1))), + Skill((Bow(UnlockShotgun), 1)), + Skill((Bow(SDamage), 1)), + Skill((Bow(SCost), 1)), ]) diff --git a/assets/common/skillset/preset/rank3/general.ron b/assets/common/skillset/preset/rank3/general.ron index 6b1cedbfe0..cec82cb4d8 100644 --- a/assets/common/skillset/preset/rank3/general.ron +++ b/assets/common/skillset/preset/rank3/general.ron @@ -1,4 +1,4 @@ ([ - Skill((General(HealthIncrease), Some(6))), - Skill((General(EnergyIncrease), Some(3))), + Skill((General(HealthIncrease), 6)), + Skill((General(EnergyIncrease), 3)), ]) diff --git a/assets/common/skillset/preset/rank3/hammer.ron b/assets/common/skillset/preset/rank3/hammer.ron index 7bf7799b10..ed4f7bb28c 100644 --- a/assets/common/skillset/preset/rank3/hammer.ron +++ b/assets/common/skillset/preset/rank3/hammer.ron @@ -2,20 +2,20 @@ Group(Weapon(Hammer)), // Single Strike, as single as you are - Skill((Hammer(SsKnockback), Some(2))), - Skill((Hammer(SsDamage), Some(2))), - Skill((Hammer(SsRegen), Some(1))), - Skill((Hammer(SsSpeed), Some(1))), + Skill((Hammer(SsKnockback), 2)), + Skill((Hammer(SsDamage), 2)), + Skill((Hammer(SsRegen), 1)), + Skill((Hammer(SsSpeed), 1)), // Charged - Skill((Hammer(CDamage), Some(2))), - Skill((Hammer(CKnockback), Some(1))), - Skill((Hammer(CDrain), Some(1))), - Skill((Hammer(CSpeed), Some(1))), + Skill((Hammer(CDamage), 2)), + Skill((Hammer(CKnockback), 1)), + Skill((Hammer(CDrain), 1)), + Skill((Hammer(CSpeed), 1)), // Leap - Skill((Hammer(UnlockLeap), None)), - Skill((Hammer(LDamage), Some(1))), - Skill((Hammer(LCost), Some(1))), - Skill((Hammer(LDistance), Some(1))), + Skill((Hammer(UnlockLeap), 1)), + Skill((Hammer(LDamage), 1)), + Skill((Hammer(LCost), 1)), + Skill((Hammer(LDistance), 1)), ]) diff --git a/assets/common/skillset/preset/rank3/sceptre.ron b/assets/common/skillset/preset/rank3/sceptre.ron index 73ad0437b6..08da4ed7d4 100644 --- a/assets/common/skillset/preset/rank3/sceptre.ron +++ b/assets/common/skillset/preset/rank3/sceptre.ron @@ -2,18 +2,18 @@ Group(Weapon(Sceptre)), // Beam - Skill((Sceptre(LDamage), Some(2))), - Skill((Sceptre(LRange), Some(2))), - Skill((Sceptre(LLifesteal), Some(1))), - Skill((Sceptre(LRegen), Some(1))), + Skill((Sceptre(LDamage), 2)), + Skill((Sceptre(LRange), 2)), + Skill((Sceptre(LLifesteal), 1)), + Skill((Sceptre(LRegen), 1)), // Heal - Skill((Sceptre(HHeal), Some(2))), - Skill((Sceptre(HDuration), Some(1))), - Skill((Sceptre(HRange), Some(1))), - Skill((Sceptre(HCost), Some(1))), + Skill((Sceptre(HHeal), 2)), + Skill((Sceptre(HDuration), 1)), + Skill((Sceptre(HRange), 1)), + Skill((Sceptre(HCost), 1)), // Ward - Skill((Sceptre(UnlockAura), None)), - Skill((Sceptre(AStrength), Some(1))), - Skill((Sceptre(ADuration), Some(1))), + Skill((Sceptre(UnlockAura), 1)), + Skill((Sceptre(AStrength), 1)), + Skill((Sceptre(ADuration), 1)), ]) diff --git a/assets/common/skillset/preset/rank3/staff.ron b/assets/common/skillset/preset/rank3/staff.ron index d5e92082fa..4fc73f68c7 100644 --- a/assets/common/skillset/preset/rank3/staff.ron +++ b/assets/common/skillset/preset/rank3/staff.ron @@ -2,14 +2,14 @@ Group(Weapon(Staff)), // Fireball - Skill((Staff(BDamage), Some(2))), - Skill((Staff(BRegen), Some(2))), - Skill((Staff(BRadius), Some(2))), + Skill((Staff(BDamage), 2)), + Skill((Staff(BRegen), 2)), + Skill((Staff(BRadius), 2)), // Flamethrower - Skill((Staff(FRange), Some(2))), - Skill((Staff(FDamage), Some(3))), - Skill((Staff(FDrain), Some(2))), - Skill((Staff(FVelocity), Some(1))), + Skill((Staff(FRange), 2)), + Skill((Staff(FDamage), 3)), + Skill((Staff(FDrain), 2)), + Skill((Staff(FVelocity), 1)), ]) \ No newline at end of file diff --git a/assets/common/skillset/preset/rank3/sword.ron b/assets/common/skillset/preset/rank3/sword.ron index a2cf9a4eac..024dd96e83 100644 --- a/assets/common/skillset/preset/rank3/sword.ron +++ b/assets/common/skillset/preset/rank3/sword.ron @@ -1,23 +1,23 @@ ([ Group(Weapon(Sword)), - Skill((Sword(InterruptingAttacks), None)), + Skill((Sword(InterruptingAttacks), 1)), // TripleStrike - Skill((Sword(TsCombo), None)), - Skill((Sword(TsDamage), Some(2))), - Skill((Sword(TsRegen), Some(2))), - Skill((Sword(TsSpeed), Some(1))), + Skill((Sword(TsCombo), 1)), + Skill((Sword(TsDamage), 2)), + Skill((Sword(TsRegen), 2)), + Skill((Sword(TsSpeed), 1)), // Dash - Skill((Sword(DCost), Some(2))), - Skill((Sword(DDrain), Some(2))), - Skill((Sword(DDamage), Some(1))), - Skill((Sword(DScaling), Some(1))), - Skill((Sword(DSpeed), None)), + Skill((Sword(DCost), 2)), + Skill((Sword(DDrain), 2)), + Skill((Sword(DDamage), 1)), + Skill((Sword(DScaling), 1)), + Skill((Sword(DSpeed), 1)), // Spin of death - Skill((Sword(UnlockSpin), None)), - Skill((Sword(SDamage), Some(1))), - Skill((Sword(SSpeed), Some(1))), + Skill((Sword(UnlockSpin), 1)), + Skill((Sword(SDamage), 1)), + Skill((Sword(SSpeed), 1)), ]) diff --git a/assets/common/skillset/preset/rank4/axe.ron b/assets/common/skillset/preset/rank4/axe.ron index 7d0451fc16..be4e3ce218 100644 --- a/assets/common/skillset/preset/rank4/axe.ron +++ b/assets/common/skillset/preset/rank4/axe.ron @@ -2,22 +2,22 @@ Group(Weapon(Axe)), // DoubleStrike - Skill((Axe(DsCombo), None)), - Skill((Axe(DsDamage), Some(2))), - Skill((Axe(DsRegen), Some(1))), - Skill((Axe(DsSpeed), Some(2))), + Skill((Axe(DsCombo), 1)), + Skill((Axe(DsDamage), 2)), + Skill((Axe(DsRegen), 1)), + Skill((Axe(DsSpeed), 2)), // Spin - Skill((Axe(SInfinite), None)), - Skill((Axe(SHelicopter), None)), - Skill((Axe(SDamage), Some(2))), - Skill((Axe(SSpeed), Some(1))), - Skill((Axe(SCost), Some(1))), + Skill((Axe(SInfinite), 1)), + Skill((Axe(SHelicopter), 1)), + Skill((Axe(SDamage), 2)), + Skill((Axe(SSpeed), 1)), + Skill((Axe(SCost), 1)), // Leap - Skill((Axe(UnlockLeap), None)), - Skill((Axe(LDamage), Some(1))), - Skill((Axe(LKnockback), Some(1))), - Skill((Axe(LCost), Some(1))), - Skill((Axe(LDistance), Some(1))), + Skill((Axe(UnlockLeap), 1)), + Skill((Axe(LDamage), 1)), + Skill((Axe(LKnockback), 1)), + Skill((Axe(LCost), 1)), + Skill((Axe(LDistance), 1)), ]) diff --git a/assets/common/skillset/preset/rank4/bow.ron b/assets/common/skillset/preset/rank4/bow.ron index 04e6187283..6f28afa66d 100644 --- a/assets/common/skillset/preset/rank4/bow.ron +++ b/assets/common/skillset/preset/rank4/bow.ron @@ -2,24 +2,24 @@ Group(Weapon(Bow)), // Projectile Speed - Skill((Bow(ProjSpeed), Some(2))), + Skill((Bow(ProjSpeed), 2)), // Charged - Skill((Bow(CDamage), Some(2))), - Skill((Bow(CKnockback), Some(2))), - Skill((Bow(CSpeed), Some(2))), - Skill((Bow(CRegen), Some(1))), - Skill((Bow(CMove), Some(1))), + Skill((Bow(CDamage), 2)), + Skill((Bow(CKnockback), 2)), + Skill((Bow(CSpeed), 2)), + Skill((Bow(CRegen), 1)), + Skill((Bow(CMove), 1)), // Repeater - Skill((Bow(RDamage), Some(2))), - Skill((Bow(RCost), Some(1))), - Skill((Bow(RSpeed), Some(1))), + Skill((Bow(RDamage), 2)), + Skill((Bow(RCost), 1)), + Skill((Bow(RSpeed), 1)), // Shotgun - Skill((Bow(UnlockShotgun), None)), - Skill((Bow(SDamage), Some(1))), - Skill((Bow(SCost), Some(1))), - Skill((Bow(SArrows), Some(1))), - Skill((Bow(SSpread), Some(1))), + Skill((Bow(UnlockShotgun), 1)), + Skill((Bow(SDamage), 1)), + Skill((Bow(SCost), 1)), + Skill((Bow(SArrows), 1)), + Skill((Bow(SSpread), 1)), ]) diff --git a/assets/common/skillset/preset/rank4/general.ron b/assets/common/skillset/preset/rank4/general.ron index 063b80d8a2..26576543d2 100644 --- a/assets/common/skillset/preset/rank4/general.ron +++ b/assets/common/skillset/preset/rank4/general.ron @@ -1,4 +1,4 @@ ([ - Skill((General(HealthIncrease), Some(8))), - Skill((General(EnergyIncrease), Some(4))), + Skill((General(HealthIncrease), 8)), + Skill((General(EnergyIncrease), 4)), ]) diff --git a/assets/common/skillset/preset/rank4/hammer.ron b/assets/common/skillset/preset/rank4/hammer.ron index ffe3c8594d..58c318c533 100644 --- a/assets/common/skillset/preset/rank4/hammer.ron +++ b/assets/common/skillset/preset/rank4/hammer.ron @@ -2,22 +2,22 @@ Group(Weapon(Hammer)), // Single Strike, as single as you are - Skill((Hammer(SsKnockback), Some(2))), - Skill((Hammer(SsDamage), Some(2))), - Skill((Hammer(SsRegen), Some(2))), - Skill((Hammer(SsSpeed), Some(2))), + Skill((Hammer(SsKnockback), 2)), + Skill((Hammer(SsDamage), 2)), + Skill((Hammer(SsRegen), 2)), + Skill((Hammer(SsSpeed), 2)), // Charged - Skill((Hammer(CDamage), Some(2))), - Skill((Hammer(CKnockback), Some(2))), - Skill((Hammer(CDrain), Some(2))), - Skill((Hammer(CSpeed), Some(2))), + Skill((Hammer(CDamage), 2)), + Skill((Hammer(CKnockback), 2)), + Skill((Hammer(CDrain), 2)), + Skill((Hammer(CSpeed), 2)), // Leap - Skill((Hammer(UnlockLeap), None)), - Skill((Hammer(LDamage), Some(1))), - Skill((Hammer(LCost), Some(1))), - Skill((Hammer(LDistance), Some(1))), - Skill((Hammer(LKnockback), Some(1))), - Skill((Hammer(LRange), Some(1))), + Skill((Hammer(UnlockLeap), 1)), + Skill((Hammer(LDamage), 1)), + Skill((Hammer(LCost), 1)), + Skill((Hammer(LDistance), 1)), + Skill((Hammer(LKnockback), 1)), + Skill((Hammer(LRange), 1)), ]) diff --git a/assets/common/skillset/preset/rank4/sceptre.ron b/assets/common/skillset/preset/rank4/sceptre.ron index b174970953..f4d49c9aa2 100644 --- a/assets/common/skillset/preset/rank4/sceptre.ron +++ b/assets/common/skillset/preset/rank4/sceptre.ron @@ -2,20 +2,20 @@ Group(Weapon(Sceptre)), // Beam - Skill((Sceptre(LDamage), Some(2))), - Skill((Sceptre(LRange), Some(2))), - Skill((Sceptre(LLifesteal), Some(2))), - Skill((Sceptre(LRegen), Some(2))), + Skill((Sceptre(LDamage), 2)), + Skill((Sceptre(LRange), 2)), + Skill((Sceptre(LLifesteal), 2)), + Skill((Sceptre(LRegen), 2)), // Heal - Skill((Sceptre(HHeal), Some(2))), - Skill((Sceptre(HDuration), Some(2))), - Skill((Sceptre(HRange), Some(2))), - Skill((Sceptre(HCost), Some(2))), + Skill((Sceptre(HHeal), 2)), + Skill((Sceptre(HDuration), 2)), + Skill((Sceptre(HRange), 2)), + Skill((Sceptre(HCost), 2)), // Ward - Skill((Sceptre(UnlockAura), None)), - Skill((Sceptre(AStrength), Some(1))), - Skill((Sceptre(ADuration), Some(1))), - Skill((Sceptre(ARange), Some(1))), - Skill((Sceptre(ACost), Some(1))), + Skill((Sceptre(UnlockAura), 1)), + Skill((Sceptre(AStrength), 1)), + Skill((Sceptre(ADuration), 1)), + Skill((Sceptre(ARange), 1)), + Skill((Sceptre(ACost), 1)), ]) diff --git a/assets/common/skillset/preset/rank4/staff.ron b/assets/common/skillset/preset/rank4/staff.ron index 454d0ff6b2..22043fe2a7 100644 --- a/assets/common/skillset/preset/rank4/staff.ron +++ b/assets/common/skillset/preset/rank4/staff.ron @@ -2,20 +2,20 @@ Group(Weapon(Staff)), // Fireball - Skill((Staff(BDamage), Some(1))), - Skill((Staff(BRegen), Some(2))), - Skill((Staff(BRadius), Some(2))), + Skill((Staff(BDamage), 1)), + Skill((Staff(BRegen), 2)), + Skill((Staff(BRadius), 2)), // Flamethrower - Skill((Staff(FRange), Some(2))), - Skill((Staff(FDamage), Some(1))), - Skill((Staff(FDrain), Some(1))), - Skill((Staff(FVelocity), Some(2))), + Skill((Staff(FRange), 2)), + Skill((Staff(FDamage), 1)), + Skill((Staff(FDrain), 1)), + Skill((Staff(FVelocity), 2)), // Shockwave - Skill((Staff(UnlockShockwave), None)), - Skill((Staff(SDamage), Some(1))), - Skill((Staff(SKnockback), Some(1))), - Skill((Staff(SRange), Some(1))), - Skill((Staff(SCost), Some(1))), + Skill((Staff(UnlockShockwave), 1)), + Skill((Staff(SDamage), 1)), + Skill((Staff(SKnockback), 1)), + Skill((Staff(SRange), 1)), + Skill((Staff(SCost), 1)), ]) diff --git a/assets/common/skillset/preset/rank4/sword.ron b/assets/common/skillset/preset/rank4/sword.ron index 8d7ba0abcc..4ae8c8e61c 100644 --- a/assets/common/skillset/preset/rank4/sword.ron +++ b/assets/common/skillset/preset/rank4/sword.ron @@ -1,26 +1,26 @@ ([ Group(Weapon(Sword)), - Skill((Sword(InterruptingAttacks), None)), + Skill((Sword(InterruptingAttacks), 1)), // TripleStrike - Skill((Sword(TsCombo), None)), - Skill((Sword(TsDamage), Some(2))), - Skill((Sword(TsRegen), Some(2))), - Skill((Sword(TsSpeed), Some(3))), + Skill((Sword(TsCombo), 1)), + Skill((Sword(TsDamage), 2)), + Skill((Sword(TsRegen), 2)), + Skill((Sword(TsSpeed), 3)), // Dash - Skill((Sword(DCost), Some(2))), - Skill((Sword(DDrain), Some(2))), - Skill((Sword(DDamage), Some(2))), - Skill((Sword(DScaling), Some(2))), - Skill((Sword(DSpeed), None)), - Skill((Sword(DInfinite), None)), + Skill((Sword(DCost), 2)), + Skill((Sword(DDrain), 2)), + Skill((Sword(DDamage), 2)), + Skill((Sword(DScaling), 2)), + Skill((Sword(DSpeed), 1)), + Skill((Sword(DChargeThrough), 1)), // Spin of death - Skill((Sword(UnlockSpin), None)), - Skill((Sword(SDamage), Some(1))), - Skill((Sword(SSpeed), Some(1))), - Skill((Sword(SSpins), Some(1))), - Skill((Sword(SCost), Some(1))), + Skill((Sword(UnlockSpin), 1)), + Skill((Sword(SDamage), 1)), + Skill((Sword(SSpeed), 1)), + Skill((Sword(SSpins), 1)), + Skill((Sword(SCost), 1)), ]) diff --git a/assets/common/skillset/preset/rank5/axe.ron b/assets/common/skillset/preset/rank5/axe.ron index 1ad1261f42..8808d7efdb 100644 --- a/assets/common/skillset/preset/rank5/axe.ron +++ b/assets/common/skillset/preset/rank5/axe.ron @@ -2,22 +2,22 @@ Group(Weapon(Axe)), // DoubleStrike - Skill((Axe(DsCombo), None)), - Skill((Axe(DsDamage), Some(3))), - Skill((Axe(DsRegen), Some(2))), - Skill((Axe(DsSpeed), Some(3))), + Skill((Axe(DsCombo), 1)), + Skill((Axe(DsDamage), 3)), + Skill((Axe(DsRegen), 2)), + Skill((Axe(DsSpeed), 3)), // Spin - Skill((Axe(SInfinite), None)), - Skill((Axe(SHelicopter), None)), - Skill((Axe(SDamage), Some(3))), - Skill((Axe(SSpeed), Some(2))), - Skill((Axe(SCost), Some(2))), + Skill((Axe(SInfinite), 1)), + Skill((Axe(SHelicopter), 1)), + Skill((Axe(SDamage), 3)), + Skill((Axe(SSpeed), 2)), + Skill((Axe(SCost), 2)), // Leap - Skill((Axe(UnlockLeap), None)), - Skill((Axe(LDamage), Some(2))), - Skill((Axe(LKnockback), Some(2))), - Skill((Axe(LCost), Some(2))), - Skill((Axe(LDistance), Some(2))), + Skill((Axe(UnlockLeap), 1)), + Skill((Axe(LDamage), 2)), + Skill((Axe(LKnockback), 2)), + Skill((Axe(LCost), 2)), + Skill((Axe(LDistance), 2)), ]) diff --git a/assets/common/skillset/preset/rank5/bow.ron b/assets/common/skillset/preset/rank5/bow.ron index cef119d0ec..38dd7176b4 100644 --- a/assets/common/skillset/preset/rank5/bow.ron +++ b/assets/common/skillset/preset/rank5/bow.ron @@ -2,24 +2,24 @@ Group(Weapon(Bow)), // Projectile Speed - Skill((Bow(ProjSpeed), Some(2))), + Skill((Bow(ProjSpeed), 2)), // Charged - Skill((Bow(CDamage), Some(3))), - Skill((Bow(CKnockback), Some(2))), - Skill((Bow(CSpeed), Some(2))), - Skill((Bow(CRegen), Some(2))), - Skill((Bow(CMove), Some(2))), + Skill((Bow(CDamage), 3)), + Skill((Bow(CKnockback), 2)), + Skill((Bow(CSpeed), 2)), + Skill((Bow(CRegen), 2)), + Skill((Bow(CMove), 2)), // Repeater - Skill((Bow(RDamage), Some(3))), - Skill((Bow(RCost), Some(2))), - Skill((Bow(RSpeed), Some(2))), + Skill((Bow(RDamage), 3)), + Skill((Bow(RCost), 2)), + Skill((Bow(RSpeed), 2)), // Shotgun - Skill((Bow(UnlockShotgun), None)), - Skill((Bow(SDamage), Some(2))), - Skill((Bow(SCost), Some(2))), - Skill((Bow(SArrows), Some(2))), - Skill((Bow(SSpread), Some(2))), + Skill((Bow(UnlockShotgun), 1)), + Skill((Bow(SDamage), 2)), + Skill((Bow(SCost), 2)), + Skill((Bow(SArrows), 2)), + Skill((Bow(SSpread), 2)), ]) diff --git a/assets/common/skillset/preset/rank5/general.ron b/assets/common/skillset/preset/rank5/general.ron index be6cbdf4aa..e6fecc7582 100644 --- a/assets/common/skillset/preset/rank5/general.ron +++ b/assets/common/skillset/preset/rank5/general.ron @@ -1,4 +1,4 @@ ([ - Skill((General(HealthIncrease), Some(10))), - Skill((General(EnergyIncrease), Some(5))), + Skill((General(HealthIncrease), 10)), + Skill((General(EnergyIncrease), 5)), ]) diff --git a/assets/common/skillset/preset/rank5/hammer.ron b/assets/common/skillset/preset/rank5/hammer.ron index 6de9d2bb50..4c60c85beb 100644 --- a/assets/common/skillset/preset/rank5/hammer.ron +++ b/assets/common/skillset/preset/rank5/hammer.ron @@ -2,22 +2,22 @@ Group(Weapon(Hammer)), // Single Strike, as single as you are - Skill((Hammer(SsKnockback), Some(2))), - Skill((Hammer(SsDamage), Some(3))), - Skill((Hammer(SsRegen), Some(2))), - Skill((Hammer(SsSpeed), Some(3))), + Skill((Hammer(SsKnockback), 2)), + Skill((Hammer(SsDamage), 3)), + Skill((Hammer(SsRegen), 2)), + Skill((Hammer(SsSpeed), 3)), // Charged - Skill((Hammer(CDamage), Some(3))), - Skill((Hammer(CKnockback), Some(3))), - Skill((Hammer(CDrain), Some(2))), - Skill((Hammer(CSpeed), Some(2))), + Skill((Hammer(CDamage), 3)), + Skill((Hammer(CKnockback), 3)), + Skill((Hammer(CDrain), 2)), + Skill((Hammer(CSpeed), 2)), // Leap - Skill((Hammer(UnlockLeap), None)), - Skill((Hammer(LDamage), Some(2))), - Skill((Hammer(LCost), Some(2))), - Skill((Hammer(LDistance), Some(2))), - Skill((Hammer(LKnockback), Some(2))), - Skill((Hammer(LRange), Some(2))), + Skill((Hammer(UnlockLeap), 1)), + Skill((Hammer(LDamage), 2)), + Skill((Hammer(LCost), 2)), + Skill((Hammer(LDistance), 2)), + Skill((Hammer(LKnockback), 2)), + Skill((Hammer(LRange), 2)), ]) diff --git a/assets/common/skillset/preset/rank5/sceptre.ron b/assets/common/skillset/preset/rank5/sceptre.ron index 39fe5f497f..3e32098ff6 100644 --- a/assets/common/skillset/preset/rank5/sceptre.ron +++ b/assets/common/skillset/preset/rank5/sceptre.ron @@ -2,20 +2,20 @@ Group(Weapon(Sceptre)), // Beam - Skill((Sceptre(LDamage), Some(3))), - Skill((Sceptre(LRange), Some(2))), - Skill((Sceptre(LLifesteal), Some(3))), - Skill((Sceptre(LRegen), Some(2))), + Skill((Sceptre(LDamage), 3)), + Skill((Sceptre(LRange), 2)), + Skill((Sceptre(LLifesteal), 3)), + Skill((Sceptre(LRegen), 2)), // Heal - Skill((Sceptre(HHeal), Some(3))), - Skill((Sceptre(HDuration), Some(2))), - Skill((Sceptre(HRange), Some(2))), - Skill((Sceptre(HCost), Some(2))), + Skill((Sceptre(HHeal), 3)), + Skill((Sceptre(HDuration), 2)), + Skill((Sceptre(HRange), 2)), + Skill((Sceptre(HCost), 2)), // Ward - Skill((Sceptre(UnlockAura), None)), - Skill((Sceptre(AStrength), Some(2))), - Skill((Sceptre(ADuration), Some(2))), - Skill((Sceptre(ARange), Some(2))), - Skill((Sceptre(ACost), Some(2))), + Skill((Sceptre(UnlockAura), 1)), + Skill((Sceptre(AStrength), 2)), + Skill((Sceptre(ADuration), 2)), + Skill((Sceptre(ARange), 2)), + Skill((Sceptre(ACost), 2)), ]) diff --git a/assets/common/skillset/preset/rank5/staff.ron b/assets/common/skillset/preset/rank5/staff.ron index d2511166dc..b9feb1238d 100644 --- a/assets/common/skillset/preset/rank5/staff.ron +++ b/assets/common/skillset/preset/rank5/staff.ron @@ -2,20 +2,20 @@ Group(Weapon(Staff)), // Fireball - Skill((Staff(BDamage), Some(3))), - Skill((Staff(BRegen), Some(2))), - Skill((Staff(BRadius), Some(3))), + Skill((Staff(BDamage), 3)), + Skill((Staff(BRegen), 2)), + Skill((Staff(BRadius), 3)), // Flamethrower - Skill((Staff(FRange), Some(2))), - Skill((Staff(FDamage), Some(3))), - Skill((Staff(FDrain), Some(2))), - Skill((Staff(FVelocity), Some(2))), + Skill((Staff(FRange), 2)), + Skill((Staff(FDamage), 3)), + Skill((Staff(FDrain), 2)), + Skill((Staff(FVelocity), 2)), // Shockwave - Skill((Staff(UnlockShockwave), None)), - Skill((Staff(SDamage), Some(2))), - Skill((Staff(SKnockback), Some(2))), - Skill((Staff(SRange), Some(2))), - Skill((Staff(SCost), Some(2))), + Skill((Staff(UnlockShockwave), 1)), + Skill((Staff(SDamage), 2)), + Skill((Staff(SKnockback), 2)), + Skill((Staff(SRange), 2)), + Skill((Staff(SCost), 2)), ]) diff --git a/assets/common/skillset/preset/rank5/sword.ron b/assets/common/skillset/preset/rank5/sword.ron index 8781f8ebae..225c7616b3 100644 --- a/assets/common/skillset/preset/rank5/sword.ron +++ b/assets/common/skillset/preset/rank5/sword.ron @@ -1,26 +1,26 @@ ([ Group(Weapon(Sword)), - Skill((Sword(InterruptingAttacks), None)), + Skill((Sword(InterruptingAttacks), 1)), // TripleStrike - Skill((Sword(TsCombo), None)), - Skill((Sword(TsDamage), Some(3))), - Skill((Sword(TsRegen), Some(2))), - Skill((Sword(TsSpeed), Some(3))), + Skill((Sword(TsCombo), 1)), + Skill((Sword(TsDamage), 3)), + Skill((Sword(TsRegen), 2)), + Skill((Sword(TsSpeed), 3)), // Dash - Skill((Sword(DCost), Some(2))), - Skill((Sword(DDrain), Some(2))), - Skill((Sword(DDamage), Some(2))), - Skill((Sword(DScaling), Some(3))), - Skill((Sword(DSpeed), None)), - Skill((Sword(DInfinite), None)), + Skill((Sword(DCost), 2)), + Skill((Sword(DDrain), 2)), + Skill((Sword(DDamage), 2)), + Skill((Sword(DScaling), 3)), + Skill((Sword(DSpeed), 1)), + Skill((Sword(DChargeThrough), 1)), // Spin of death - Skill((Sword(UnlockSpin), None)), - Skill((Sword(SDamage), Some(2))), - Skill((Sword(SSpeed), Some(2))), - Skill((Sword(SSpins), Some(2))), - Skill((Sword(SCost), Some(2))), + Skill((Sword(UnlockSpin), 1)), + Skill((Sword(SDamage), 2)), + Skill((Sword(SSpeed), 2)), + Skill((Sword(SSpins), 2)), + Skill((Sword(SCost), 2)), ]) diff --git a/assets/server/manifests/presets.ron b/assets/server/manifests/presets.ron index c29c3b5b3f..86a74f1db6 100644 --- a/assets/server/manifests/presets.ron +++ b/assets/server/manifests/presets.ron @@ -27,7 +27,7 @@ (Sword(DDamage), 2), (Sword(DScaling), 3), (Sword(DSpeed), 1), - (Sword(DInfinite), 1), + (Sword(DChargeThrough), 1), (Sword(UnlockSpin), 1), (Sword(SDamage), 2), @@ -157,7 +157,7 @@ (Sword(DDamage), 2), (Sword(DScaling), 2), (Sword(DSpeed), 1), - (Sword(DInfinite), 1), + (Sword(DChargeThrough), 1), (Sword(UnlockSpin), 1), (Sword(SDamage), 1), diff --git a/client/src/lib.rs b/client/src/lib.rs index 2bbdf82010..41f364b938 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -753,11 +753,11 @@ impl Client { | ClientGeneral::ExitInGame | ClientGeneral::PlayerPhysics { .. } | ClientGeneral::UnlockSkill(_) - | ClientGeneral::RefundSkill(_) | ClientGeneral::RequestSiteInfo(_) | ClientGeneral::UnlockSkillGroup(_) | ClientGeneral::RequestPlayerPhysics { .. } - | ClientGeneral::RequestLossyTerrainCompression { .. } => { + | ClientGeneral::RequestLossyTerrainCompression { .. } + | ClientGeneral::AcknowledgePersistenceLoadError => { #[cfg(feature = "tracy")] { ingame = 1.0; @@ -1410,6 +1410,10 @@ impl Client { })) } + pub fn acknolwedge_persistence_load_error(&mut self) { + self.send_msg(ClientGeneral::AcknowledgePersistenceLoadError) + } + /// Execute a single client tick, handle input and update the game state by /// the given duration. pub fn tick( diff --git a/common/Cargo.toml b/common/Cargo.toml index 8e5977d4bf..095d169e7f 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -29,6 +29,8 @@ enum-iterator = "0.7" vek = { version = "=0.14.1", features = ["serde"] } chrono = "0.4" chrono-tz = "0.6" +sha2 = "0.9.8" +serde_json = "1.0.50" # Strum strum = { version = "0.23", features = ["derive"] } diff --git a/common/net/src/msg/client.rs b/common/net/src/msg/client.rs index a2082fb4d3..a4030044d3 100644 --- a/common/net/src/msg/client.rs +++ b/common/net/src/msg/client.rs @@ -76,7 +76,6 @@ pub enum ClientGeneral { ori: comp::Ori, }, UnlockSkill(Skill), - RefundSkill(Skill), UnlockSkillGroup(SkillGroupKind), RequestSiteInfo(SiteId), //Only in Game, via terrain stream @@ -93,6 +92,7 @@ pub enum ClientGeneral { RequestLossyTerrainCompression { lossy_terrain_compression: bool, }, + AcknowledgePersistenceLoadError, } impl ClientMsg { @@ -128,11 +128,11 @@ impl ClientMsg { | ClientGeneral::PlayerPhysics { .. } | ClientGeneral::TerrainChunkRequest { .. } | ClientGeneral::UnlockSkill(_) - | ClientGeneral::RefundSkill(_) | ClientGeneral::RequestSiteInfo(_) | ClientGeneral::UnlockSkillGroup(_) | ClientGeneral::RequestPlayerPhysics { .. } - | ClientGeneral::RequestLossyTerrainCompression { .. } => { + | ClientGeneral::RequestLossyTerrainCompression { .. } + | ClientGeneral::AcknowledgePersistenceLoadError => { c_type == ClientType::Game && presence.is_some() }, //Always possible diff --git a/common/src/combat.rs b/common/src/combat.rs index ba4493e80b..51a12c133b 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -10,7 +10,7 @@ use crate::{ }, slot::EquipSlot, }, - skills::SkillGroupKind, + skillset::SkillGroupKind, Alignment, Body, CharacterState, Combo, Energy, Health, HealthChange, Inventory, Ori, Player, Poise, SkillSet, Stats, }, diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index ae39fc25f1..1c5daf23d9 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -12,7 +12,10 @@ use crate::{ Inventory, }, projectile::ProjectileConstructor, - skills::{self, Skill, SkillSet, SKILL_MODIFIERS}, + skillset::{ + skills::{self, Skill, SKILL_MODIFIERS}, + SkillSet, + }, Body, CharacterState, LightEmitter, StateUpdate, }, states::{ @@ -1158,11 +1161,7 @@ impl CharacterAbility { #[must_use = "method returns new ability and doesn't mutate the original value"] #[warn(clippy::pedantic)] - pub fn adjusted_by_skills( - mut self, - skillset: &skills::SkillSet, - tool: Option, - ) -> Self { + pub fn adjusted_by_skills(mut self, skillset: &SkillSet, tool: Option) -> Self { match tool { Some(ToolKind::Sword) => self.adjusted_by_sword_skills(skillset), Some(ToolKind::Axe) => self.adjusted_by_axe_skills(skillset), @@ -1178,7 +1177,7 @@ impl CharacterAbility { } #[warn(clippy::pedantic)] - fn adjusted_by_mining_skills(&mut self, skillset: &skills::SkillSet) { + fn adjusted_by_mining_skills(&mut self, skillset: &SkillSet) { use skills::MiningSkill::Speed; if let CharacterAbility::BasicMelee { @@ -1188,7 +1187,7 @@ impl CharacterAbility { .. } = self { - if let Ok(Some(level)) = skillset.skill_level(Skill::Pick(Speed)) { + if let Ok(level) = skillset.skill_level(Skill::Pick(Speed)) { let modifiers = SKILL_MODIFIERS.mining_tree; let speed = modifiers.speed.powi(level.into()); @@ -1200,7 +1199,7 @@ impl CharacterAbility { } #[warn(clippy::pedantic)] - fn adjusted_by_general_skills(&mut self, skillset: &skills::SkillSet) { + fn adjusted_by_general_skills(&mut self, skillset: &SkillSet) { if let CharacterAbility::Roll { ref mut energy_cost, ref mut roll_strength, @@ -1212,20 +1211,20 @@ impl CharacterAbility { let modifiers = SKILL_MODIFIERS.general_tree.roll; - if let Ok(Some(level)) = skillset.skill_level(Skill::Roll(Cost)) { + if let Ok(level) = skillset.skill_level(Skill::Roll(Cost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Skill::Roll(Strength)) { + if let Ok(level) = skillset.skill_level(Skill::Roll(Strength)) { *roll_strength *= modifiers.strength.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Skill::Roll(Duration)) { + if let Ok(level) = skillset.skill_level(Skill::Roll(Duration)) { *movement_duration *= modifiers.duration.powi(level.into()); } } } #[warn(clippy::pedantic)] - fn adjusted_by_sword_skills(&mut self, skillset: &skills::SkillSet) { + fn adjusted_by_sword_skills(&mut self, skillset: &SkillSet) { #![allow(clippy::enum_glob_use)] use skills::{Skill::Sword, SwordSkill::*}; @@ -1242,10 +1241,8 @@ impl CharacterAbility { *is_interruptible = skillset.has_skill(Sword(InterruptingAttacks)); if skillset.has_skill(Sword(TsCombo)) { - let speed_segments = Sword(TsSpeed) - .max_level() - .map_or(1.0, |l| f32::from(l) + 1.0); - let speed_level = f32::from(skillset.skill_level_or(Sword(TsSpeed), 0)); + let speed_segments = f32::from(Sword(TsSpeed).max_level()) + 1.0; + let speed_level = f32::from(skillset.skill_level(Sword(TsSpeed)).unwrap_or(0)); *speed_increase = (speed_level + 1.0) / speed_segments; *max_speed_increase = (speed_level + 1.0) / speed_segments; } else { @@ -1253,15 +1250,15 @@ impl CharacterAbility { *max_speed_increase = 0.0; } - let energy_level = skillset.skill_level_or(Sword(TsRegen), 0); + let energy_level = skillset.skill_level(Sword(TsRegen)).unwrap_or(0); let stages = u16::try_from(stage_data.len()) .expect("number of stages can't be more than u16"); *max_energy_gain *= f32::from((energy_level + 1) * stages - 1) * f32::from(stages - 1) - / f32::from(Sword(TsRegen).max_level().unwrap() + 1); - *scales_from_combo = skillset.skill_level_or(Sword(TsDamage), 0).into(); + / f32::from(Sword(TsRegen).max_level() + 1); + *scales_from_combo = skillset.skill_level(Sword(TsDamage)).unwrap_or(0).into(); }, CharacterAbility::DashMelee { ref mut is_interruptible, @@ -1275,22 +1272,22 @@ impl CharacterAbility { } => { let modifiers = SKILL_MODIFIERS.sword_tree.dash; *is_interruptible = skillset.has_skill(Sword(InterruptingAttacks)); - if let Ok(Some(level)) = skillset.skill_level(Sword(DCost)) { + if let Ok(level) = skillset.skill_level(Sword(DCost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Sword(DDrain)) { + if let Ok(level) = skillset.skill_level(Sword(DDrain)) { *energy_drain *= modifiers.energy_drain.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Sword(DDamage)) { + if let Ok(level) = skillset.skill_level(Sword(DDamage)) { *base_damage *= modifiers.base_damage.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Sword(DScaling)) { + if let Ok(level) = skillset.skill_level(Sword(DScaling)) { *scaled_damage *= modifiers.scaled_damage.powi(level.into()); } if skillset.has_skill(Sword(DSpeed)) { *forward_speed *= modifiers.forward_speed; } - *charge_through = skillset.has_skill(Sword(DInfinite)); + *charge_through = skillset.has_skill(Sword(DChargeThrough)); }, CharacterAbility::SpinMelee { ref mut is_interruptible, @@ -1302,16 +1299,16 @@ impl CharacterAbility { } => { let modifiers = SKILL_MODIFIERS.sword_tree.spin; *is_interruptible = skillset.has_skill(Sword(InterruptingAttacks)); - if let Ok(Some(level)) = skillset.skill_level(Sword(SDamage)) { + if let Ok(level) = skillset.skill_level(Sword(SDamage)) { *base_damage *= modifiers.base_damage.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Sword(SSpeed)) { + if let Ok(level) = skillset.skill_level(Sword(SSpeed)) { *swing_duration *= modifiers.swing_duration.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Sword(SCost)) { + if let Ok(level) = skillset.skill_level(Sword(SCost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } - let spin_level = skillset.skill_level_or(Sword(SSpins), 0); + let spin_level = skillset.skill_level(Sword(SSpins)).unwrap_or(0); *num_spins = u32::from(spin_level) * modifiers.num + 1; }, _ => {}, @@ -1319,7 +1316,7 @@ impl CharacterAbility { } #[warn(clippy::pedantic)] - fn adjusted_by_axe_skills(&mut self, skillset: &skills::SkillSet) { + fn adjusted_by_axe_skills(&mut self, skillset: &SkillSet) { #![allow(clippy::enum_glob_use)] use skills::{AxeSkill::*, Skill::Axe}; @@ -1335,20 +1332,20 @@ impl CharacterAbility { if !skillset.has_skill(Axe(DsCombo)) { stage_data.pop(); } - let speed_segments = f32::from(Axe(DsSpeed).max_level().unwrap_or(1)); - let speed_level = f32::from(skillset.skill_level_or(Axe(DsSpeed), 0)); + let speed_segments = f32::from(Axe(DsSpeed).max_level()); + let speed_level = f32::from(skillset.skill_level(Axe(DsSpeed)).unwrap_or(0)); *speed_increase *= speed_level / speed_segments; *max_speed_increase *= speed_level / speed_segments; - let energy_level = skillset.skill_level_or(Axe(DsRegen), 0); + let energy_level = skillset.skill_level(Axe(DsRegen)).unwrap_or(0); let stages = u16::try_from(stage_data.len()) .expect("number of stages can't be more than u16"); *max_energy_gain *= f32::from((energy_level + 1) * stages - 1).max(1.0) * f32::from(stages - 1).max(1.0) - / f32::from(Axe(DsRegen).max_level().unwrap() + 1); - *scales_from_combo = skillset.skill_level_or(Axe(DsDamage), 0).into(); + / f32::from(Axe(DsRegen).max_level() + 1); + *scales_from_combo = skillset.skill_level(Axe(DsDamage)).unwrap_or(0).into(); }, CharacterAbility::SpinMelee { ref mut base_damage, @@ -1366,13 +1363,13 @@ impl CharacterAbility { } else { spin_melee::MovementBehavior::ForwardGround }; - if let Ok(Some(level)) = skillset.skill_level(Axe(SDamage)) { + if let Ok(level) = skillset.skill_level(Axe(SDamage)) { *base_damage *= modifiers.base_damage.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Axe(SSpeed)) { + if let Ok(level) = skillset.skill_level(Axe(SSpeed)) { *swing_duration *= modifiers.swing_duration.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Axe(SCost)) { + if let Ok(level) = skillset.skill_level(Axe(SCost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } }, @@ -1385,16 +1382,16 @@ impl CharacterAbility { .. } => { let modifiers = SKILL_MODIFIERS.axe_tree.leap; - if let Ok(Some(level)) = skillset.skill_level(Axe(LDamage)) { + if let Ok(level) = skillset.skill_level(Axe(LDamage)) { *base_damage *= modifiers.base_damage.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Axe(LKnockback)) { + if let Ok(level) = skillset.skill_level(Axe(LKnockback)) { *knockback *= modifiers.knockback.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Axe(LCost)) { + if let Ok(level) = skillset.skill_level(Axe(LCost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Axe(LDistance)) { + if let Ok(level) = skillset.skill_level(Axe(LDistance)) { let strength = modifiers.leap_strength; *forward_leap_strength *= strength.powi(level.into()); *vertical_leap_strength *= strength.powi(level.into()); @@ -1405,7 +1402,7 @@ impl CharacterAbility { } #[warn(clippy::pedantic)] - fn adjusted_by_hammer_skills(&mut self, skillset: &skills::SkillSet) { + fn adjusted_by_hammer_skills(&mut self, skillset: &SkillSet) { #![allow(clippy::enum_glob_use)] use skills::{HammerSkill::*, Skill::Hammer}; @@ -1420,26 +1417,26 @@ impl CharacterAbility { } => { let modifiers = SKILL_MODIFIERS.hammer_tree.single_strike; - if let Ok(Some(level)) = skillset.skill_level(Hammer(SsKnockback)) { + if let Ok(level) = skillset.skill_level(Hammer(SsKnockback)) { *stage_data = (*stage_data) .iter() .map(|s| s.modify_strike(modifiers.knockback.powi(level.into()))) .collect::>(); } - let speed_segments = f32::from(Hammer(SsSpeed).max_level().unwrap_or(1)); - let speed_level = f32::from(skillset.skill_level_or(Hammer(SsSpeed), 0)); + let speed_segments = f32::from(Hammer(SsSpeed).max_level()); + let speed_level = f32::from(skillset.skill_level(Hammer(SsSpeed)).unwrap_or(0)); *speed_increase *= speed_level / speed_segments; *max_speed_increase *= speed_level / speed_segments; - let energy_level = skillset.skill_level_or(Hammer(SsRegen), 0); + let energy_level = skillset.skill_level(Hammer(SsRegen)).unwrap_or(0); let stages = u16::try_from(stage_data.len()) .expect("number of stages can't be more than u16"); *max_energy_gain *= f32::from((energy_level + 1) * stages) - / f32::from((Hammer(SsRegen).max_level().unwrap() + 1) * stages); + / f32::from((Hammer(SsRegen).max_level() + 1) * stages); - *scales_from_combo = skillset.skill_level_or(Hammer(SsDamage), 0).into(); + *scales_from_combo = skillset.skill_level(Hammer(SsDamage)).unwrap_or(0).into(); }, CharacterAbility::ChargedMelee { ref mut scaled_damage, @@ -1450,16 +1447,16 @@ impl CharacterAbility { } => { let modifiers = SKILL_MODIFIERS.hammer_tree.charged; - if let Ok(Some(level)) = skillset.skill_level(Hammer(CDamage)) { + if let Ok(level) = skillset.skill_level(Hammer(CDamage)) { *scaled_damage *= modifiers.scaled_damage.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Hammer(CKnockback)) { + if let Ok(level) = skillset.skill_level(Hammer(CKnockback)) { *scaled_knockback *= modifiers.scaled_knockback.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Hammer(CDrain)) { + if let Ok(level) = skillset.skill_level(Hammer(CDrain)) { *energy_drain *= modifiers.energy_drain.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Hammer(CSpeed)) { + if let Ok(level) = skillset.skill_level(Hammer(CSpeed)) { let charge_time = 1.0 / modifiers.charge_rate; *charge_duration *= charge_time.powi(level.into()); } @@ -1474,21 +1471,21 @@ impl CharacterAbility { .. } => { let modifiers = SKILL_MODIFIERS.hammer_tree.leap; - if let Ok(Some(level)) = skillset.skill_level(Hammer(LDamage)) { + if let Ok(level) = skillset.skill_level(Hammer(LDamage)) { *base_damage *= modifiers.base_damage.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Hammer(LKnockback)) { + if let Ok(level) = skillset.skill_level(Hammer(LKnockback)) { *knockback *= modifiers.knockback.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Hammer(LCost)) { + if let Ok(level) = skillset.skill_level(Hammer(LCost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Hammer(LDistance)) { + if let Ok(level) = skillset.skill_level(Hammer(LDistance)) { let strength = modifiers.leap_strength; *forward_leap_strength *= strength.powi(level.into()); *vertical_leap_strength *= strength.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Hammer(LRange)) { + if let Ok(level) = skillset.skill_level(Hammer(LRange)) { *range += modifiers.range * f32::from(level); } }, @@ -1497,7 +1494,7 @@ impl CharacterAbility { } #[warn(clippy::pedantic)] - fn adjusted_by_bow_skills(&mut self, skillset: &skills::SkillSet) { + fn adjusted_by_bow_skills(&mut self, skillset: &SkillSet) { #![allow(clippy::enum_glob_use)] use skills::{BowSkill::*, Skill::Bow}; @@ -1517,31 +1514,31 @@ impl CharacterAbility { .. } => { let modifiers = SKILL_MODIFIERS.bow_tree.charged; - if let Ok(Some(level)) = skillset.skill_level(Bow(ProjSpeed)) { + if let Ok(level) = skillset.skill_level(Bow(ProjSpeed)) { let projectile_speed_scaling = projectile_speed_modifier.powi(level.into()); *initial_projectile_speed *= projectile_speed_scaling; *scaled_projectile_speed *= projectile_speed_scaling; } - if let Ok(Some(level)) = skillset.skill_level(Bow(CDamage)) { + if let Ok(level) = skillset.skill_level(Bow(CDamage)) { let damage_scaling = modifiers.damage_scaling.powi(level.into()); *initial_damage *= damage_scaling; *scaled_damage *= damage_scaling; } - if let Ok(Some(level)) = skillset.skill_level(Bow(CRegen)) { + if let Ok(level) = skillset.skill_level(Bow(CRegen)) { let regen_scaling = modifiers.regen_scaling.powi(level.into()); *initial_regen *= regen_scaling; *scaled_regen *= regen_scaling; } - if let Ok(Some(level)) = skillset.skill_level(Bow(CKnockback)) { + if let Ok(level) = skillset.skill_level(Bow(CKnockback)) { let knockback_scaling = modifiers.knockback_scaling.powi(level.into()); *initial_knockback *= knockback_scaling; *scaled_knockback *= knockback_scaling; } - if let Ok(Some(level)) = skillset.skill_level(Bow(CSpeed)) { + if let Ok(level) = skillset.skill_level(Bow(CSpeed)) { let charge_time = 1.0 / modifiers.charge_rate; *charge_duration *= charge_time.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Bow(CMove)) { + if let Ok(level) = skillset.skill_level(Bow(CMove)) { *move_speed *= modifiers.move_speed.powi(level.into()); } }, @@ -1553,17 +1550,17 @@ impl CharacterAbility { .. } => { let modifiers = SKILL_MODIFIERS.bow_tree.repeater; - if let Ok(Some(level)) = skillset.skill_level(Bow(ProjSpeed)) { + if let Ok(level) = skillset.skill_level(Bow(ProjSpeed)) { *projectile_speed *= projectile_speed_modifier.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Bow(RDamage)) { + if let Ok(level) = skillset.skill_level(Bow(RDamage)) { let power = modifiers.power.powi(level.into()); *projectile = projectile.modified_projectile(power, 1_f32, 1_f32); } - if let Ok(Some(level)) = skillset.skill_level(Bow(RCost)) { + if let Ok(level) = skillset.skill_level(Bow(RCost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Bow(RSpeed)) { + if let Ok(level) = skillset.skill_level(Bow(RSpeed)) { *max_speed *= modifiers.max_speed.powi(level.into()); } }, @@ -1576,20 +1573,20 @@ impl CharacterAbility { .. } => { let modifiers = SKILL_MODIFIERS.bow_tree.shotgun; - if let Ok(Some(level)) = skillset.skill_level(Bow(ProjSpeed)) { + if let Ok(level) = skillset.skill_level(Bow(ProjSpeed)) { *projectile_speed *= projectile_speed_modifier.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Bow(SDamage)) { + if let Ok(level) = skillset.skill_level(Bow(SDamage)) { let power = modifiers.power.powi(level.into()); *projectile = projectile.modified_projectile(power, 1_f32, 1_f32); } - if let Ok(Some(level)) = skillset.skill_level(Bow(SCost)) { + if let Ok(level) = skillset.skill_level(Bow(SCost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Bow(SArrows)) { + if let Ok(level) = skillset.skill_level(Bow(SArrows)) { *num_projectiles += u32::from(level) * modifiers.num_projectiles; } - if let Ok(Some(level)) = skillset.skill_level(Bow(SSpread)) { + if let Ok(level) = skillset.skill_level(Bow(SSpread)) { *projectile_spread *= modifiers.spread.powi(level.into()); } }, @@ -1598,7 +1595,7 @@ impl CharacterAbility { } #[warn(clippy::pedantic)] - fn adjusted_by_staff_skills(&mut self, skillset: &skills::SkillSet) { + fn adjusted_by_staff_skills(&mut self, skillset: &SkillSet) { #![allow(clippy::enum_glob_use)] use skills::{Skill::Staff, StaffSkill::*}; @@ -1607,9 +1604,9 @@ impl CharacterAbility { ref mut projectile, .. } => { let modifiers = SKILL_MODIFIERS.staff_tree.fireball; - let damage_level = skillset.skill_level_or(Staff(BDamage), 0); - let regen_level = skillset.skill_level_or(Staff(BRegen), 0); - let range_level = skillset.skill_level_or(Staff(BRadius), 0); + let damage_level = skillset.skill_level(Staff(BDamage)).unwrap_or(0); + let regen_level = skillset.skill_level(Staff(BRegen)).unwrap_or(0); + let range_level = skillset.skill_level(Staff(BRadius)).unwrap_or(0); let power = modifiers.power.powi(damage_level.into()); let regen = modifiers.regen.powi(regen_level.into()); let range = modifiers.range.powi(range_level.into()); @@ -1623,19 +1620,19 @@ impl CharacterAbility { .. } => { let modifiers = SKILL_MODIFIERS.staff_tree.flamethrower; - if let Ok(Some(level)) = skillset.skill_level(Staff(FDamage)) { + if let Ok(level) = skillset.skill_level(Staff(FDamage)) { *damage *= modifiers.damage.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Staff(FRange)) { + if let Ok(level) = skillset.skill_level(Staff(FRange)) { let range_mod = modifiers.range.powi(level.into()); *range *= range_mod; // Duration modified to keep velocity constant *beam_duration *= range_mod; } - if let Ok(Some(level)) = skillset.skill_level(Staff(FDrain)) { + if let Ok(level) = skillset.skill_level(Staff(FDrain)) { *energy_drain *= modifiers.energy_drain.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Staff(FVelocity)) { + if let Ok(level) = skillset.skill_level(Staff(FVelocity)) { let velocity_increase = modifiers.velocity.powi(level.into()); let duration_mod = 1.0 / (1.0 + velocity_increase); *beam_duration *= duration_mod; @@ -1649,17 +1646,17 @@ impl CharacterAbility { .. } => { let modifiers = SKILL_MODIFIERS.staff_tree.shockwave; - if let Ok(Some(level)) = skillset.skill_level(Staff(SDamage)) { + if let Ok(level) = skillset.skill_level(Staff(SDamage)) { *damage *= modifiers.damage.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Staff(SKnockback)) { + if let Ok(level) = skillset.skill_level(Staff(SKnockback)) { let knockback_mod = modifiers.knockback.powi(level.into()); *knockback = knockback.modify_strength(knockback_mod); } - if let Ok(Some(level)) = skillset.skill_level(Staff(SRange)) { + if let Ok(level) = skillset.skill_level(Staff(SRange)) { *shockwave_duration *= modifiers.duration.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Staff(SCost)) { + if let Ok(level) = skillset.skill_level(Staff(SCost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } }, @@ -1668,7 +1665,7 @@ impl CharacterAbility { } #[warn(clippy::pedantic)] - fn adjusted_by_sceptre_skills(&mut self, skillset: &skills::SkillSet) { + fn adjusted_by_sceptre_skills(&mut self, skillset: &SkillSet) { #![allow(clippy::enum_glob_use)] use skills::{SceptreSkill::*, Skill::Sceptre}; @@ -1682,19 +1679,19 @@ impl CharacterAbility { .. } => { let modifiers = SKILL_MODIFIERS.sceptre_tree.beam; - if let Ok(Some(level)) = skillset.skill_level(Sceptre(LDamage)) { + if let Ok(level) = skillset.skill_level(Sceptre(LDamage)) { *damage *= modifiers.damage.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(LRange)) { + if let Ok(level) = skillset.skill_level(Sceptre(LRange)) { let range_mod = modifiers.range.powi(level.into()); *range *= range_mod; // Duration modified to keep velocity constant *beam_duration *= range_mod; } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(LRegen)) { + if let Ok(level) = skillset.skill_level(Sceptre(LRegen)) { *energy_regen *= modifiers.energy_regen.powi(level.into()); } - if let (Ok(Some(level)), Some(CombatEffect::Lifesteal(ref mut lifesteal))) = + if let (Ok(level), Some(CombatEffect::Lifesteal(ref mut lifesteal))) = (skillset.skill_level(Sceptre(LLifesteal)), damage_effect) { *lifesteal *= modifiers.lifesteal.powi(level.into()); @@ -1708,18 +1705,18 @@ impl CharacterAbility { .. } => { let modifiers = SKILL_MODIFIERS.sceptre_tree.healing_aura; - if let Ok(Some(level)) = skillset.skill_level(Sceptre(HHeal)) { + if let Ok(level) = skillset.skill_level(Sceptre(HHeal)) { aura.strength *= modifiers.strength.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(HDuration)) { + if let Ok(level) = skillset.skill_level(Sceptre(HDuration)) { if let Some(ref mut duration) = aura.duration { *duration *= modifiers.duration.powi(level.into()); } } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(HRange)) { + if let Ok(level) = skillset.skill_level(Sceptre(HRange)) { *range *= modifiers.range.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(HCost)) { + if let Ok(level) = skillset.skill_level(Sceptre(HCost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } }, @@ -1731,18 +1728,18 @@ impl CharacterAbility { .. } => { let modifiers = SKILL_MODIFIERS.sceptre_tree.warding_aura; - if let Ok(Some(level)) = skillset.skill_level(Sceptre(AStrength)) { + if let Ok(level) = skillset.skill_level(Sceptre(AStrength)) { aura.strength *= modifiers.strength.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(ADuration)) { + if let Ok(level) = skillset.skill_level(Sceptre(ADuration)) { if let Some(ref mut duration) = aura.duration { *duration *= modifiers.duration.powi(level.into()); } } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(ARange)) { + if let Ok(level) = skillset.skill_level(Sceptre(ARange)) { *range *= modifiers.range.powi(level.into()); } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(ACost)) { + if let Ok(level) = skillset.skill_level(Sceptre(ACost)) { *energy_cost *= modifiers.energy_cost.powi(level.into()); } }, diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index e7d64afe5d..f47cd1eff0 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -12,7 +12,7 @@ use std::{ time::Duration, }; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Ord, PartialOrd)] pub enum ToolKind { // weapons Sword, diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index e3bbd9fd74..be2c405d12 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -40,7 +40,7 @@ pub mod projectile; #[cfg(not(target_arch = "wasm32"))] pub mod shockwave; #[cfg(not(target_arch = "wasm32"))] -pub mod skills; +pub mod skillset; #[cfg(not(target_arch = "wasm32"))] mod stats; #[cfg(not(target_arch = "wasm32"))] pub mod visual; @@ -99,7 +99,10 @@ pub use self::{ poise::{Poise, PoiseState}, projectile::{Projectile, ProjectileConstructor}, shockwave::{Shockwave, ShockwaveHitEntities}, - skills::{Skill, SkillGroup, SkillGroupKind, SkillSet}, + skillset::{ + skills::{self, Skill}, + SkillGroup, SkillGroupKind, SkillSet, + }, stats::{Stats, StatsModifier}, visual::{LightAnimation, LightEmitter}, }; diff --git a/common/src/comp/skills.rs b/common/src/comp/skills.rs deleted file mode 100644 index 5c9cf61a14..0000000000 --- a/common/src/comp/skills.rs +++ /dev/null @@ -1,1124 +0,0 @@ -use crate::{ - assets::{self, Asset, AssetExt}, - comp::item::tool::ToolKind, -}; -use hashbrown::{HashMap, HashSet}; -use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; -use specs::{Component, DerefFlaggedStorage}; -use specs_idvs::IdvStorage; -use std::hash::Hash; -use tracing::{trace, warn}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SkillTreeMap(HashMap>); - -impl Asset for SkillTreeMap { - type Loader = assets::RonLoader; - - const EXTENSION: &'static str = "ron"; -} - -pub struct SkillGroupDef { - pub skills: HashSet, - pub total_skill_point_cost: u16, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SkillLevelMap(HashMap>); - -impl Asset for SkillLevelMap { - type Loader = assets::RonLoader; - - const EXTENSION: &'static str = "ron"; -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SkillPrerequisitesMap(HashMap>>); - -impl Asset for SkillPrerequisitesMap { - type Loader = assets::RonLoader; - - const EXTENSION: &'static str = "ron"; -} - -lazy_static! { - // Determines the skills that comprise each skill group. - // - // This data is used to determine which of a player's skill groups a - // particular skill should be added to when a skill unlock is requested. - pub static ref SKILL_GROUP_DEFS: HashMap = { - let map = SkillTreeMap::load_expect_cloned( - "common.skill_trees.skills_skill-groups_manifest", - ).0; - map.iter().map(|(sgk, skills)| - (*sgk, SkillGroupDef { skills: skills.clone(), - total_skill_point_cost: skills - .iter() - .map(|skill| { - if let Some(max_level) = skill.max_level() { - (1..=max_level) - .into_iter() - .map(|level| skill.skill_cost(Some(level))) - .sum() - } else { - skill.skill_cost(None) - } - }) - .sum() - }) - ) - .collect() - }; - // Creates a hashmap for the reverse lookup of skill groups from a skill - pub static ref SKILL_GROUP_LOOKUP: HashMap = { - let map = SkillTreeMap::load_expect_cloned( - "common.skill_trees.skills_skill-groups_manifest", - ).0; - map.iter().flat_map(|(sgk, skills)| skills.into_iter().map(move |s| (*s, *sgk))).collect() - }; - // Loads the maximum level that a skill can obtain - pub static ref SKILL_MAX_LEVEL: HashMap> = { - SkillLevelMap::load_expect_cloned( - "common.skill_trees.skill_max_levels", - ).0 - }; - // Loads the prerequisite skills for a particular skill - pub static ref SKILL_PREREQUISITES: HashMap>> = { - SkillPrerequisitesMap::load_expect_cloned( - "common.skill_trees.skill_prerequisites", - ).0 - }; -} - -/// Represents a skill that a player can unlock, that either grants them some -/// kind of active ability, or a passive effect etc. Obviously because this is -/// an enum it doesn't describe what the skill actually -does-, this will be -/// handled by dedicated ECS systems. -// NOTE: if skill does use some constant, add it to corresponding -// SkillTree Modifiers below. -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum Skill { - General(GeneralSkill), - Sword(SwordSkill), - Axe(AxeSkill), - Hammer(HammerSkill), - Bow(BowSkill), - Staff(StaffSkill), - Sceptre(SceptreSkill), - UnlockGroup(SkillGroupKind), - Roll(RollSkill), - Climb(ClimbSkill), - Swim(SwimSkill), - Pick(MiningSkill), -} - -/// Tree of modifiers that represent how stats are -/// changed per each skill level. -/// -/// It's used as bridge between ECS systems -/// and voxygen Diary for skill descriptions and helps to sync them. -/// -/// NOTE: Just adding constant does nothing, you need to use it in both -/// ECS systems and Diary. -// TODO: make it lazy_static and move to .ron? -pub const SKILL_MODIFIERS: SkillTreeModifiers = SkillTreeModifiers::get(); - -pub struct SkillTreeModifiers { - pub sword_tree: SwordTreeModifiers, - pub axe_tree: AxeTreeModifiers, - pub hammer_tree: HammerTreeModifiers, - pub bow_tree: BowTreeModifiers, - pub staff_tree: StaffTreeModifiers, - pub sceptre_tree: SceptreTreeModifiers, - pub mining_tree: MiningTreeModifiers, - pub general_tree: GeneralTreeModifiers, -} - -impl SkillTreeModifiers { - const fn get() -> Self { - Self { - sword_tree: SwordTreeModifiers::get(), - axe_tree: AxeTreeModifiers::get(), - hammer_tree: HammerTreeModifiers::get(), - bow_tree: BowTreeModifiers::get(), - staff_tree: StaffTreeModifiers::get(), - sceptre_tree: SceptreTreeModifiers::get(), - mining_tree: MiningTreeModifiers::get(), - general_tree: GeneralTreeModifiers::get(), - } - } -} - -pub struct SwordTreeModifiers { - pub dash: SwordDashModifiers, - pub spin: SwordSpinModifiers, -} - -pub struct SwordDashModifiers { - pub energy_cost: f32, - pub energy_drain: f32, - pub base_damage: f32, - pub scaled_damage: f32, - pub forward_speed: f32, -} - -pub struct SwordSpinModifiers { - pub base_damage: f32, - pub swing_duration: f32, - pub energy_cost: f32, - pub num: u32, -} - -impl SwordTreeModifiers { - const fn get() -> Self { - Self { - dash: SwordDashModifiers { - energy_cost: 0.9, - energy_drain: 0.9, - base_damage: 1.1, - scaled_damage: 1.1, - forward_speed: 1.05, - }, - spin: SwordSpinModifiers { - base_damage: 1.2, - swing_duration: 0.9, - energy_cost: 0.9, - num: 1, - }, - } - } -} - -pub struct AxeTreeModifiers { - pub spin: AxeSpinModifiers, - pub leap: AxeLeapModifiers, -} - -pub struct AxeSpinModifiers { - pub base_damage: f32, - pub swing_duration: f32, - pub energy_cost: f32, -} - -pub struct AxeLeapModifiers { - pub base_damage: f32, - pub knockback: f32, - pub energy_cost: f32, - // TODO: split to forward and vertical? - pub leap_strength: f32, -} - -impl AxeTreeModifiers { - const fn get() -> Self { - Self { - spin: AxeSpinModifiers { - base_damage: 1.2, - swing_duration: 0.85, - energy_cost: 0.85, - }, - leap: AxeLeapModifiers { - base_damage: 1.2, - knockback: 1.2, - energy_cost: 0.75, - leap_strength: 1.1, - }, - } - } -} - -pub struct HammerTreeModifiers { - pub single_strike: HammerStrikeModifiers, - pub charged: HammerChargedModifers, - pub leap: HammerLeapModifiers, -} - -pub struct HammerStrikeModifiers { - pub knockback: f32, -} - -pub struct HammerChargedModifers { - pub scaled_damage: f32, - pub scaled_knockback: f32, - pub energy_drain: f32, - pub charge_rate: f32, -} - -pub struct HammerLeapModifiers { - pub base_damage: f32, - pub knockback: f32, - pub energy_cost: f32, - pub leap_strength: f32, - pub range: f32, -} - -impl HammerTreeModifiers { - const fn get() -> Self { - Self { - single_strike: HammerStrikeModifiers { knockback: 1.25 }, - charged: HammerChargedModifers { - scaled_damage: 1.2, - scaled_knockback: 1.3, - energy_drain: 0.85, - charge_rate: 1.15, - }, - leap: HammerLeapModifiers { - base_damage: 1.25, - knockback: 1.3, - energy_cost: 0.75, - leap_strength: 1.1, - range: 0.5, - }, - } - } -} - -pub struct BowTreeModifiers { - pub universal: BowUniversalModifiers, - pub charged: BowChargedModifiers, - pub repeater: BowRepeaterModifiers, - pub shotgun: BowShotgunModifiers, -} - -pub struct BowUniversalModifiers { - // TODO: split per abilities? - pub projectile_speed: f32, -} - -pub struct BowChargedModifiers { - pub damage_scaling: f32, - pub regen_scaling: f32, - pub knockback_scaling: f32, - pub charge_rate: f32, - pub move_speed: f32, -} - -pub struct BowRepeaterModifiers { - pub power: f32, - pub energy_cost: f32, - pub max_speed: f32, -} - -pub struct BowShotgunModifiers { - pub power: f32, - pub energy_cost: f32, - pub num_projectiles: u32, - pub spread: f32, -} - -impl BowTreeModifiers { - const fn get() -> Self { - Self { - universal: BowUniversalModifiers { - projectile_speed: 1.1, - }, - charged: BowChargedModifiers { - damage_scaling: 1.1, - regen_scaling: 1.1, - knockback_scaling: 1.1, - charge_rate: 1.1, - move_speed: 1.1, - }, - repeater: BowRepeaterModifiers { - power: 1.1, - energy_cost: 0.9, - max_speed: 1.2, - }, - shotgun: BowShotgunModifiers { - power: 1.1, - energy_cost: 0.9, - num_projectiles: 1, - spread: 0.9, - }, - } - } -} - -pub struct StaffTreeModifiers { - pub fireball: StaffFireballModifiers, - pub flamethrower: StaffFlamethrowerModifiers, - pub shockwave: StaffShockwaveModifiers, -} - -pub struct StaffFireballModifiers { - pub power: f32, - pub regen: f32, - pub range: f32, -} - -pub struct StaffFlamethrowerModifiers { - pub damage: f32, - pub range: f32, - pub energy_drain: f32, - pub velocity: f32, -} - -pub struct StaffShockwaveModifiers { - pub damage: f32, - pub knockback: f32, - pub duration: f32, - pub energy_cost: f32, -} - -impl StaffTreeModifiers { - const fn get() -> Self { - Self { - fireball: StaffFireballModifiers { - power: 1.1, - regen: 1.1, - range: 1.1, - }, - flamethrower: StaffFlamethrowerModifiers { - damage: 1.2, - range: 1.1, - energy_drain: 0.9, - velocity: 1.1, - }, - shockwave: StaffShockwaveModifiers { - damage: 1.15, - knockback: 1.15, - duration: 1.1, - energy_cost: 0.9, - }, - } - } -} - -pub struct SceptreTreeModifiers { - pub beam: SceptreBeamModifiers, - pub healing_aura: SceptreHealingAuraModifiers, - pub warding_aura: SceptreWardingAuraModifiers, -} - -pub struct SceptreBeamModifiers { - pub damage: f32, - pub range: f32, - pub energy_regen: f32, - pub lifesteal: f32, -} - -pub struct SceptreHealingAuraModifiers { - pub strength: f32, - pub duration: f32, - pub range: f32, - pub energy_cost: f32, -} - -pub struct SceptreWardingAuraModifiers { - pub strength: f32, - pub duration: f32, - pub range: f32, - pub energy_cost: f32, -} - -impl SceptreTreeModifiers { - const fn get() -> Self { - Self { - beam: SceptreBeamModifiers { - damage: 1.1, - range: 1.1, - energy_regen: 1.1, - lifesteal: 1.05, - }, - healing_aura: SceptreHealingAuraModifiers { - strength: 1.05, - duration: 1.1, - range: 1.1, - energy_cost: 0.90, - }, - warding_aura: SceptreWardingAuraModifiers { - strength: 1.05, - duration: 1.1, - range: 1.1, - energy_cost: 0.95, - }, - } - } -} - -pub struct MiningTreeModifiers { - pub speed: f32, - pub gem_gain: f32, - pub ore_gain: f32, -} - -impl MiningTreeModifiers { - const fn get() -> Self { - Self { - speed: 1.1, - gem_gain: 0.05, - ore_gain: 0.05, - } - } -} - -pub struct GeneralTreeModifiers { - pub roll: RollTreeModifiers, - pub swim: SwimTreeModifiers, - pub climb: ClimbTreeModifiers, -} - -pub struct RollTreeModifiers { - pub energy_cost: f32, - pub strength: f32, - pub duration: f32, -} - -pub struct SwimTreeModifiers { - pub speed: f32, -} - -pub struct ClimbTreeModifiers { - pub energy_cost: f32, - pub speed: f32, -} - -impl GeneralTreeModifiers { - const fn get() -> Self { - Self { - roll: RollTreeModifiers { - energy_cost: 0.95, - strength: 1.05, - duration: 1.05, - }, - swim: SwimTreeModifiers { speed: 1.25 }, - climb: ClimbTreeModifiers { - energy_cost: 0.8, - speed: 1.2, - }, - } - } -} -pub enum SkillError { - MissingSkill, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum SwordSkill { - // Sword passives - InterruptingAttacks, - // Triple strike upgrades - TsCombo, - TsDamage, - TsRegen, - TsSpeed, - // Dash upgrades - DCost, - DDrain, - DDamage, - DScaling, - DSpeed, - DInfinite, // Represents charge through, not migrated because laziness - // Spin upgrades - UnlockSpin, - SDamage, - SSpeed, - SCost, - SSpins, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum AxeSkill { - // Double strike upgrades - DsCombo, - DsDamage, - DsSpeed, - DsRegen, - // Spin upgrades - SInfinite, - SHelicopter, - SDamage, - SSpeed, - SCost, - // Leap upgrades - UnlockLeap, - LDamage, - LKnockback, - LCost, - LDistance, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum HammerSkill { - // Single strike upgrades - SsKnockback, - SsDamage, - SsSpeed, - SsRegen, - // Charged melee upgrades - CDamage, - CKnockback, - CDrain, - CSpeed, - // Leap upgrades - UnlockLeap, - LDamage, - LCost, - LDistance, - LKnockback, - LRange, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum BowSkill { - // Passives - ProjSpeed, - // Charged upgrades - CDamage, - CRegen, - CKnockback, - CSpeed, - CMove, - // Repeater upgrades - RDamage, - RCost, - RSpeed, - // Shotgun upgrades - UnlockShotgun, - SDamage, - SCost, - SArrows, - SSpread, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum StaffSkill { - // Basic ranged upgrades - BDamage, - BRegen, - BRadius, - // Flamethrower upgrades - FDamage, - FRange, - FDrain, - FVelocity, - // Shockwave upgrades - UnlockShockwave, - SDamage, - SKnockback, - SRange, - SCost, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum SceptreSkill { - // Lifesteal beam upgrades - LDamage, - LRange, - LLifesteal, - LRegen, - // Healing aura upgrades - HHeal, - HRange, - HDuration, - HCost, - // Warding aura upgrades - UnlockAura, - AStrength, - ADuration, - ARange, - ACost, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum GeneralSkill { - HealthIncrease, - EnergyIncrease, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum RollSkill { - Cost, - Strength, - Duration, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum ClimbSkill { - Cost, - Speed, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum SwimSkill { - Speed, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum MiningSkill { - Speed, - OreGain, - GemGain, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum SkillGroupKind { - General, - Weapon(ToolKind), -} - -impl SkillGroupKind { - /// Gets the cost in experience of earning a skill point - pub fn skill_point_cost(self, level: u16) -> u16 { - const EXP_INCREMENT: f32 = 10.0; - const STARTING_EXP: f32 = 70.0; - const EXP_CEILING: f32 = 1000.0; - const SCALING_FACTOR: f32 = 0.125; - (EXP_INCREMENT - * (EXP_CEILING - / EXP_INCREMENT - / (1.0 - + std::f32::consts::E.powf(-SCALING_FACTOR * level as f32) - * (EXP_CEILING / STARTING_EXP - 1.0))) - .floor()) as u16 - } - - /// Gets the total amount of skill points that can be spent in a particular - /// skill group - pub fn total_skill_point_cost(self) -> u16 { - if let Some(SkillGroupDef { - total_skill_point_cost, - .. - }) = SKILL_GROUP_DEFS.get(&self) - { - *total_skill_point_cost - } else { - 0 - } - } -} - -/// A group of skills that have been unlocked by a player. Each skill group has -/// independent exp and skill points which are used to unlock skills in that -/// skill group. -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct SkillGroup { - pub skill_group_kind: SkillGroupKind, - pub exp: u16, - pub available_sp: u16, - pub earned_sp: u16, -} - -impl SkillGroup { - fn new(skill_group_kind: SkillGroupKind) -> SkillGroup { - SkillGroup { - skill_group_kind, - exp: 0, - available_sp: 0, - earned_sp: 0, - } - } -} - -/// Contains all of a player's skill groups and skills. Provides methods for -/// manipulating assigned skills and skill groups including unlocking skills, -/// refunding skills etc. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct SkillSet { - pub skill_groups: Vec, - pub skills: HashMap>, - pub modify_health: bool, - pub modify_energy: bool, -} - -impl Component for SkillSet { - type Storage = DerefFlaggedStorage>; -} - -impl Default for SkillSet { - /// Instantiate a new skill set with the default skill groups with no - /// unlocked skills in them - used when adding a skill set to a new - /// player - fn default() -> Self { - Self { - skill_groups: vec![ - SkillGroup::new(SkillGroupKind::General), - SkillGroup::new(SkillGroupKind::Weapon(ToolKind::Pick)), - ], - skills: HashMap::new(), - modify_health: false, - modify_energy: false, - } - } -} - -impl SkillSet { - /// Unlocks a skill group for a player. It starts with 0 exp and 0 skill - /// points. - /// - /// ``` - /// use veloren_common::comp::{ - /// item::tool::ToolKind, - /// skills::{SkillGroupKind, SkillSet}, - /// }; - /// - /// let mut skillset = SkillSet::default(); - /// skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Sword)); - /// - /// assert_eq!(skillset.skill_groups.len(), 3); - /// ``` - pub fn unlock_skill_group(&mut self, skill_group_kind: SkillGroupKind) { - if !self.contains_skill_group(skill_group_kind) { - self.skill_groups.push(SkillGroup::new(skill_group_kind)); - } else { - warn!("Tried to unlock already known skill group"); - } - } - - /// Unlocks a skill for a player, assuming they have the relevant skill - /// group unlocked and available SP in that skill group. - /// - /// ``` - /// use veloren_common::comp::skills::{GeneralSkill, Skill, SkillGroupKind, SkillSet}; - /// - /// let mut skillset = SkillSet::default(); - /// skillset.add_skill_points(SkillGroupKind::General, 1); - /// - /// skillset.unlock_skill(Skill::General(GeneralSkill::HealthIncrease)); - /// - /// assert_eq!(skillset.skills.len(), 1); - /// ``` - pub fn unlock_skill(&mut self, skill: Skill) { - if let Some(skill_group_kind) = skill.skill_group_kind() { - let next_level = self.next_skill_level(skill); - let prerequisites_met = self.prerequisites_met(skill); - if !matches!(self.skills.get(&skill), Some(level) if *level == skill.max_level()) { - if let Some(mut skill_group) = self.skill_group_mut(skill_group_kind) { - if prerequisites_met { - if skill_group.available_sp >= skill.skill_cost(next_level) { - skill_group.available_sp -= skill.skill_cost(next_level); - if let Skill::UnlockGroup(group) = skill { - self.unlock_skill_group(group); - } - if matches!(skill, Skill::General(GeneralSkill::HealthIncrease)) { - self.modify_health = true; - } - if matches!(skill, Skill::General(GeneralSkill::EnergyIncrease)) { - self.modify_energy = true; - } - self.skills.insert(skill, next_level); - } else { - trace!("Tried to unlock skill for skill group with insufficient SP"); - } - } else { - trace!("Tried to unlock skill without meeting prerequisite skills"); - } - } else { - trace!("Tried to unlock skill for a skill group that player does not have"); - } - } else { - trace!("Tried to unlock skill the player already has") - } - } else { - warn!( - ?skill, - "Tried to unlock skill that does not exist in any skill group!" - ); - } - } - - /// Removes a skill from a player and refunds 1 skill point in the relevant - /// skill group. - /// - /// ``` - /// use veloren_common::comp::skills::{GeneralSkill, Skill, SkillGroupKind, SkillSet}; - /// - /// let mut skillset = SkillSet::default(); - /// skillset.add_skill_points(SkillGroupKind::General, 1); - /// skillset.unlock_skill(Skill::General(GeneralSkill::HealthIncrease)); - /// - /// skillset.refund_skill(Skill::General(GeneralSkill::HealthIncrease)); - /// - /// assert_eq!(skillset.skills.len(), 0); - /// ``` - pub fn refund_skill(&mut self, skill: Skill) { - if let Ok(level) = self.skill_level(skill) { - if let Some(skill_group_kind) = skill.skill_group_kind() { - if let Some(mut skill_group) = self.skill_group_mut(skill_group_kind) { - skill_group.available_sp += skill.skill_cost(level); - if level.map_or(false, |l| l > 1) { - self.skills.insert(skill, level.map(|l| l - 1)); - } else { - self.skills.remove(&skill); - } - } else { - warn!("Tried to refund skill for a skill group that player does not have"); - } - } else { - warn!( - ?skill, - "Tried to refund skill that does not exist in any skill group" - ) - } - } else { - warn!("Tried to refund skill that has not been unlocked"); - } - } - - /// Adds skill points to a skill group as long as the player has that skill - /// group type. - /// - /// ``` - /// use veloren_common::comp::skills::{SkillGroupKind, SkillSet}; - /// - /// let mut skillset = SkillSet::default(); - /// skillset.add_skill_points(SkillGroupKind::General, 1); - /// - /// assert_eq!(skillset.skill_groups[0].available_sp, 1); - /// ``` - pub fn add_skill_points( - &mut self, - skill_group_kind: SkillGroupKind, - number_of_skill_points: u16, - ) { - if let Some(mut skill_group) = self.skill_group_mut(skill_group_kind) { - skill_group.available_sp = skill_group - .available_sp - .saturating_add(number_of_skill_points); - skill_group.earned_sp = skill_group.earned_sp.saturating_add(number_of_skill_points); - } else { - warn!("Tried to add skill points to a skill group that player does not have"); - } - } - - /// Adds a skill point while subtracting the necessary amount of experience - pub fn earn_skill_point(&mut self, skill_group_kind: SkillGroupKind) { - let sp_cost = self.skill_point_cost(skill_group_kind); - if let Some(mut skill_group) = self.skill_group_mut(skill_group_kind) { - skill_group.exp = skill_group.exp.saturating_sub(sp_cost); - skill_group.available_sp = skill_group.available_sp.saturating_add(1); - skill_group.earned_sp = skill_group.earned_sp.saturating_add(1); - } - } - - /// Checks if the skill set of an entity contains a particular skill group - /// type - pub fn contains_skill_group(&self, skill_group_kind: SkillGroupKind) -> bool { - self.skill_groups - .iter() - .any(|x| x.skill_group_kind == skill_group_kind) - } - - /// Adds/subtracts experience to the skill group within an entity's skill - /// set - pub fn change_experience(&mut self, skill_group_kind: SkillGroupKind, amount: i32) { - if let Some(mut skill_group) = self.skill_group_mut(skill_group_kind) { - skill_group.exp = (skill_group.exp as i32 + amount) as u16; - } else { - warn!("Tried to add experience to a skill group that player does not have"); - } - } - - /// Checks that the skill set contains all prerequisite skills for a - /// particular skill - pub fn prerequisites_met(&self, skill: Skill) -> bool { - skill - .prerequisite_skills() - .all(|(s, l)| self.skill_level(s).map_or(false, |l_b| l_b >= l)) - } - - /// Returns a reference to a particular skill group in a skillset - fn skill_group(&self, skill_group: SkillGroupKind) -> Option<&SkillGroup> { - self.skill_groups - .iter() - .find(|s_g| s_g.skill_group_kind == skill_group) - } - - /// Returns a reference to a particular skill group in a skillset - fn skill_group_mut(&mut self, skill_group: SkillGroupKind) -> Option<&mut SkillGroup> { - self.skill_groups - .iter_mut() - .find(|s_g| s_g.skill_group_kind == skill_group) - } - - /// Gets the available points for a particular skill group - pub fn available_sp(&self, skill_group: SkillGroupKind) -> u16 { - self.skill_group(skill_group) - .map_or(0, |s_g| s_g.available_sp) - } - - /// Gets the total earned points for a particular skill group - pub fn earned_sp(&self, skill_group: SkillGroupKind) -> u16 { - self.skill_group(skill_group).map_or(0, |s_g| s_g.earned_sp) - } - - /// Gets the available experience for a particular skill group - pub fn experience(&self, skill_group: SkillGroupKind) -> u16 { - self.skill_group(skill_group).map_or(0, |s_g| s_g.exp) - } - - /// Gets skill point cost to purchase skill of next level - pub fn skill_cost(&self, skill: Skill) -> u16 { - let next_level = self.next_skill_level(skill); - skill.skill_cost(next_level) - } - - /// Checks if player has sufficient skill points to purchase a skill - pub fn sufficient_skill_points(&self, skill: Skill) -> bool { - if let Some(skill_group_kind) = skill.skill_group_kind() { - if let Some(skill_group) = self - .skill_groups - .iter() - .find(|x| x.skill_group_kind == skill_group_kind) - { - let needed_sp = self.skill_cost(skill); - skill_group.available_sp >= needed_sp - } else { - false - } - } else { - false - } - } - - /// Checks if the player has available SP to spend - pub fn has_available_sp(&self) -> bool { - self.skill_groups.iter().any(|sg| { - sg.available_sp > 0 - && (sg.earned_sp - sg.available_sp) < sg.skill_group_kind.total_skill_point_cost() - }) - } - - /// Checks how much experience is needed for the next skill point in a tree - pub fn skill_point_cost(&self, skill_group: SkillGroupKind) -> u16 { - if let Some(level) = self.skill_group(skill_group).map(|sg| sg.earned_sp) { - skill_group.skill_point_cost(level) - } else { - skill_group.skill_point_cost(0) - } - } - - /// Checks if the skill is at max level in a skill set - pub fn is_at_max_level(&self, skill: Skill) -> bool { - if let Ok(level) = self.skill_level(skill) { - level == skill.max_level() - } else { - false - } - } - - /// Checks if skill set contains a skill - pub fn has_skill(&self, skill: Skill) -> bool { self.skills.contains_key(&skill) } - - /// Returns the level of the skill - pub fn skill_level(&self, skill: Skill) -> Result, SkillError> { - if let Some(level) = self.skills.get(&skill).copied() { - Ok(level) - } else { - Err(SkillError::MissingSkill) - } - } - - /// Returns the level of the skill or passed value as default - pub fn skill_level_or(&self, skill: Skill, default: u16) -> u16 { - if let Ok(Some(level)) = self.skill_level(skill) { - level - } else { - default - } - } - - /// Checks the next level of a skill - fn next_skill_level(&self, skill: Skill) -> Option { - if let Ok(level) = self.skill_level(skill) { - level.map(|l| l + 1) - } else { - skill.max_level().map(|_| 1) - } - } -} - -impl Skill { - /// Returns a vec of prerequisite skills (it should only be necessary to - /// note direct prerequisites) - pub fn prerequisite_skills(&self) -> impl Iterator)> { - SKILL_PREREQUISITES - .get(self) - .into_iter() - .flatten() - .map(|(skill, level)| (*skill, *level)) - } - - /// Returns the cost in skill points of unlocking a particular skill - pub fn skill_cost(&self, level: Option) -> u16 { - // TODO: Better balance the costs later - level.unwrap_or(1) - } - - /// Returns the maximum level a skill can reach, returns None if the skill - /// doesn't level - pub fn max_level(&self) -> Option { SKILL_MAX_LEVEL.get(self).copied().flatten() } - - /// Returns the skill group type for a skill from the static skill group - /// definitions. - pub fn skill_group_kind(&self) -> Option { - SKILL_GROUP_LOOKUP.get(self).copied() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_refund_skill() { - let mut skillset = SkillSet::default(); - skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)); - skillset.add_skill_points(SkillGroupKind::Weapon(ToolKind::Axe), 1); - skillset.unlock_skill(Skill::Axe(AxeSkill::UnlockLeap)); - - assert_eq!(skillset.skill_groups[2].available_sp, 0); - assert_eq!(skillset.skills.len(), 1); - assert!(skillset.has_skill(Skill::Axe(AxeSkill::UnlockLeap))); - - skillset.refund_skill(Skill::Axe(AxeSkill::UnlockLeap)); - - assert_eq!(skillset.skill_groups[2].available_sp, 1); - assert_eq!(skillset.skills.get(&Skill::Axe(AxeSkill::UnlockLeap)), None); - } - - #[test] - fn test_unlock_skillgroup() { - let mut skillset = SkillSet::default(); - skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)); - - assert_eq!(skillset.skill_groups.len(), 3); - assert_eq!( - skillset.skill_groups[2], - SkillGroup::new(SkillGroupKind::Weapon(ToolKind::Axe)) - ); - } - - #[test] - fn test_unlock_skill() { - let mut skillset = SkillSet::default(); - - skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)); - skillset.add_skill_points(SkillGroupKind::Weapon(ToolKind::Axe), 1); - - assert_eq!(skillset.skill_groups[2].available_sp, 1); - assert_eq!(skillset.skills.len(), 0); - - // Try unlocking a skill with enough skill points - skillset.unlock_skill(Skill::Axe(AxeSkill::UnlockLeap)); - - assert_eq!(skillset.skill_groups[2].available_sp, 0); - assert_eq!(skillset.skills.len(), 1); - assert!(skillset.has_skill(Skill::Axe(AxeSkill::UnlockLeap))); - - // Try unlocking a skill without enough skill points - skillset.unlock_skill(Skill::Axe(AxeSkill::DsCombo)); - - assert_eq!(skillset.skills.len(), 1); - assert_eq!(skillset.skills.get(&Skill::Axe(AxeSkill::DsCombo)), None); - } - - #[test] - fn test_add_skill_points() { - let mut skillset = SkillSet::default(); - skillset.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Axe)); - skillset.add_skill_points(SkillGroupKind::Weapon(ToolKind::Axe), 1); - - assert_eq!(skillset.skill_groups[2].available_sp, 1); - } -} diff --git a/common/src/comp/skillset/mod.rs b/common/src/comp/skillset/mod.rs new file mode 100644 index 0000000000..5ef2e4b325 --- /dev/null +++ b/common/src/comp/skillset/mod.rs @@ -0,0 +1,574 @@ +use crate::{ + assets::{self, Asset, AssetExt}, + comp::{ + item::tool::ToolKind, + skills::{GeneralSkill, Skill}, + }, +}; +use hashbrown::HashMap; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use specs::{Component, DerefFlaggedStorage}; +use specs_idvs::IdvStorage; +use std::{collections::BTreeSet, hash::Hash}; +use tracing::{trace, warn}; + +pub mod skills; + +/// BTreeSet is used here to ensure that skills are ordered. This is important +/// to ensure that the hash created from it is consistent so that we don't +/// needlessly force a respec when loading skills from persistence. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SkillTreeMap(HashMap>); + +impl Asset for SkillTreeMap { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} + +pub struct SkillGroupDef { + pub skills: BTreeSet, + pub total_skill_point_cost: u16, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SkillLevelMap(HashMap); + +impl Asset for SkillLevelMap { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} + +/// Contains the prerequisite skills for each skill. It cannot currently detect +/// cyclic dependencies, so if you modify the prerequisite map ensure that there +/// are no cycles of prerequisites. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SkillPrerequisitesMap(HashMap>); + +impl Asset for SkillPrerequisitesMap { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} + +lazy_static! { + // Determines the skills that comprise each skill group. + // + // This data is used to determine which of a player's skill groups a + // particular skill should be added to when a skill unlock is requested. + pub static ref SKILL_GROUP_DEFS: HashMap = { + let map = SkillTreeMap::load_expect_cloned( + "common.skill_trees.skills_skill-groups_manifest", + ).0; + map.iter().map(|(sgk, skills)| + (*sgk, SkillGroupDef { skills: skills.clone(), + total_skill_point_cost: skills + .iter() + .map(|skill| { + let max_level = skill.max_level(); + (1..=max_level) + .into_iter() + .map(|level| skill.skill_cost(level)) + .sum::() + }) + .sum() + }) + ) + .collect() + }; + // Creates a hashmap for the reverse lookup of skill groups from a skill + pub static ref SKILL_GROUP_LOOKUP: HashMap = { + let map = SkillTreeMap::load_expect_cloned( + "common.skill_trees.skills_skill-groups_manifest", + ).0; + map.iter().flat_map(|(sgk, skills)| skills.iter().map(move |s| (*s, *sgk))).collect() + }; + // Loads the maximum level that a skill can obtain + pub static ref SKILL_MAX_LEVEL: HashMap = { + SkillLevelMap::load_expect_cloned( + "common.skill_trees.skill_max_levels", + ).0 + }; + // Loads the prerequisite skills for a particular skill + pub static ref SKILL_PREREQUISITES: HashMap> = { + SkillPrerequisitesMap::load_expect_cloned( + "common.skill_trees.skill_prerequisites", + ).0 + }; + pub static ref SKILL_GROUP_HASHES: HashMap> = { + let map = SkillTreeMap::load_expect_cloned( + "common.skill_trees.skills_skill-groups_manifest", + ).0; + let mut hashes = HashMap::new(); + for (skill_group_kind, skills) in map.iter() { + let mut hasher = Sha256::new(); + let json_input: Vec<_> = skills.iter().map(|skill| (*skill, skill.max_level())).collect(); + let hash_input = serde_json::to_string(&json_input).unwrap_or_default(); + hasher.update(hash_input.as_bytes()); + let hash_result = hasher.finalize(); + hashes.insert(*skill_group_kind, hash_result.iter().copied().collect()); + } + hashes + }; +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum SkillGroupKind { + General, + Weapon(ToolKind), +} + +impl SkillGroupKind { + /// Gets the cost in experience of earning a skill point + /// Changing this is forward compatible with persistence and will + /// automatically force a respec for skill group kinds that are affected. + pub fn skill_point_cost(self, level: u16) -> u32 { + const EXP_INCREMENT: f32 = 10.0; + const STARTING_EXP: f32 = 70.0; + const EXP_CEILING: f32 = 1000.0; + const SCALING_FACTOR: f32 = 0.125; + (EXP_INCREMENT + * (EXP_CEILING + / EXP_INCREMENT + / (1.0 + + std::f32::consts::E.powf(-SCALING_FACTOR * level as f32) + * (EXP_CEILING / STARTING_EXP - 1.0))) + .floor()) as u32 + } + + /// Gets the total amount of skill points that can be spent in a particular + /// skill group + pub fn total_skill_point_cost(self) -> u16 { + if let Some(SkillGroupDef { + total_skill_point_cost, + .. + }) = SKILL_GROUP_DEFS.get(&self) + { + *total_skill_point_cost + } else { + 0 + } + } +} + +/// A group of skills that have been unlocked by a player. Each skill group has +/// independent exp and skill points which are used to unlock skills in that +/// skill group. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct SkillGroup { + // Invariant should be maintained that this is the same kind as the key that the skill group is + // inserted into the skillset as. + pub skill_group_kind: SkillGroupKind, + // The invariant that (earned_exp >= available_exp) should not be violated + pub available_exp: u32, + pub earned_exp: u32, + // The invariant that (earned_sp >= available_sp) should not be violated + pub available_sp: u16, + pub earned_sp: u16, + // Used for persistence + pub ordered_skills: Vec, +} + +impl SkillGroup { + fn new(skill_group_kind: SkillGroupKind) -> SkillGroup { + SkillGroup { + skill_group_kind, + available_exp: 0, + earned_exp: 0, + available_sp: 0, + earned_sp: 0, + ordered_skills: Vec::new(), + } + } + + /// Returns the amount of experience in a skill group that has been spent to + /// acquire skill points Relies on the invariant that (earned_exp >= + /// available_exp) to ensure function does not underflow + pub fn spent_exp(&self) -> u32 { self.earned_exp - self.available_exp } + + /// Adds a skill point while subtracting the necessary amount of experience + fn earn_skill_point(&mut self) -> Result<(), SpRewardError> { + let sp_cost = self.skill_group_kind.skill_point_cost(self.earned_sp); + // If there is insufficient available exp, checked sub will fail as the result + // would be less than 0 + let new_available_exp = self + .available_exp + .checked_sub(sp_cost) + .ok_or(SpRewardError::InsufficientExp)?; + let new_earned_sp = self + .earned_sp + .checked_add(1) + .ok_or(SpRewardError::Overflow)?; + let new_available_sp = self + .available_sp + .checked_add(1) + .ok_or(SpRewardError::Overflow)?; + self.available_exp = new_available_exp; + self.earned_sp = new_earned_sp; + self.available_sp = new_available_sp; + Ok(()) + } + + /// Also attempts to earn a skill point after adding experience. If a skill + /// point was earned, returns how many skill points the skill group now has + /// earned in total. + pub fn add_experience(&mut self, amount: u32) -> Option { + self.earned_exp = self.earned_exp.saturating_add(amount); + self.available_exp = self.available_exp.saturating_add(amount); + + let mut return_val = None; + // Attempt to earn skill point + while self.earn_skill_point().is_ok() { + return_val = Some(self.earned_sp); + } + return_val + } +} + +/// Contains all of a player's skill groups and skills. Provides methods for +/// manipulating assigned skills and skill groups including unlocking skills, +/// refunding skills etc. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct SkillSet { + skill_groups: HashMap, + skills: HashMap, + pub modify_health: bool, + pub modify_energy: bool, + /// Used to indicate to the frontend that there was an error in loading the + /// skillset from the database + pub persistence_load_error: Option, +} + +impl Component for SkillSet { + type Storage = DerefFlaggedStorage>; +} + +impl Default for SkillSet { + /// Instantiate a new skill set with the default skill groups with no + /// unlocked skills in them - used when adding a skill set to a new + /// player + fn default() -> Self { + // Create an empty skillset + let mut skill_group = Self { + skill_groups: HashMap::new(), + skills: SkillSet::initial_skills(), + modify_health: false, + modify_energy: false, + persistence_load_error: None, + }; + + // Insert default skill groups + skill_group.unlock_skill_group(SkillGroupKind::General); + skill_group.unlock_skill_group(SkillGroupKind::Weapon(ToolKind::Pick)); + + skill_group + } +} + +impl SkillSet { + pub fn initial_skills() -> HashMap { + let mut skills = HashMap::new(); + skills.insert(Skill::UnlockGroup(SkillGroupKind::General), 1); + skills.insert( + Skill::UnlockGroup(SkillGroupKind::Weapon(ToolKind::Pick)), + 1, + ); + skills + } + + pub fn load_from_database( + skill_groups: HashMap, + mut all_skills: HashMap, SkillsPersistenceError>>, + ) -> Self { + let mut skillset = SkillSet { + skill_groups, + skills: SkillSet::initial_skills(), + modify_health: true, + modify_energy: true, + persistence_load_error: None, + }; + + // Loops while checking the all_skills hashmap. For as long as it can find an + // entry where the skill group kind is unlocked, insert the skills corresponding + // to that skill group kind. When no more skill group kinds can be found, break + // the loop. + while let Some(skill_group_kind) = all_skills + .keys() + .find(|kind| skillset.has_skill(Skill::UnlockGroup(**kind))) + .copied() + { + // Remove valid skill group kind from the hash map so that loop eventually + // terminates. + if let Some(skills_result) = all_skills.remove(&skill_group_kind) { + match skills_result { + Ok(skills) => { + let backup_skillset = skillset.clone(); + // Iterate over all skills and make sure that unlocking them is successful. + // If any fail, fall back to skillset before + // unlocking any to allow a full respec + if !skills + .iter() + .all(|skill| skillset.unlock_skill(*skill).is_ok()) + { + skillset = backup_skillset; + // If unlocking failed, set persistence_load_error + skillset.persistence_load_error = + Some(SkillsPersistenceError::SkillsUnlockFailed) + } + }, + Err(persistence_error) => { + skillset.persistence_load_error = Some(persistence_error) + }, + } + } + } + + skillset + } + + /// Checks if a particular skill group is accessible for an entity + pub fn skill_group_accessible(&self, skill_group_kind: SkillGroupKind) -> bool { + self.skill_groups.contains_key(&skill_group_kind) + && self.has_skill(Skill::UnlockGroup(skill_group_kind)) + } + + /// Unlocks a skill group for a player. It starts with 0 exp and 0 skill + /// points. + pub fn unlock_skill_group(&mut self, skill_group_kind: SkillGroupKind) { + if !self.skill_groups.contains_key(&skill_group_kind) { + self.skill_groups + .insert(skill_group_kind, SkillGroup::new(skill_group_kind)); + } else { + warn!("Tried to unlock already known skill group"); + } + } + + /// Returns an iterator over skill groups + pub fn skill_groups(&self) -> impl Iterator { self.skill_groups.values() } + + /// Returns a reference to a particular skill group in a skillset + fn skill_group(&self, skill_group: SkillGroupKind) -> Option<&SkillGroup> { + self.skill_groups.get(&skill_group) + } + + /// Returns a mutable reference to a particular skill group in a skillset + /// Requires that skillset contains skill that unlocks the skill group + fn skill_group_mut(&mut self, skill_group: SkillGroupKind) -> Option<&mut SkillGroup> { + // In order to mutate skill group, we check that the prerequisite skill has been + // acquired, as this is one of the requirements for us to consider the skill + // group accessible. + let skill_group_accessible = self.skill_group_accessible(skill_group); + if skill_group_accessible { + self.skill_groups.get_mut(&skill_group) + } else { + None + } + } + + /// Adds experience to the skill group within an entity's skill set, will + /// attempt to earn a skill point while doing so. If a skill point was + /// earned, returns the number of earned skill points in the skill group. + pub fn add_experience(&mut self, skill_group_kind: SkillGroupKind, amount: u32) -> Option { + if let Some(skill_group) = self.skill_group_mut(skill_group_kind) { + skill_group.add_experience(amount) + } else { + warn!("Tried to add experience to a skill group that player does not have"); + None + } + } + + /// Gets the available experience for a particular skill group + pub fn available_experience(&self, skill_group: SkillGroupKind) -> u32 { + self.skill_group(skill_group) + .map_or(0, |s_g| s_g.available_exp) + } + + /// Checks how much experience is needed for the next skill point in a tree + pub fn skill_point_cost(&self, skill_group: SkillGroupKind) -> u32 { + if let Some(level) = self.skill_group(skill_group).map(|sg| sg.earned_sp) { + skill_group.skill_point_cost(level) + } else { + skill_group.skill_point_cost(0) + } + } + + /// Adds skill points to a skill group as long as the player has that skill + /// group type. + pub fn add_skill_points( + &mut self, + skill_group_kind: SkillGroupKind, + number_of_skill_points: u16, + ) { + for _ in 0..number_of_skill_points { + let exp_needed = self.skill_point_cost(skill_group_kind); + self.add_experience(skill_group_kind, exp_needed); + } + } + + /// Gets the available points for a particular skill group + pub fn available_sp(&self, skill_group: SkillGroupKind) -> u16 { + self.skill_group(skill_group) + .map_or(0, |s_g| s_g.available_sp) + } + + /// Gets the total earned points for a particular skill group + pub fn earned_sp(&self, skill_group: SkillGroupKind) -> u16 { + self.skill_group(skill_group).map_or(0, |s_g| s_g.earned_sp) + } + + /// Checks that the skill set contains all prerequisite skills of the + /// required level for a particular skill + pub fn prerequisites_met(&self, skill: Skill) -> bool { + skill + .prerequisite_skills() + .all(|(s, l)| self.skill_level(s).map_or(false, |l_b| l_b >= l)) + } + + /// Gets skill point cost to purchase skill of next level + pub fn skill_cost(&self, skill: Skill) -> u16 { + let next_level = self.next_skill_level(skill); + skill.skill_cost(next_level) + } + + /// Checks if player has sufficient skill points to purchase a skill + pub fn sufficient_skill_points(&self, skill: Skill) -> bool { + if let Some(skill_group_kind) = skill.skill_group_kind() { + if let Some(skill_group) = self.skill_group(skill_group_kind) { + let needed_sp = self.skill_cost(skill); + skill_group.available_sp >= needed_sp + } else { + false + } + } else { + false + } + } + + /// Checks the next level of a skill + fn next_skill_level(&self, skill: Skill) -> u16 { + if let Ok(level) = self.skill_level(skill) { + // If already has skill, then level + 1 + level + 1 + } else { + // Otherwise the next level is the first level + 1 + } + } + + /// Unlocks a skill for a player, assuming they have the relevant skill + /// group unlocked and available SP in that skill group. + pub fn unlock_skill(&mut self, skill: Skill) -> Result<(), SkillUnlockError> { + if let Some(skill_group_kind) = skill.skill_group_kind() { + let next_level = self.next_skill_level(skill); + let prerequisites_met = self.prerequisites_met(skill); + // Check that skill is not yet at max level + if !matches!(self.skills.get(&skill), Some(level) if *level == skill.max_level()) { + if let Some(mut skill_group) = self.skill_group_mut(skill_group_kind) { + if prerequisites_met { + if let Some(new_available_sp) = skill_group + .available_sp + .checked_sub(skill.skill_cost(next_level)) + { + skill_group.available_sp = new_available_sp; + skill_group.ordered_skills.push(skill); + match skill { + Skill::UnlockGroup(group) => { + self.unlock_skill_group(group); + }, + Skill::General(GeneralSkill::HealthIncrease) => { + self.modify_health = true; + }, + Skill::General(GeneralSkill::EnergyIncrease) => { + self.modify_energy = true; + }, + _ => {}, + } + self.skills.insert(skill, next_level); + Ok(()) + } else { + trace!("Tried to unlock skill for skill group with insufficient SP"); + Err(SkillUnlockError::InsufficientSP) + } + } else { + trace!("Tried to unlock skill without meeting prerequisite skills"); + Err(SkillUnlockError::MissingPrerequisites) + } + } else { + trace!("Tried to unlock skill for a skill group that player does not have"); + Err(SkillUnlockError::UnavailableSkillGroup) + } + } else { + trace!("Tried to unlock skill the player already has"); + Err(SkillUnlockError::SkillAlreadyUnlocked) + } + } else { + warn!( + ?skill, + "Tried to unlock skill that does not exist in any skill group!" + ); + Err(SkillUnlockError::NoParentSkillTree) + } + } + + /// Checks if the player has available SP to spend + pub fn has_available_sp(&self) -> bool { + self.skill_groups.iter().any(|(kind, sg)| { + sg.available_sp > 0 + // Subtraction in bounds because of the invariant that available_sp <= earned_sp + && (sg.earned_sp - sg.available_sp) < kind.total_skill_point_cost() + }) + } + + /// Checks if the skill is at max level in a skill set + pub fn is_at_max_level(&self, skill: Skill) -> bool { + if let Ok(level) = self.skill_level(skill) { + level == skill.max_level() + } else { + false + } + } + + /// Checks if skill set contains a skill + pub fn has_skill(&self, skill: Skill) -> bool { self.skills.contains_key(&skill) } + + /// Returns the level of the skill + pub fn skill_level(&self, skill: Skill) -> Result { + if let Some(level) = self.skills.get(&skill).copied() { + Ok(level) + } else { + Err(SkillError::MissingSkill) + } + } +} + +#[derive(Debug)] +pub enum SkillError { + MissingSkill, +} + +#[derive(Debug)] +pub enum SkillUnlockError { + InsufficientSP, + MissingPrerequisites, + UnavailableSkillGroup, + SkillAlreadyUnlocked, + NoParentSkillTree, +} + +#[derive(Debug)] +pub enum SpRewardError { + InsufficientExp, + UnavailableSkillGroup, + Overflow, +} + +#[derive(Debug, PartialEq, Clone, Copy, Deserialize, Serialize)] +pub enum SkillsPersistenceError { + HashMismatch, + DeserializationFailure, + SpentExpMismatch, + SkillsUnlockFailed, +} diff --git a/common/src/comp/skillset/skills.rs b/common/src/comp/skillset/skills.rs new file mode 100644 index 0000000000..5ecbf8db35 --- /dev/null +++ b/common/src/comp/skillset/skills.rs @@ -0,0 +1,593 @@ +use crate::comp::skillset::{ + SkillGroupKind, SKILL_GROUP_LOOKUP, SKILL_MAX_LEVEL, SKILL_PREREQUISITES, +}; +use serde::{Deserialize, Serialize}; + +/// Represents a skill that a player can unlock, that either grants them some +/// kind of active ability, or a passive effect etc. Obviously because this is +/// an enum it doesn't describe what the skill actually -does-, this will be +/// handled by dedicated ECS systems. +// NOTE: if skill does use some constant, add it to corresponding +// SkillTree Modifiers below. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum Skill { + General(GeneralSkill), + Sword(SwordSkill), + Axe(AxeSkill), + Hammer(HammerSkill), + Bow(BowSkill), + Staff(StaffSkill), + Sceptre(SceptreSkill), + Roll(RollSkill), + Climb(ClimbSkill), + Swim(SwimSkill), + Pick(MiningSkill), + UnlockGroup(SkillGroupKind), +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum SwordSkill { + // Sword passives + InterruptingAttacks, + // Triple strike upgrades + TsCombo, + TsDamage, + TsRegen, + TsSpeed, + // Dash upgrades + DCost, + DDrain, + DDamage, + DScaling, + DSpeed, + DChargeThrough, + // Spin upgrades + UnlockSpin, + SDamage, + SSpeed, + SCost, + SSpins, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum AxeSkill { + // Double strike upgrades + DsCombo, + DsDamage, + DsSpeed, + DsRegen, + // Spin upgrades + SInfinite, + SHelicopter, + SDamage, + SSpeed, + SCost, + // Leap upgrades + UnlockLeap, + LDamage, + LKnockback, + LCost, + LDistance, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum HammerSkill { + // Single strike upgrades + SsKnockback, + SsDamage, + SsSpeed, + SsRegen, + // Charged melee upgrades + CDamage, + CKnockback, + CDrain, + CSpeed, + // Leap upgrades + UnlockLeap, + LDamage, + LCost, + LDistance, + LKnockback, + LRange, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum BowSkill { + // Passives + ProjSpeed, + // Charged upgrades + CDamage, + CRegen, + CKnockback, + CSpeed, + CMove, + // Repeater upgrades + RDamage, + RCost, + RSpeed, + // Shotgun upgrades + UnlockShotgun, + SDamage, + SCost, + SArrows, + SSpread, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum StaffSkill { + // Basic ranged upgrades + BDamage, + BRegen, + BRadius, + // Flamethrower upgrades + FDamage, + FRange, + FDrain, + FVelocity, + // Shockwave upgrades + UnlockShockwave, + SDamage, + SKnockback, + SRange, + SCost, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum SceptreSkill { + // Lifesteal beam upgrades + LDamage, + LRange, + LLifesteal, + LRegen, + // Healing aura upgrades + HHeal, + HRange, + HDuration, + HCost, + // Warding aura upgrades + UnlockAura, + AStrength, + ADuration, + ARange, + ACost, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum GeneralSkill { + HealthIncrease, + EnergyIncrease, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum RollSkill { + Cost, + Strength, + Duration, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum ClimbSkill { + Cost, + Speed, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum SwimSkill { + Speed, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub enum MiningSkill { + Speed, + OreGain, + GemGain, +} + +impl Skill { + /// Returns a vec of prerequisite skills (it should only be necessary to + /// note direct prerequisites) + /// Automatically filters itself from the skills returned + /// Is unable to detect cyclic dependencies, so ensure that there are no + /// cycles if you modify the prerequisite map. + pub fn prerequisite_skills(&self) -> impl Iterator + '_ { + SKILL_PREREQUISITES + .get(self) + .into_iter() + .flatten() + .filter(move |(skill, _)| self != *skill) + .map(|(skill, level)| (*skill, *level)) + } + + /// Returns the cost in skill points of unlocking a particular skill + pub fn skill_cost(&self, level: u16) -> u16 { + // TODO: Better balance the costs later + level + } + + /// Returns the maximum level a skill can reach, returns None if the skill + /// doesn't level + pub fn max_level(&self) -> u16 { SKILL_MAX_LEVEL.get(self).copied().unwrap_or(1) } + + /// Returns the skill group type for a skill from the static skill group + /// definitions. + pub fn skill_group_kind(&self) -> Option { + SKILL_GROUP_LOOKUP.get(self).copied() + } +} + +/// Tree of modifiers that represent how stats are +/// changed per each skill level. +/// +/// It's used as bridge between ECS systems +/// and voxygen Diary for skill descriptions and helps to sync them. +/// +/// NOTE: Just adding constant does nothing, you need to use it in both +/// ECS systems and Diary. +// TODO: make it lazy_static and move to .ron? +pub const SKILL_MODIFIERS: SkillTreeModifiers = SkillTreeModifiers::get(); + +pub struct SkillTreeModifiers { + pub sword_tree: SwordTreeModifiers, + pub axe_tree: AxeTreeModifiers, + pub hammer_tree: HammerTreeModifiers, + pub bow_tree: BowTreeModifiers, + pub staff_tree: StaffTreeModifiers, + pub sceptre_tree: SceptreTreeModifiers, + pub mining_tree: MiningTreeModifiers, + pub general_tree: GeneralTreeModifiers, +} + +impl SkillTreeModifiers { + const fn get() -> Self { + Self { + sword_tree: SwordTreeModifiers::get(), + axe_tree: AxeTreeModifiers::get(), + hammer_tree: HammerTreeModifiers::get(), + bow_tree: BowTreeModifiers::get(), + staff_tree: StaffTreeModifiers::get(), + sceptre_tree: SceptreTreeModifiers::get(), + mining_tree: MiningTreeModifiers::get(), + general_tree: GeneralTreeModifiers::get(), + } + } +} + +pub struct SwordTreeModifiers { + pub dash: SwordDashModifiers, + pub spin: SwordSpinModifiers, +} + +pub struct SwordDashModifiers { + pub energy_cost: f32, + pub energy_drain: f32, + pub base_damage: f32, + pub scaled_damage: f32, + pub forward_speed: f32, +} + +pub struct SwordSpinModifiers { + pub base_damage: f32, + pub swing_duration: f32, + pub energy_cost: f32, + pub num: u32, +} + +impl SwordTreeModifiers { + const fn get() -> Self { + Self { + dash: SwordDashModifiers { + energy_cost: 0.9, + energy_drain: 0.9, + base_damage: 1.1, + scaled_damage: 1.1, + forward_speed: 1.05, + }, + spin: SwordSpinModifiers { + base_damage: 1.2, + swing_duration: 0.9, + energy_cost: 0.9, + num: 1, + }, + } + } +} + +pub struct AxeTreeModifiers { + pub spin: AxeSpinModifiers, + pub leap: AxeLeapModifiers, +} + +pub struct AxeSpinModifiers { + pub base_damage: f32, + pub swing_duration: f32, + pub energy_cost: f32, +} + +pub struct AxeLeapModifiers { + pub base_damage: f32, + pub knockback: f32, + pub energy_cost: f32, + // TODO: split to forward and vertical? + pub leap_strength: f32, +} + +impl AxeTreeModifiers { + const fn get() -> Self { + Self { + spin: AxeSpinModifiers { + base_damage: 1.2, + swing_duration: 0.85, + energy_cost: 0.85, + }, + leap: AxeLeapModifiers { + base_damage: 1.2, + knockback: 1.2, + energy_cost: 0.75, + leap_strength: 1.1, + }, + } + } +} + +pub struct HammerTreeModifiers { + pub single_strike: HammerStrikeModifiers, + pub charged: HammerChargedModifers, + pub leap: HammerLeapModifiers, +} + +pub struct HammerStrikeModifiers { + pub knockback: f32, +} + +pub struct HammerChargedModifers { + pub scaled_damage: f32, + pub scaled_knockback: f32, + pub energy_drain: f32, + pub charge_rate: f32, +} + +pub struct HammerLeapModifiers { + pub base_damage: f32, + pub knockback: f32, + pub energy_cost: f32, + pub leap_strength: f32, + pub range: f32, +} + +impl HammerTreeModifiers { + const fn get() -> Self { + Self { + single_strike: HammerStrikeModifiers { knockback: 1.25 }, + charged: HammerChargedModifers { + scaled_damage: 1.2, + scaled_knockback: 1.3, + energy_drain: 0.85, + charge_rate: 1.15, + }, + leap: HammerLeapModifiers { + base_damage: 1.25, + knockback: 1.3, + energy_cost: 0.75, + leap_strength: 1.1, + range: 0.5, + }, + } + } +} + +pub struct BowTreeModifiers { + pub universal: BowUniversalModifiers, + pub charged: BowChargedModifiers, + pub repeater: BowRepeaterModifiers, + pub shotgun: BowShotgunModifiers, +} + +pub struct BowUniversalModifiers { + // TODO: split per abilities? + pub projectile_speed: f32, +} + +pub struct BowChargedModifiers { + pub damage_scaling: f32, + pub regen_scaling: f32, + pub knockback_scaling: f32, + pub charge_rate: f32, + pub move_speed: f32, +} + +pub struct BowRepeaterModifiers { + pub power: f32, + pub energy_cost: f32, + pub max_speed: f32, +} + +pub struct BowShotgunModifiers { + pub power: f32, + pub energy_cost: f32, + pub num_projectiles: u32, + pub spread: f32, +} + +impl BowTreeModifiers { + const fn get() -> Self { + Self { + universal: BowUniversalModifiers { + projectile_speed: 1.1, + }, + charged: BowChargedModifiers { + damage_scaling: 1.1, + regen_scaling: 1.1, + knockback_scaling: 1.1, + charge_rate: 1.1, + move_speed: 1.1, + }, + repeater: BowRepeaterModifiers { + power: 1.1, + energy_cost: 0.9, + max_speed: 1.2, + }, + shotgun: BowShotgunModifiers { + power: 1.1, + energy_cost: 0.9, + num_projectiles: 1, + spread: 0.9, + }, + } + } +} + +pub struct StaffTreeModifiers { + pub fireball: StaffFireballModifiers, + pub flamethrower: StaffFlamethrowerModifiers, + pub shockwave: StaffShockwaveModifiers, +} + +pub struct StaffFireballModifiers { + pub power: f32, + pub regen: f32, + pub range: f32, +} + +pub struct StaffFlamethrowerModifiers { + pub damage: f32, + pub range: f32, + pub energy_drain: f32, + pub velocity: f32, +} + +pub struct StaffShockwaveModifiers { + pub damage: f32, + pub knockback: f32, + pub duration: f32, + pub energy_cost: f32, +} + +impl StaffTreeModifiers { + const fn get() -> Self { + Self { + fireball: StaffFireballModifiers { + power: 1.1, + regen: 1.1, + range: 1.1, + }, + flamethrower: StaffFlamethrowerModifiers { + damage: 1.2, + range: 1.1, + energy_drain: 0.9, + velocity: 1.1, + }, + shockwave: StaffShockwaveModifiers { + damage: 1.15, + knockback: 1.15, + duration: 1.1, + energy_cost: 0.9, + }, + } + } +} + +pub struct SceptreTreeModifiers { + pub beam: SceptreBeamModifiers, + pub healing_aura: SceptreHealingAuraModifiers, + pub warding_aura: SceptreWardingAuraModifiers, +} + +pub struct SceptreBeamModifiers { + pub damage: f32, + pub range: f32, + pub energy_regen: f32, + pub lifesteal: f32, +} + +pub struct SceptreHealingAuraModifiers { + pub strength: f32, + pub duration: f32, + pub range: f32, + pub energy_cost: f32, +} + +pub struct SceptreWardingAuraModifiers { + pub strength: f32, + pub duration: f32, + pub range: f32, + pub energy_cost: f32, +} + +impl SceptreTreeModifiers { + const fn get() -> Self { + Self { + beam: SceptreBeamModifiers { + damage: 1.1, + range: 1.1, + energy_regen: 1.1, + lifesteal: 1.05, + }, + healing_aura: SceptreHealingAuraModifiers { + strength: 1.05, + duration: 1.1, + range: 1.1, + energy_cost: 0.90, + }, + warding_aura: SceptreWardingAuraModifiers { + strength: 1.05, + duration: 1.1, + range: 1.1, + energy_cost: 0.95, + }, + } + } +} + +pub struct MiningTreeModifiers { + pub speed: f32, + pub gem_gain: f32, + pub ore_gain: f32, +} + +impl MiningTreeModifiers { + const fn get() -> Self { + Self { + speed: 1.1, + gem_gain: 0.05, + ore_gain: 0.05, + } + } +} + +pub struct GeneralTreeModifiers { + pub roll: RollTreeModifiers, + pub swim: SwimTreeModifiers, + pub climb: ClimbTreeModifiers, +} + +pub struct RollTreeModifiers { + pub energy_cost: f32, + pub strength: f32, + pub duration: f32, +} + +pub struct SwimTreeModifiers { + pub speed: f32, +} + +pub struct ClimbTreeModifiers { + pub energy_cost: f32, + pub speed: f32, +} + +impl GeneralTreeModifiers { + const fn get() -> Self { + Self { + roll: RollTreeModifiers { + energy_cost: 0.95, + strength: 1.05, + duration: 1.05, + }, + swim: SwimTreeModifiers { speed: 1.25 }, + climb: ClimbTreeModifiers { + energy_cost: 0.8, + speed: 1.2, + }, + } + } +} diff --git a/common/src/outcome.rs b/common/src/outcome.rs index 51d87d54ce..b8974ff327 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -1,5 +1,5 @@ use crate::{comp, uid::Uid}; -use comp::{beam, item::Reagent, poise::PoiseState, skills::SkillGroupKind, UtteranceKind}; +use comp::{beam, item::Reagent, poise::PoiseState, skillset::SkillGroupKind, UtteranceKind}; use hashbrown::HashSet; use serde::{Deserialize, Serialize}; use vek::*; @@ -36,12 +36,12 @@ pub enum Outcome { }, ExpChange { uid: Uid, - exp: i32, + exp: u32, xp_pools: HashSet, }, SkillPointGain { uid: Uid, - skill_tree: comp::skills::SkillGroupKind, + skill_tree: comp::skillset::SkillGroupKind, total_points: u16, // TODO: Access ECS to get position from Uid to conserve bandwidth pos: Vec3, diff --git a/common/src/skillset_builder.rs b/common/src/skillset_builder.rs index 90282d912e..dec454c499 100644 --- a/common/src/skillset_builder.rs +++ b/common/src/skillset_builder.rs @@ -1,6 +1,6 @@ #![warn(clippy::pedantic)] //#![warn(clippy::nursery)] -use crate::comp::skills::{Skill, SkillGroupKind, SkillSet}; +use crate::comp::skillset::{skills::Skill, SkillGroupKind, SkillSet}; use crate::assets::{self, AssetExt}; use serde::{Deserialize, Serialize}; @@ -23,19 +23,19 @@ impl assets::Asset for SkillSetTree { #[derive(Debug, Deserialize, Clone)] enum SkillNode { Tree(String), - Skill((Skill, Option)), + Skill((Skill, u16)), Group(SkillGroupKind), } #[must_use] -fn skills_from_asset_expect(asset_specifier: &str) -> Vec<(Skill, Option)> { +fn skills_from_asset_expect(asset_specifier: &str) -> Vec<(Skill, u16)> { let nodes = SkillSetTree::load_expect(asset_specifier).read(); skills_from_nodes(&nodes.0) } #[must_use] -fn skills_from_nodes(nodes: &[SkillNode]) -> Vec<(Skill, Option)> { +fn skills_from_nodes(nodes: &[SkillNode]) -> Vec<(Skill, u16)> { let mut skills = Vec::new(); for node in nodes { match node { @@ -46,7 +46,7 @@ fn skills_from_nodes(nodes: &[SkillNode]) -> Vec<(Skill, Option)> { skills.push(*req); }, SkillNode::Group(group) => { - skills.push((Skill::UnlockGroup(*group), None)); + skills.push((Skill::UnlockGroup(*group), 1)); }, } } @@ -95,7 +95,7 @@ impl SkillSetBuilder { /// 1) If added skill doesn't have any group /// 2) If added skill already applied /// 3) If added skill wasn't applied at the end - pub fn with_skill(mut self, skill: Skill, level: Option) -> Self { + pub fn with_skill(mut self, skill: Skill, level: u16) -> Self { let group = if let Some(skill_group) = skill.skill_group_kind() { skill_group } else { @@ -114,9 +114,12 @@ impl SkillSetBuilder { ); common_base::dev_panic!(err, or return self); } - for _ in 0..level.unwrap_or(1) { + for _ in 0..level { skill_set.add_skill_points(group, skill_set.skill_cost(skill)); - skill_set.unlock_skill(skill); + if let Err(err) = skill_set.unlock_skill(skill) { + let err_msg = format!("Failed to add skill: {:?}. Error: {:?}", skill, err); + common_base::dev_panic!(err_msg); + } } if !skill_is_applied(skill_set, skill, level) { let err = format!( @@ -134,7 +137,7 @@ impl SkillSetBuilder { } #[must_use] -fn skill_is_applied(skill_set: &SkillSet, skill: Skill, level: Option) -> bool { +fn skill_is_applied(skill_set: &SkillSet, skill: Skill, level: u16) -> bool { if let Ok(applied_level) = skill_set.skill_level(skill) { applied_level == level } else { diff --git a/common/src/states/climb.rs b/common/src/states/climb.rs index c20875aa3f..0f4bbbb4c4 100644 --- a/common/src/states/climb.rs +++ b/common/src/states/climb.rs @@ -34,10 +34,10 @@ impl Data { pub fn create_adjusted_by_skills(join_data: &JoinData) -> Self { let modifiers = SKILL_MODIFIERS.general_tree.climb; let mut data = Data::default(); - if let Ok(Some(level)) = join_data.skill_set.skill_level(Skill::Climb(Cost)) { + if let Ok(level) = join_data.skill_set.skill_level(Skill::Climb(Cost)) { data.static_data.energy_cost *= modifiers.energy_cost.powi(level.into()); } - if let Ok(Some(level)) = join_data.skill_set.skill_level(Skill::Climb(Speed)) { + if let Ok(level) = join_data.skill_set.skill_level(Skill::Climb(Speed)) { data.static_data.movement_speed *= modifiers.speed.powi(level.into()); } data diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index c567d46769..2492a322a0 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -437,7 +437,7 @@ fn swim_move( let force = efficiency * force; let mut water_accel = force / data.mass.0; - if let Ok(Some(level)) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) { + if let Ok(level) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) { let modifiers = SKILL_MODIFIERS.general_tree.swim; water_accel *= modifiers.speed.powi(level.into()); } diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index 76536279bc..2b38d30698 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -7,12 +7,9 @@ use common::{ StatsModifier, }, event::{EventBus, ServerEvent}, - outcome::Outcome, resources::{DeltaTime, EntitiesDiedLastTick, Time}, - uid::Uid, }; use common_ecs::{Job, Origin, Phase, System}; -use hashbrown::HashSet; use specs::{ shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, Write, WriteStorage, }; @@ -28,7 +25,6 @@ pub struct ReadData<'a> { time: Read<'a, Time>, server_bus: Read<'a, EventBus>, positions: ReadStorage<'a, Pos>, - uids: ReadStorage<'a, Uid>, bodies: ReadStorage<'a, Body>, char_states: ReadStorage<'a, CharacterState>, inventories: ReadStorage<'a, Inventory>, @@ -48,7 +44,6 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Energy>, WriteStorage<'a, Combo>, Write<'a, EntitiesDiedLastTick>, - Write<'a, Vec>, ); const NAME: &'static str = "stats"; @@ -66,7 +61,6 @@ impl<'a> System<'a> for Sys { mut energies, mut combos, mut entities_died_last_tick, - mut outcomes, ): Self::SystemData, ) { entities_died_last_tick.0.clear(); @@ -74,11 +68,9 @@ impl<'a> System<'a> for Sys { let dt = read_data.dt.0; // Update stats - for (entity, uid, stats, mut skill_set, mut health, pos, mut energy, inventory) in ( + for (entity, stats, mut health, pos, mut energy, inventory) in ( &read_data.entities, - &read_data.uids, &stats, - &mut skill_sets, &mut healths, &read_data.positions, &mut energies, @@ -127,27 +119,6 @@ impl<'a> System<'a> for Sys { if change_energy { energy.update_maximum(energy_mods); } - - let skills_to_level = skill_set - .skill_groups - .iter() - .filter_map(|s_g| { - (s_g.exp >= skill_set.skill_point_cost(s_g.skill_group_kind)) - .then(|| s_g.skill_group_kind) - }) - .collect::>(); - - if !skills_to_level.is_empty() { - for skill_group in skills_to_level { - skill_set.earn_skill_point(skill_group); - outcomes.push(Outcome::SkillPointGain { - uid: *uid, - skill_tree: skill_group, - total_points: skill_set.earned_sp(skill_group), - pos: pos.0, - }); - } - } } // Apply effects from leveling skills @@ -162,7 +133,6 @@ impl<'a> System<'a> for Sys { if skill_set.modify_health { let health_level = skill_set .skill_level(Skill::General(GeneralSkill::HealthIncrease)) - .unwrap_or(None) .unwrap_or(0); health.update_max_hp(*body, health_level); skill_set.modify_health = false; @@ -170,7 +140,6 @@ impl<'a> System<'a> for Sys { if skill_set.modify_energy { let energy_level = skill_set .skill_level(Skill::General(GeneralSkill::EnergyIncrease)) - .unwrap_or(None) .unwrap_or(0); energy.update_max_energy(*body, energy_level); skill_set.modify_energy = false; diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 023f25f1fd..858bba1866 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -2854,8 +2854,8 @@ fn handle_skill_point( } } -fn parse_skill_tree(skill_tree: &str) -> CmdResult { - use comp::{item::tool::ToolKind, skills::SkillGroupKind}; +fn parse_skill_tree(skill_tree: &str) -> CmdResult { + use comp::{item::tool::ToolKind, skillset::SkillGroupKind}; match skill_tree { "general" => Ok(SkillGroupKind::General), "sword" => Ok(SkillGroupKind::Weapon(ToolKind::Sword)), @@ -3477,7 +3477,10 @@ fn set_skills(skill_set: &mut comp::SkillSet, preset: &str) -> CmdResult<()> { for _ in 0..*level { let cost = skill_set.skill_cost(*skill); skill_set.add_skill_points(group, cost); - skill_set.unlock_skill(*skill); + match skill_set.unlock_skill(*skill) { + Ok(_) | Err(comp::skillset::SkillUnlockError::SkillAlreadyUnlocked) => Ok(()), + Err(err) => Err(format!("{:?}", err)), + }?; } } Ok(()) diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index ec862c858e..67b4c2ce4c 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -2,7 +2,7 @@ use crate::{ client::Client, comp::{ agent::{Agent, AgentEvent, Sound, SoundKind}, - skills::SkillGroupKind, + skillset::SkillGroupKind, BuffKind, BuffSource, PhysicsState, }, rtsim::RtSim, @@ -303,7 +303,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt let contributor_exp = exp_reward * damage_percent; match damage_contributor { DamageContrib::Solo(attacker) => { - // No exp for self kills or PvP + // No exp for self kills or PvP if *attacker == entity || is_pvp_kill(*attacker) { return None; } // Only give EXP to the attacker if they are within EXP range of the killed entity @@ -353,16 +353,18 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt } }).flatten().for_each(|(attacker, exp_reward)| { // Process the calculated EXP rewards - if let (Some(mut attacker_skill_set), Some(attacker_uid), Some(attacker_inventory)) = ( + if let (Some(mut attacker_skill_set), Some(attacker_uid), Some(attacker_inventory), Some(pos)) = ( skill_sets.get_mut(attacker), uids.get(attacker), inventories.get(attacker), + positions.get(attacker), ) { handle_exp_gain( exp_reward, attacker_inventory, &mut attacker_skill_set, attacker_uid, + pos, &mut outcomes, ); } @@ -1087,6 +1089,7 @@ fn handle_exp_gain( inventory: &Inventory, skill_set: &mut SkillSet, uid: &Uid, + pos: &Pos, outcomes: &mut Vec, ) { use comp::inventory::{item::ItemKind, slot::EquipSlot}; @@ -1105,7 +1108,7 @@ fn handle_exp_gain( }); if let Some(weapon) = tool_kind { // Only adds to xp pools if entity has that skill group available - if skill_set.contains_skill_group(SkillGroupKind::Weapon(weapon)) { + if skill_set.skill_group_accessible(SkillGroupKind::Weapon(weapon)) { xp_pools.insert(SkillGroupKind::Weapon(weapon)); } } @@ -1117,11 +1120,20 @@ fn handle_exp_gain( add_tool_from_slot(EquipSlot::InactiveOffhand); let num_pools = xp_pools.len() as f32; for pool in xp_pools.iter() { - skill_set.change_experience(*pool, (exp_reward / num_pools).ceil() as i32); + if let Some(level_outcome) = + skill_set.add_experience(*pool, (exp_reward / num_pools).ceil() as u32) + { + outcomes.push(Outcome::SkillPointGain { + uid: *uid, + skill_tree: *pool, + total_points: level_outcome, + pos: pos.0, + }); + } } outcomes.push(Outcome::ExpChange { uid: *uid, - exp: exp_reward as i32, + exp: exp_reward as u32, xp_pools, }); } diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 5c41ad8b8a..d3d92106e9 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -301,7 +301,7 @@ fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<& } #[derive(Deserialize)] -struct ResourceExperienceManifest(HashMap); +struct ResourceExperienceManifest(HashMap); impl assets::Asset for ResourceExperienceManifest { type Loader = assets::RonLoader; @@ -339,15 +339,25 @@ pub fn handle_mine_block( .0 .get(item.item_definition_id()), ) { - skillset.change_experience(SkillGroupKind::Weapon(tool), *exp_reward); - state - .ecs() - .write_resource::>() - .push(Outcome::ExpChange { + let skill_group = SkillGroupKind::Weapon(tool); + let mut outcomes = state.ecs().write_resource::>(); + let positions = state.ecs().read_component::(); + if let (Some(level_outcome), Some(pos)) = ( + skillset.add_experience(skill_group, *exp_reward), + positions.get(entity), + ) { + outcomes.push(Outcome::SkillPointGain { uid, - exp: *exp_reward, - xp_pools: HashSet::from_iter(vec![SkillGroupKind::Weapon(tool)]), + skill_tree: skill_group, + total_points: level_outcome, + pos: pos.0, }); + } + outcomes.push(Outcome::ExpChange { + uid, + exp: *exp_reward, + xp_pools: HashSet::from_iter(vec![skill_group]), + }); } use common::comp::skills::{MiningSkill, Skill, SKILL_MODIFIERS}; use rand::Rng; @@ -355,15 +365,17 @@ pub fn handle_mine_block( let need_double_ore = |rng: &mut rand::rngs::ThreadRng| { let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.ore_gain); - let skill_level = - skillset.skill_level_or(Skill::Pick(MiningSkill::OreGain), 0); + let skill_level = skillset + .skill_level(Skill::Pick(MiningSkill::OreGain)) + .unwrap_or(0); rng.gen_bool(chance_mod * f64::from(skill_level)) }; let need_double_gem = |rng: &mut rand::rngs::ThreadRng| { let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.gem_gain); - let skill_level = - skillset.skill_level_or(Skill::Pick(MiningSkill::GemGain), 0); + let skill_level = skillset + .skill_level(Skill::Pick(MiningSkill::GemGain)) + .unwrap_or(0); rng.gen_bool(chance_mod * f64::from(skill_level)) }; diff --git a/server/src/migrations/V46__skill_persistence.sql b/server/src/migrations/V46__skill_persistence.sql new file mode 100644 index 0000000000..eaa50b2599 --- /dev/null +++ b/server/src/migrations/V46__skill_persistence.sql @@ -0,0 +1,61 @@ +-- Temp table relating earned_sp to the experience required to earn that amount +CREATE TEMP TABLE _sp_series +( + earned_sp INT NOT NULL, + exp INT NOT NULL +); + +-- Inserts exp values corresponding to the sp value +INSERT INTO _sp_series +WITH RECURSIVE sp_series(earned_sp, exp) AS ( + SELECT 0, 0 + UNION ALL + -- Function is the same as function in skillset/mod.rs in fn skill_point_cost as of time of this migration + -- though slightly modified to account for sqlite lacking functions for floor and exp + -- Floor modification is replacing floor(a) with round(a - 0.5) + -- Exp mofidication is replacing exp(-a) with 1 / (2^(a*1.442695)) where 1.442695 = log(e)/log(2) + -- Bit shifting is used to emulate 2^a, though unfortunately this does have some mild accuracy issues + SELECT earned_sp + 1, + exp + + CASE + WHEN earned_sp < 300 + THEN (10 * ROUND(((1000.0 / 10.0) / (1.0 + 1.0 / (1 << ROUND((0.125 * (earned_sp + 1) * 1.442695) - 0.1)) * (1000.0 / 70.0 - 1.0))) - 0.5)) + ELSE + 1000 + END + FROM sp_series + -- Only create table up to maximum value of earned_sp in database + WHERE earned_sp <= (SELECT MAX(earned_sp) FROM skill_group) +) +SELECT earned_sp, + exp +FROM sp_series; + +-- Update exp column with new values, add the leftover exp to this value +UPDATE skill_group +SET exp = skill_group.exp + (SELECT exp FROM _sp_series WHERE earned_sp = skill_group.earned_sp); + +CREATE TABLE _skill_group +( + entity_id INTEGER NOT NULL, + skill_group_kind TEXT NOT NULL, + earned_exp INTEGER NOT NULL, + spent_exp INTEGER NOT NULL, + skills TEXT NOT NULL, + hash_val BLOB NOT NULL, + FOREIGN KEY(entity_id) REFERENCES entity(entity_id), + PRIMARY KEY(entity_id,skill_group_kind) +); + +INSERT INTO _skill_group +SELECT sg.entity_id, sg.skill_group_kind, sg.exp, 0, "", x'0000000000000000000000000000000000000000000000000000000000000000' +FROM skill_group sg; + +-- Skills now tracked in skill_group table, can ust drop +DROP TABLE skill; +-- Table no longer needed +DROP TABLE skill_group; +-- Rename table to proper name +ALTER TABLE _skill_group RENAME TO skill_group; +-- Temp table no longer needed, drop it +DROP TABLE _sp_series; \ No newline at end of file diff --git a/server/src/persistence/character.rs b/server/src/persistence/character.rs index 7ce7c5e1bd..5668adcec5 100644 --- a/server/src/persistence/character.rs +++ b/server/src/persistence/character.rs @@ -16,8 +16,8 @@ use crate::{ convert_character_from_database, convert_inventory_from_database_items, convert_items_to_database_items, convert_loadout_from_database_items, convert_skill_groups_to_database, convert_skill_set_from_database, - convert_skills_to_database, convert_stats_from_database, - convert_waypoint_from_database_json, convert_waypoint_to_database_json, + convert_stats_from_database, convert_waypoint_from_database_json, + convert_waypoint_to_database_json, }, character_loader::{CharacterCreationResult, CharacterDataResult, CharacterListResult}, character_updater::PetPersistenceData, @@ -162,31 +162,13 @@ pub fn load_character_data( } }); - let mut stmt = connection.prepare_cached( - " - SELECT skill, - level - FROM skill - WHERE entity_id = ?1", - )?; - - let skill_data = stmt - .query_map(&[char_id], |row| { - Ok(Skill { - entity_id: char_id, - skill: row.get(0)?, - level: row.get(1)?, - }) - })? - .filter_map(Result::ok) - .collect::>(); - let mut stmt = connection.prepare_cached( " SELECT skill_group_kind, - exp, - available_sp, - earned_sp + earned_exp, + spent_exp, + skills, + hash_val FROM skill_group WHERE entity_id = ?1", )?; @@ -196,9 +178,10 @@ pub fn load_character_data( Ok(SkillGroup { entity_id: char_id, skill_group_kind: row.get(0)?, - exp: row.get(1)?, - available_sp: row.get(2)?, - earned_sp: row.get(3)?, + earned_exp: row.get(1)?, + spent_exp: row.get(2)?, + skills: row.get(3)?, + hash_val: row.get(4)?, }) })? .filter_map(Result::ok) @@ -254,7 +237,7 @@ pub fn load_character_data( Ok(( convert_body_from_database(&body_data.variant, &body_data.body_data)?, convert_stats_from_database(character_data.alias), - convert_skill_set_from_database(&skill_data, &skill_group_data), + convert_skill_set_from_database(&skill_group_data), convert_inventory_from_database_items( character_containers.inventory_container_id, &inventory_items, @@ -436,25 +419,27 @@ pub fn create_character( ])?; drop(stmt); - let db_skill_groups = convert_skill_groups_to_database(character_id, skill_set.skill_groups); + let db_skill_groups = convert_skill_groups_to_database(character_id, skill_set.skill_groups()); let mut stmt = transactionn.prepare_cached( " INSERT INTO skill_group (entity_id, skill_group_kind, - exp, - available_sp, - earned_sp) - VALUES (?1, ?2, ?3, ?4, ?5)", + earned_exp, + spent_exp, + skills, + hash_val) + VALUES (?1, ?2, ?3, ?4, ?5, ?6)", )?; for skill_group in db_skill_groups { stmt.execute(&[ &character_id as &dyn ToSql, &skill_group.skill_group_kind, - &skill_group.exp, - &skill_group.available_sp, - &skill_group.earned_sp, + &skill_group.earned_exp, + &skill_group.spent_exp, + &skill_group.skills, + &skill_group.hash_val, ])?; } drop(stmt); @@ -574,16 +559,6 @@ pub fn delete_character( "Requested character to delete does not belong to the requesting player".to_string(), )); } - // Delete skills - let mut stmt = transaction.prepare_cached( - " - DELETE - FROM skill - WHERE entity_id = ?1", - )?; - - stmt.execute(&[&char_id])?; - drop(stmt); // Delete skill groups let mut stmt = transaction.prepare_cached( @@ -1016,62 +991,31 @@ pub fn update( } } - let db_skill_groups = convert_skill_groups_to_database(char_id, char_skill_set.skill_groups); + let db_skill_groups = convert_skill_groups_to_database(char_id, char_skill_set.skill_groups()); let mut stmt = transaction.prepare_cached( " REPLACE INTO skill_group (entity_id, skill_group_kind, - exp, - available_sp, - earned_sp) - VALUES (?1, ?2, ?3, ?4, ?5)", + earned_exp, + spent_exp, + skills, + hash_val) + VALUES (?1, ?2, ?3, ?4, ?5, ?6)", )?; for skill_group in db_skill_groups { stmt.execute(&[ &skill_group.entity_id as &dyn ToSql, &skill_group.skill_group_kind, - &skill_group.exp, - &skill_group.available_sp, - &skill_group.earned_sp, + &skill_group.earned_exp, + &skill_group.spent_exp, + &skill_group.skills, + &skill_group.hash_val, ])?; } - let db_skills = convert_skills_to_database(char_id, char_skill_set.skills); - - let known_skills = Rc::new( - db_skills - .iter() - .map(|x| Value::from(x.skill.clone())) - .collect::>(), - ); - - let mut stmt = transaction.prepare_cached( - " - DELETE - FROM skill - WHERE entity_id = ?1 - AND skill NOT IN rarray(?2)", - )?; - - let delete_count = stmt.execute(&[&char_id as &dyn ToSql, &known_skills])?; - trace!("Deleted {} skills", delete_count); - - let mut stmt = transaction.prepare_cached( - " - REPLACE - INTO skill (entity_id, - skill, - level) - VALUES (?1, ?2, ?3)", - )?; - - for skill in db_skills { - stmt.execute(&[&skill.entity_id as &dyn ToSql, &skill.skill, &skill.level])?; - } - let db_waypoint = convert_waypoint_to_database_json(char_waypoint); let mut stmt = transaction.prepare_cached( diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index 4544c7da34..a962ea3ce8 100644 --- a/server/src/persistence/character/conversions.rs +++ b/server/src/persistence/character/conversions.rs @@ -1,6 +1,6 @@ use crate::persistence::{ character::EntityId, - models::{Character, Item, Skill, SkillGroup}, + models::{Character, Item, SkillGroup}, }; use crate::persistence::{ @@ -16,7 +16,7 @@ use common::{ loadout_builder::LoadoutBuilder, slot::InvSlotId, }, - skills, Body as CompBody, Waypoint, *, + skillset, Body as CompBody, Waypoint, *, }, resources::Time, }; @@ -24,7 +24,7 @@ use core::{convert::TryFrom, num::NonZeroU64}; use hashbrown::HashMap; use lazy_static::lazy_static; use std::{collections::VecDeque, str::FromStr, sync::Arc}; -use tracing::trace; +use tracing::{trace, warn}; #[derive(Debug)] pub struct ItemModelPair { @@ -408,6 +408,15 @@ pub fn convert_loadout_from_database_items( Ok(loadout) } +fn get_item_from_asset(item_definition_id: &str) -> Result { + common::comp::Item::new_from_asset(item_definition_id).map_err(|err| { + PersistenceError::AssetError(format!( + "Error loading item asset: {} - {}", + item_definition_id, err + )) + }) +} + /// Generates the code to deserialize a specific body variant from JSON macro_rules! deserialize_body { ($body_data:expr, $body_variant:tt, $body_type:tt) => {{ @@ -500,77 +509,96 @@ pub fn convert_stats_from_database(alias: String) -> common::comp::Stats { new_stats } -pub fn convert_skill_set_from_database( - skills: &[Skill], +pub fn convert_skill_set_from_database(skill_groups: &[SkillGroup]) -> common::comp::SkillSet { + let (skillless_skill_groups, deserialized_skills) = + convert_skill_groups_from_database(skill_groups); + common::comp::SkillSet::load_from_database(skillless_skill_groups, deserialized_skills) +} + +#[allow(clippy::type_complexity)] +fn convert_skill_groups_from_database( skill_groups: &[SkillGroup], -) -> common::comp::SkillSet { - skills::SkillSet { - skill_groups: convert_skill_groups_from_database(skill_groups), - skills: convert_skills_from_database(skills), - modify_health: true, - modify_energy: true, - } -} - -fn get_item_from_asset(item_definition_id: &str) -> Result { - common::comp::Item::new_from_asset(item_definition_id).map_err(|err| { - PersistenceError::AssetError(format!( - "Error loading item asset: {} - {}", - item_definition_id, err - )) - }) -} - -fn convert_skill_groups_from_database(skill_groups: &[SkillGroup]) -> Vec { - let mut new_skill_groups = Vec::new(); +) -> ( + // Skill groups in the vec do not contain skills, those are added later. The skill group only + // contains fields related to experience and skill points + HashMap, + // + HashMap, skillset::SkillsPersistenceError>>, +) { + let mut new_skill_groups = HashMap::new(); + let mut deserialized_skills = HashMap::new(); for skill_group in skill_groups.iter() { let skill_group_kind = json_models::db_string_to_skill_group(&skill_group.skill_group_kind); - let new_skill_group = skills::SkillGroup { + let mut new_skill_group = skillset::SkillGroup { skill_group_kind, - exp: skill_group.exp as u16, - available_sp: skill_group.available_sp as u16, - earned_sp: skill_group.earned_sp as u16, + // Available and earned exp and sp are reconstructed below + earned_exp: 0, + available_exp: 0, + available_sp: 0, + earned_sp: 0, + // Ordered skills empty here as skills get inserted later as they are unlocked, so long + // as there is not a respec. + ordered_skills: Vec::new(), }; - new_skill_groups.push(new_skill_group); + + // Add experience to skill group through method to ensure invariant of + // (earned_exp >= available_exp) are maintained + // Adding experience will automatically earn all possible skill points + let skill_group_exp = skill_group.earned_exp.clamp(0, i64::from(u32::MAX)) as u32; + new_skill_group.add_experience(skill_group_exp); + + use skillset::SkillsPersistenceError; + + let skills_result = if skill_group.spent_exp != i64::from(new_skill_group.spent_exp()) { + // If persisted spent exp does not equal the spent exp after reacquiring skill + // points, force a respec + Err(SkillsPersistenceError::SpentExpMismatch) + } else if Some(&skill_group.hash_val) != skillset::SKILL_GROUP_HASHES.get(&skill_group_kind) + { + // Else if persisted hash for skill group does not match current hash for skill + // group, force a respec + Err(SkillsPersistenceError::HashMismatch) + } else { + // Else attempt to deserialize skills from a json string + match serde_json::from_str::>(&skill_group.skills) { + // If it correctly deserializes, return the persisted skills + Ok(skills) => Ok(skills), + // Else if doesn't deserialize correctly, force a respec + Err(err) => { + warn!( + "Skills failed to correctly deserialized\nError: {:#?}\nRaw JSON: {:#?}", + err, &skill_group.skills + ); + Err(SkillsPersistenceError::DeserializationFailure) + }, + } + }; + + deserialized_skills.insert(skill_group_kind, skills_result); + + new_skill_groups.insert(skill_group_kind, new_skill_group); } - new_skill_groups + (new_skill_groups, deserialized_skills) } -fn convert_skills_from_database(skills: &[Skill]) -> HashMap> { - let mut new_skills = HashMap::new(); - for skill in skills.iter() { - let new_skill = json_models::db_string_to_skill(&skill.skill); - new_skills.insert(new_skill, skill.level.map(|l| l as u16)); - } - new_skills -} - -pub fn convert_skill_groups_to_database( +pub fn convert_skill_groups_to_database<'a, I: Iterator>( entity_id: CharacterId, - skill_groups: Vec, + skill_groups: I, ) -> Vec { + let skill_group_hashes = &skillset::SKILL_GROUP_HASHES; skill_groups .into_iter() .map(|sg| SkillGroup { entity_id, skill_group_kind: json_models::skill_group_to_db_string(sg.skill_group_kind), - exp: sg.exp as i32, - available_sp: sg.available_sp as i32, - earned_sp: sg.earned_sp as i32, - }) - .collect() -} - -pub fn convert_skills_to_database( - entity_id: CharacterId, - skills: HashMap>, -) -> Vec { - skills - .iter() - .map(|(s, l)| Skill { - entity_id, - skill: json_models::skill_to_db_string(*s), - level: l.map(|l| l as i32), + earned_exp: i64::from(sg.earned_exp), + spent_exp: i64::from(sg.spent_exp()), + // If fails to convert, just forces a respec on next login + skills: serde_json::to_string(&sg.ordered_skills).unwrap_or_else(|_| "".to_string()), + hash_val: skill_group_hashes + .get(&sg.skill_group_kind) + .cloned() + .unwrap_or_default(), }) .collect() } diff --git a/server/src/persistence/json_models.rs b/server/src/persistence/json_models.rs index fe59dd22f0..3418e87a4b 100644 --- a/server/src/persistence/json_models.rs +++ b/server/src/persistence/json_models.rs @@ -63,250 +63,8 @@ pub struct CharacterPosition { pub waypoint: Vec3, } -pub fn skill_to_db_string(skill: comp::skills::Skill) -> String { - use comp::{ - item::tool::ToolKind, - skills::{ - AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, MiningSkill, RollSkill, - SceptreSkill, Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, - }, - }; - let skill_string = match skill { - General(GeneralSkill::HealthIncrease) => "General HealthIncrease", - General(GeneralSkill::EnergyIncrease) => "General EnergyIncrease", - Sword(SwordSkill::InterruptingAttacks) => "Sword InterruptingAttacks", - Sword(SwordSkill::TsCombo) => "Sword TsCombo", - Sword(SwordSkill::TsDamage) => "Sword TsDamage", - Sword(SwordSkill::TsRegen) => "Sword TsRegen", - Sword(SwordSkill::TsSpeed) => "Sword TsSpeed", - Sword(SwordSkill::DCost) => "Sword DCost", - Sword(SwordSkill::DDrain) => "Sword DDrain", - Sword(SwordSkill::DDamage) => "Sword DDamage", - Sword(SwordSkill::DScaling) => "Sword DScaling", - Sword(SwordSkill::DSpeed) => "Sword DSpeed", - Sword(SwordSkill::DInfinite) => "Sword DInfinite", - Sword(SwordSkill::UnlockSpin) => "Sword UnlockSpin", - Sword(SwordSkill::SDamage) => "Sword SDamage", - Sword(SwordSkill::SSpeed) => "Sword SSpeed", - Sword(SwordSkill::SCost) => "Sword SCost", - Sword(SwordSkill::SSpins) => "Sword SSpins", - Axe(AxeSkill::DsCombo) => "Axe DsCombo", - Axe(AxeSkill::DsDamage) => "Axe DsDamage", - Axe(AxeSkill::DsSpeed) => "Axe DsSpeed", - Axe(AxeSkill::DsRegen) => "Axe DsRegen", - Axe(AxeSkill::SInfinite) => "Axe SInfinite", - Axe(AxeSkill::SHelicopter) => "Axe SHelicopter", - Axe(AxeSkill::SDamage) => "Axe SDamage", - Axe(AxeSkill::SSpeed) => "Axe SSpeed", - Axe(AxeSkill::SCost) => "Axe SCost", - Axe(AxeSkill::UnlockLeap) => "Axe UnlockLeap", - Axe(AxeSkill::LDamage) => "Axe LDamage", - Axe(AxeSkill::LKnockback) => "Axe LKnockback", - Axe(AxeSkill::LCost) => "Axe LCost", - Axe(AxeSkill::LDistance) => "Axe LDistance", - Hammer(HammerSkill::SsKnockback) => "Hammer SsKnockback", - Hammer(HammerSkill::SsDamage) => "Hammer SsDamage", - Hammer(HammerSkill::SsSpeed) => "Hammer SsSpeed", - Hammer(HammerSkill::SsRegen) => "Hammer SsRegen", - Hammer(HammerSkill::CDamage) => "Hammer CDamage", - Hammer(HammerSkill::CKnockback) => "Hammer CKnockback", - Hammer(HammerSkill::CDrain) => "Hammer CDrain", - Hammer(HammerSkill::CSpeed) => "Hammer CSpeed", - Hammer(HammerSkill::UnlockLeap) => "Hammer UnlockLeap", - Hammer(HammerSkill::LDamage) => "Hammer LDamage", - Hammer(HammerSkill::LCost) => "Hammer LCost", - Hammer(HammerSkill::LDistance) => "Hammer LDistance", - Hammer(HammerSkill::LKnockback) => "Hammer LKnockback", - Hammer(HammerSkill::LRange) => "Hammer LRange", - Bow(BowSkill::ProjSpeed) => "Bow ProjSpeed", - Bow(BowSkill::CDamage) => "Bow CDamage", - Bow(BowSkill::CRegen) => "Bow CRegen", - Bow(BowSkill::CKnockback) => "Bow CKnockback", - Bow(BowSkill::CSpeed) => "Bow CSpeed", - Bow(BowSkill::CMove) => "Bow CMove", - Bow(BowSkill::RDamage) => "Bow RDamage", - Bow(BowSkill::RCost) => "Bow RCost", - Bow(BowSkill::RSpeed) => "Bow RSpeed", - Bow(BowSkill::UnlockShotgun) => "Bow UnlockShotgun", - Bow(BowSkill::SDamage) => "Bow SDamage", - Bow(BowSkill::SCost) => "Bow SCost", - Bow(BowSkill::SArrows) => "Bow SArrows", - Bow(BowSkill::SSpread) => "Bow SSpread", - Staff(StaffSkill::BDamage) => "Staff BDamage", - Staff(StaffSkill::BRegen) => "Staff BRegen", - Staff(StaffSkill::BRadius) => "Staff BRadius", - Staff(StaffSkill::FDamage) => "Staff FDamage", - Staff(StaffSkill::FRange) => "Staff FRange", - Staff(StaffSkill::FDrain) => "Staff FDrain", - Staff(StaffSkill::FVelocity) => "Staff FVelocity", - Staff(StaffSkill::UnlockShockwave) => "Staff UnlockShockwave", - Staff(StaffSkill::SDamage) => "Staff SDamage", - Staff(StaffSkill::SKnockback) => "Staff SKnockback", - Staff(StaffSkill::SRange) => "Staff SRange", - Staff(StaffSkill::SCost) => "Staff SCost", - Sceptre(SceptreSkill::LDamage) => "Sceptre LDamage", - Sceptre(SceptreSkill::LRange) => "Sceptre LRange", - Sceptre(SceptreSkill::LLifesteal) => "Sceptre LLifesteal", - Sceptre(SceptreSkill::LRegen) => "Sceptre LRegen", - Sceptre(SceptreSkill::HHeal) => "Sceptre HHeal", - Sceptre(SceptreSkill::HDuration) => "Sceptre HDuration", - Sceptre(SceptreSkill::HRange) => "Sceptre HRange", - Sceptre(SceptreSkill::HCost) => "Sceptre HCost", - Sceptre(SceptreSkill::UnlockAura) => "Sceptre UnlockAura", - Sceptre(SceptreSkill::AStrength) => "Sceptre AStrength", - Sceptre(SceptreSkill::ADuration) => "Sceptre ADuration", - Sceptre(SceptreSkill::ARange) => "Sceptre ARange", - Sceptre(SceptreSkill::ACost) => "Sceptre ACost", - Roll(RollSkill::Cost) => "Roll Cost", - Roll(RollSkill::Strength) => "Roll Strength", - Roll(RollSkill::Duration) => "Roll Duration", - Climb(ClimbSkill::Cost) => "Climb Cost", - Climb(ClimbSkill::Speed) => "Climb Speed", - Swim(SwimSkill::Speed) => "Swim Speed", - Pick(MiningSkill::Speed) => "Pick Speed", - Pick(MiningSkill::OreGain) => "Pick OreGain", - Pick(MiningSkill::GemGain) => "Pick GemGain", - UnlockGroup(SkillGroupKind::Weapon(ToolKind::Sword)) => "Unlock Weapon Sword", - UnlockGroup(SkillGroupKind::Weapon(ToolKind::Axe)) => "Unlock Weapon Axe", - UnlockGroup(SkillGroupKind::Weapon(ToolKind::Hammer)) => "Unlock Weapon Hammer", - UnlockGroup(SkillGroupKind::Weapon(ToolKind::Bow)) => "Unlock Weapon Bow", - UnlockGroup(SkillGroupKind::Weapon(ToolKind::Staff)) => "Unlock Weapon Staff", - UnlockGroup(SkillGroupKind::Weapon(ToolKind::Sceptre)) => "Unlock Weapon Sceptre", - UnlockGroup(SkillGroupKind::Weapon(ToolKind::Dagger)) - | UnlockGroup(SkillGroupKind::Weapon(ToolKind::Shield)) - | UnlockGroup(SkillGroupKind::Weapon(ToolKind::Spear)) - | UnlockGroup(SkillGroupKind::Weapon(ToolKind::Debug)) - | UnlockGroup(SkillGroupKind::Weapon(ToolKind::Farming)) - | UnlockGroup(SkillGroupKind::Weapon(ToolKind::Pick)) - | UnlockGroup(SkillGroupKind::Weapon(ToolKind::Empty)) - | UnlockGroup(SkillGroupKind::Weapon(ToolKind::Natural)) - | UnlockGroup(SkillGroupKind::General) => { - panic!("Tried to add unsupported skill to database: {:?}", skill) - }, - }; - skill_string.to_string() -} - -pub fn db_string_to_skill(skill_string: &str) -> comp::skills::Skill { - use comp::{ - item::tool::ToolKind, - skills::{ - AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, MiningSkill, RollSkill, - SceptreSkill, Skill::*, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, - }, - }; - match skill_string { - "General HealthIncrease" => General(GeneralSkill::HealthIncrease), - "General EnergyIncrease" => General(GeneralSkill::EnergyIncrease), - "Sword InterruptingAttacks" => Sword(SwordSkill::InterruptingAttacks), - "Sword TsCombo" => Sword(SwordSkill::TsCombo), - "Sword TsDamage" => Sword(SwordSkill::TsDamage), - "Sword TsRegen" => Sword(SwordSkill::TsRegen), - "Sword TsSpeed" => Sword(SwordSkill::TsSpeed), - "Sword DCost" => Sword(SwordSkill::DCost), - "Sword DDrain" => Sword(SwordSkill::DDrain), - "Sword DDamage" => Sword(SwordSkill::DDamage), - "Sword DScaling" => Sword(SwordSkill::DScaling), - "Sword DSpeed" => Sword(SwordSkill::DSpeed), - "Sword DInfinite" => Sword(SwordSkill::DInfinite), - "Sword UnlockSpin" => Sword(SwordSkill::UnlockSpin), - "Sword SDamage" => Sword(SwordSkill::SDamage), - "Sword SSpeed" => Sword(SwordSkill::SSpeed), - "Sword SCost" => Sword(SwordSkill::SCost), - "Sword SSpins" => Sword(SwordSkill::SSpins), - "Axe DsCombo" => Axe(AxeSkill::DsCombo), - "Axe DsDamage" => Axe(AxeSkill::DsDamage), - "Axe DsSpeed" => Axe(AxeSkill::DsSpeed), - "Axe DsRegen" => Axe(AxeSkill::DsRegen), - "Axe SInfinite" => Axe(AxeSkill::SInfinite), - "Axe SHelicopter" => Axe(AxeSkill::SHelicopter), - "Axe SDamage" => Axe(AxeSkill::SDamage), - "Axe SSpeed" => Axe(AxeSkill::SSpeed), - "Axe SCost" => Axe(AxeSkill::SCost), - "Axe UnlockLeap" => Axe(AxeSkill::UnlockLeap), - "Axe LDamage" => Axe(AxeSkill::LDamage), - "Axe LKnockback" => Axe(AxeSkill::LKnockback), - "Axe LCost" => Axe(AxeSkill::LCost), - "Axe LDistance" => Axe(AxeSkill::LDistance), - "Hammer SsKnockback" => Hammer(HammerSkill::SsKnockback), - "Hammer SsDamage" => Hammer(HammerSkill::SsDamage), - "Hammer SsSpeed" => Hammer(HammerSkill::SsSpeed), - "Hammer SsRegen" => Hammer(HammerSkill::SsRegen), - "Hammer CDamage" => Hammer(HammerSkill::CDamage), - "Hammer CKnockback" => Hammer(HammerSkill::CKnockback), - "Hammer CDrain" => Hammer(HammerSkill::CDrain), - "Hammer CSpeed" => Hammer(HammerSkill::CSpeed), - "Hammer UnlockLeap" => Hammer(HammerSkill::UnlockLeap), - "Hammer LDamage" => Hammer(HammerSkill::LDamage), - "Hammer LCost" => Hammer(HammerSkill::LCost), - "Hammer LDistance" => Hammer(HammerSkill::LDistance), - "Hammer LKnockback" => Hammer(HammerSkill::LKnockback), - "Hammer LRange" => Hammer(HammerSkill::LRange), - "Bow ProjSpeed" => Bow(BowSkill::ProjSpeed), - "Bow CDamage" => Bow(BowSkill::CDamage), - "Bow CRegen" => Bow(BowSkill::CRegen), - "Bow CKnockback" => Bow(BowSkill::CKnockback), - "Bow CSpeed" => Bow(BowSkill::CSpeed), - "Bow CMove" => Bow(BowSkill::CMove), - "Bow RDamage" => Bow(BowSkill::RDamage), - "Bow RCost" => Bow(BowSkill::RCost), - "Bow RSpeed" => Bow(BowSkill::RSpeed), - "Bow UnlockShotgun" => Bow(BowSkill::UnlockShotgun), - "Bow SDamage" => Bow(BowSkill::SDamage), - "Bow SCost" => Bow(BowSkill::SCost), - "Bow SArrows" => Bow(BowSkill::SArrows), - "Bow SSpread" => Bow(BowSkill::SSpread), - "Staff BDamage" => Staff(StaffSkill::BDamage), - "Staff BRegen" => Staff(StaffSkill::BRegen), - "Staff BRadius" => Staff(StaffSkill::BRadius), - "Staff FDamage" => Staff(StaffSkill::FDamage), - "Staff FRange" => Staff(StaffSkill::FRange), - "Staff FDrain" => Staff(StaffSkill::FDrain), - "Staff FVelocity" => Staff(StaffSkill::FVelocity), - "Staff UnlockShockwave" => Staff(StaffSkill::UnlockShockwave), - "Staff SDamage" => Staff(StaffSkill::SDamage), - "Staff SKnockback" => Staff(StaffSkill::SKnockback), - "Staff SRange" => Staff(StaffSkill::SRange), - "Staff SCost" => Staff(StaffSkill::SCost), - "Sceptre LDamage" => Sceptre(SceptreSkill::LDamage), - "Sceptre LRange" => Sceptre(SceptreSkill::LRange), - "Sceptre LLifesteal" => Sceptre(SceptreSkill::LLifesteal), - "Sceptre LRegen" => Sceptre(SceptreSkill::LRegen), - "Sceptre HHeal" => Sceptre(SceptreSkill::HHeal), - "Sceptre HDuration" => Sceptre(SceptreSkill::HDuration), - "Sceptre HRange" => Sceptre(SceptreSkill::HRange), - "Sceptre HCost" => Sceptre(SceptreSkill::HCost), - "Sceptre UnlockAura" => Sceptre(SceptreSkill::UnlockAura), - "Sceptre AStrength" => Sceptre(SceptreSkill::AStrength), - "Sceptre ADuration" => Sceptre(SceptreSkill::ADuration), - "Sceptre ARange" => Sceptre(SceptreSkill::ARange), - "Sceptre ACost" => Sceptre(SceptreSkill::ACost), - "Roll Cost" => Roll(RollSkill::Cost), - "Roll Strength" => Roll(RollSkill::Strength), - "Roll Duration" => Roll(RollSkill::Duration), - "Climb Cost" => Climb(ClimbSkill::Cost), - "Climb Speed" => Climb(ClimbSkill::Speed), - "Swim Speed" => Swim(SwimSkill::Speed), - "Pick Speed" => Pick(MiningSkill::Speed), - "Pick GemGain" => Pick(MiningSkill::GemGain), - "Pick OreGain" => Pick(MiningSkill::OreGain), - "Unlock Weapon Sword" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Sword)), - "Unlock Weapon Axe" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Axe)), - "Unlock Weapon Hammer" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Hammer)), - "Unlock Weapon Bow" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Bow)), - "Unlock Weapon Staff" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Staff)), - "Unlock Weapon Sceptre" => UnlockGroup(SkillGroupKind::Weapon(ToolKind::Sceptre)), - _ => { - panic!( - "Tried to convert an unsupported string from the database: {}", - skill_string - ) - }, - } -} - -pub fn skill_group_to_db_string(skill_group: comp::skills::SkillGroupKind) -> String { - use comp::{item::tool::ToolKind, skills::SkillGroupKind::*}; +pub fn skill_group_to_db_string(skill_group: comp::skillset::SkillGroupKind) -> String { + use comp::{item::tool::ToolKind, skillset::SkillGroupKind::*}; let skill_group_string = match skill_group { General => "General", Weapon(ToolKind::Sword) => "Weapon Sword", @@ -330,8 +88,8 @@ pub fn skill_group_to_db_string(skill_group: comp::skills::SkillGroupKind) -> St skill_group_string.to_string() } -pub fn db_string_to_skill_group(skill_group_string: &str) -> comp::skills::SkillGroupKind { - use comp::{item::tool::ToolKind, skills::SkillGroupKind::*}; +pub fn db_string_to_skill_group(skill_group_string: &str) -> comp::skillset::SkillGroupKind { + use comp::{item::tool::ToolKind, skillset::SkillGroupKind::*}; match skill_group_string { "General" => General, "Weapon Sword" => Weapon(ToolKind::Sword), diff --git a/server/src/persistence/models.rs b/server/src/persistence/models.rs index a2ce2d18dc..42d6468af7 100644 --- a/server/src/persistence/models.rs +++ b/server/src/persistence/models.rs @@ -20,18 +20,13 @@ pub struct Body { pub body_data: String, } -pub struct Skill { - pub entity_id: i64, - pub skill: String, - pub level: Option, -} - pub struct SkillGroup { pub entity_id: i64, pub skill_group_kind: String, - pub exp: i32, - pub available_sp: i32, - pub earned_sp: i32, + pub earned_exp: i64, + pub spent_exp: i64, + pub skills: String, + pub hash_val: Vec, } pub struct Pet { diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 336c70d755..57df0dc189 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -221,7 +221,6 @@ impl StateExt for State { body, skill_set .skill_level(Skill::General(GeneralSkill::EnergyIncrease)) - .unwrap_or(None) .unwrap_or(0), )) .with(stats) @@ -508,11 +507,9 @@ impl StateExt for State { let (health_level, energy_level) = ( skill_set .skill_level(Skill::General(GeneralSkill::HealthIncrease)) - .unwrap_or(None) .unwrap_or(0), skill_set .skill_level(Skill::General(GeneralSkill::EnergyIncrease)) - .unwrap_or(None) .unwrap_or(0), ); self.write_component_ignore_entity_dead(entity, comp::Health::new(body, health_level)); diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index ffd3986882..e38dd9c6aa 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -250,11 +250,6 @@ impl Sys { .get_mut(entity) .map(|mut skill_set| skill_set.unlock_skill(skill)); }, - ClientGeneral::RefundSkill(skill) => { - skill_sets - .get_mut(entity) - .map(|mut skill_set| skill_set.refund_skill(skill)); - }, ClientGeneral::UnlockSkillGroup(skill_group_kind) => { skill_sets .get_mut(entity) @@ -281,6 +276,11 @@ impl Sys { } => { presence.lossy_terrain_compression = lossy_terrain_compression; }, + ClientGeneral::AcknowledgePersistenceLoadError => { + skill_sets + .get_mut(entity) + .map(|mut skill_set| skill_set.persistence_load_error = None); + }, ClientGeneral::RequestCharacterList | ClientGeneral::CreateCharacter { .. } | ClientGeneral::EditCharacter { .. } diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index e00c598d50..60946f1ee1 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -21,10 +21,9 @@ use common::{ item::tool::ToolKind, skills::{ self, AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, MiningSkill, - RollSkill, SceptreSkill, Skill, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, - SKILL_MODIFIERS, + RollSkill, SceptreSkill, Skill, StaffSkill, SwimSkill, SwordSkill, SKILL_MODIFIERS, }, - SkillSet, + skillset::{SkillGroupKind, SkillSet}, }, consts::{ENERGY_PER_LEVEL, HUMANOID_HP_PER_LEVEL}, }; @@ -242,7 +241,7 @@ impl<'a> Diary<'a> { } } -pub type SelectedSkillTree = skills::SkillGroupKind; +pub type SelectedSkillTree = SkillGroupKind; // TODO: make it enum? const TREES: [&str; 8] = [ @@ -371,7 +370,7 @@ impl<'a> Widget for Diary<'a> { }; // Check if we have this skill tree unlocked - let locked = !self.skill_set.contains_skill_group(skill_group); + let locked = !self.skill_set.skill_group_accessible(skill_group); // Weapon button image let btn_img = { @@ -464,7 +463,7 @@ impl<'a> Widget for Diary<'a> { } // Exp Bars and Rank Display - let current_exp = self.skill_set.experience(*sel_tab) as f64; + let current_exp = self.skill_set.available_experience(*sel_tab) as f64; let max_exp = self.skill_set.skill_point_cost(*sel_tab) as f64; let exp_percentage = current_exp / max_exp; let rank = self.skill_set.earned_sp(*sel_tab); @@ -642,7 +641,8 @@ impl<'a> Diary<'a> { skills_bot_l, skills_bot_r, ); - use skills::{GeneralSkill::*, RollSkill::*, SkillGroupKind::*}; + use skills::{GeneralSkill::*, RollSkill::*}; + use SkillGroupKind::*; use ToolKind::*; // General Combat Image::new(animate_by_pulse( @@ -903,7 +903,7 @@ impl<'a> Diary<'a> { id: state.skill_sword_dash_4, }, SkillIcon::Unlockable { - skill: Skill::Sword(DInfinite), + skill: Skill::Sword(DChargeThrough), image: self.imgs.physical_distance_skill, position: MidTopWithMarginOn(state.skills_top_r[5], 3.0), id: state.skill_sword_dash_5, @@ -1996,11 +1996,8 @@ impl<'a> Diary<'a> { diary_tooltip: &Tooltip, ) { let label = if self.skill_set.prerequisites_met(skill) { - let current = self - .skill_set - .skill_level(skill) - .map_or(0, |l| l.unwrap_or(1)); - let max = skill.max_level().unwrap_or(1); + let current = self.skill_set.skill_level(skill).unwrap_or(0); + let max = skill.max_level(); format!("{}/{}", current, max) } else { "".to_owned() @@ -2195,7 +2192,7 @@ fn sword_skill_strings(skill: SwordSkill, i18n: &Localization) -> (&str, Cow localize( + SwordSkill::DChargeThrough => localize( i18n, "hud.skill.sw_dash_charge_through_title", "hud.skill.sw_dash_charge_through", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index f2c42d0937..97e077b6f1 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -77,7 +77,7 @@ use common::{ self, fluid_dynamics, inventory::{slot::InvSlotId, trade_pricing::TradePricing}, item::{tool::ToolKind, ItemDesc, MaterialStatManifest, Quality}, - skills::{Skill, SkillGroupKind}, + skillset::{skills::Skill, SkillGroupKind}, BuffData, BuffKind, Item, }, consts::MAX_PICKUP_RANGE, @@ -109,6 +109,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; +use tracing::warn; use vek::*; const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); @@ -435,7 +436,7 @@ pub struct BuffInfo { pub struct ExpFloater { pub owner: Uid, - pub exp_change: i32, + pub exp_change: u32, pub timer: f32, pub rand_offset: (f32, f32), pub xp_pools: HashSet, @@ -538,6 +539,7 @@ pub enum Event { ChangeAbility(usize, comp::ability::AuxiliaryAbility), SettingsChange(SettingsChange), + AcknowledgePersistenceLoadError, } // TODO: Are these the possible layouts we want? @@ -888,6 +890,7 @@ impl Show { pub struct PromptDialogSettings { message: String, affirmative_event: Event, + negative_option: bool, negative_event: Option, outcome_via_keypress: Option, } @@ -897,6 +900,7 @@ impl PromptDialogSettings { Self { message, affirmative_event, + negative_option: true, negative_event, outcome_via_keypress: None, } @@ -905,6 +909,12 @@ impl PromptDialogSettings { pub fn set_outcome_via_keypress(&mut self, outcome: bool) { self.outcome_via_keypress = Some(outcome); } + + #[must_use] + pub fn with_no_negative_option(mut self) -> Self { + self.negative_option = false; + self + } } pub struct Floaters { @@ -1129,6 +1139,47 @@ impl Hud { let me = client.entity(); let poises = ecs.read_storage::(); + // Check if there was a persistence load error of the skillset, and if so + // display a dialog prompt + if self.show.prompt_dialog.is_none() { + if let Some(skill_set) = skill_sets.get(me) { + if let Some(persistence_error) = skill_set.persistence_load_error { + use comp::skillset::SkillsPersistenceError; + let persistence_error = match persistence_error { + SkillsPersistenceError::HashMismatch => { + "There was a difference detected in one of your skill groups since \ + you last played." + }, + SkillsPersistenceError::DeserializationFailure => { + "There was a error in loading some of your skills from the \ + database." + }, + SkillsPersistenceError::SpentExpMismatch => { + "The amount of free experience you had in one of your skill groups \ + differed from when you last played." + }, + SkillsPersistenceError::SkillsUnlockFailed => { + "Your skills were not able to be obtained in the same order you \ + acquired them. Prerequisites or costs may have changed." + }, + }; + + let common_message = "Some of your skill points have been reset. You will \ + need to reassign them."; + + warn!("{}\n{}", persistence_error, common_message); + let prompt_dialog = PromptDialogSettings::new( + format!("{}\n", common_message), + Event::AcknowledgePersistenceLoadError, + None, + ) + .with_no_negative_option(); + // self.set_prompt_dialog(prompt_dialog); + self.show.prompt_dialog = Some(prompt_dialog); + } + } + } + if (client.pending_trade().is_some() && !self.show.trade) || (client.pending_trade().is_none() && self.show.trade) { diff --git a/voxygen/src/hud/prompt_dialog.rs b/voxygen/src/hud/prompt_dialog.rs index 64a66a91d6..a9309ff741 100644 --- a/voxygen/src/hud/prompt_dialog.rs +++ b/voxygen/src/hud/prompt_dialog.rs @@ -143,42 +143,49 @@ impl<'a> Widget for PromptDialog<'a> { self.prompt_dialog_settings.affirmative_event.clone(), )); } - Text::new("Accept") - .bottom_right_with_margins_on(state.ids.accept_key, 5.0, -65.0) + let accept_txt = if self.prompt_dialog_settings.negative_option { + "Accept" + } else { + "Ok" + }; + Text::new(accept_txt) + .bottom_left_with_margins_on(state.ids.accept_key, 4.0, 28.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(18)) .color(TEXT_COLOR) .set(state.ids.accept_txt, ui); - if Button::image(self.imgs.key_button) - .w_h(20.0, 20.0) - .hover_image(self.imgs.close_btn_hover) - .press_image(self.imgs.close_btn_press) - .label(&decline_key) - .image_color(UI_HIGHLIGHT_0) - .label_color(TEXT_COLOR) - .label_font_size(self.fonts.cyri.scale(16)) - .label_font_id(self.fonts.cyri.conrod_id) - .label_y(conrod_core::position::Relative::Scalar(2.5)) - .label_x(conrod_core::position::Relative::Scalar(0.5)) - .bottom_right_with_margins_on(state.ids.bot, 4.0, 6.0) - .set(state.ids.decline_key, ui) - .was_clicked() - || self - .prompt_dialog_settings - .outcome_via_keypress - .map_or(false, |outcome| !outcome) - { - event = Some(DialogOutcomeEvent::Negative( - self.prompt_dialog_settings.negative_event.as_ref().cloned(), - )); + if self.prompt_dialog_settings.negative_option { + if Button::image(self.imgs.key_button) + .w_h(20.0, 20.0) + .hover_image(self.imgs.close_btn_hover) + .press_image(self.imgs.close_btn_press) + .label(&decline_key) + .image_color(UI_HIGHLIGHT_0) + .label_color(TEXT_COLOR) + .label_font_size(self.fonts.cyri.scale(16)) + .label_font_id(self.fonts.cyri.conrod_id) + .label_y(conrod_core::position::Relative::Scalar(2.5)) + .label_x(conrod_core::position::Relative::Scalar(0.5)) + .bottom_right_with_margins_on(state.ids.bot, 4.0, 6.0) + .set(state.ids.decline_key, ui) + .was_clicked() + || self + .prompt_dialog_settings + .outcome_via_keypress + .map_or(false, |outcome| !outcome) + { + event = Some(DialogOutcomeEvent::Negative( + self.prompt_dialog_settings.negative_event.as_ref().cloned(), + )); + } + Text::new("Decline") + .bottom_left_with_margins_on(state.ids.decline_key, 4.0, -65.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(18)) + .color(TEXT_COLOR) + .set(state.ids.decline_txt, ui); } - Text::new("Decline") - .bottom_left_with_margins_on(state.ids.decline_key, 4.0, -65.0) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(18)) - .color(TEXT_COLOR) - .set(state.ids.decline_txt, ui); // Prompt Description Text::new(&self.prompt_dialog_settings.message) diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 29553d520e..fba8c75c5b 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1428,6 +1428,11 @@ impl PlayState for SessionState { HudEvent::SettingsChange(settings_change) => { settings_change.process(global_state, self); }, + HudEvent::AcknowledgePersistenceLoadError => { + self.client + .borrow_mut() + .acknolwedge_persistence_load_error(); + }, } }