Merge branch 'sam/skill-persistence-fixes' into 'master'

Skill persistence fixes

See merge request veloren/veloren!3006
This commit is contained in:
Samuel Keiffer 2022-01-04 01:57:24 +00:00
commit 380265bec2
69 changed files with 2112 additions and 2173 deletions

48
Cargo.lock generated
View File

@ -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",

View File

@ -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,
})

View File

@ -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},
})

View File

@ -26,7 +26,7 @@
Sword(DDamage),
Sword(DScaling),
Sword(DSpeed),
Sword(DInfinite),
Sword(DChargeThrough),
Sword(UnlockSpin),
Sword(SDamage),
Sword(SSpeed),

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -1,4 +1,4 @@
([
Skill((General(HealthIncrease), Some(2))),
Skill((General(EnergyIncrease), Some(1))),
Skill((General(HealthIncrease), 2)),
Skill((General(EnergyIncrease), 1)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -1,4 +1,4 @@
([
Skill((General(HealthIncrease), Some(4))),
Skill((General(EnergyIncrease), Some(2))),
Skill((General(HealthIncrease), 4)),
Skill((General(EnergyIncrease), 2)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -1,4 +1,4 @@
([
Skill((General(HealthIncrease), Some(6))),
Skill((General(EnergyIncrease), Some(3))),
Skill((General(HealthIncrease), 6)),
Skill((General(EnergyIncrease), 3)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -1,4 +1,4 @@
([
Skill((General(HealthIncrease), Some(8))),
Skill((General(EnergyIncrease), Some(4))),
Skill((General(HealthIncrease), 8)),
Skill((General(EnergyIncrease), 4)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -1,4 +1,4 @@
([
Skill((General(HealthIncrease), Some(10))),
Skill((General(EnergyIncrease), Some(5))),
Skill((General(HealthIncrease), 10)),
Skill((General(EnergyIncrease), 5)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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)),
])

View File

@ -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),

View File

@ -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(

View File

@ -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"] }

View File

@ -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

View File

@ -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,
},

View File

@ -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<ToolKind>,
) -> Self {
pub fn adjusted_by_skills(mut self, skillset: &SkillSet, tool: Option<ToolKind>) -> 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::<Vec<_>>();
}
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());
}
},

View File

@ -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,

View File

@ -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},
};

File diff suppressed because it is too large Load Diff

View File

@ -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<SkillGroupKind, BTreeSet<Skill>>);
impl Asset for SkillTreeMap {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
pub struct SkillGroupDef {
pub skills: BTreeSet<Skill>,
pub total_skill_point_cost: u16,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SkillLevelMap(HashMap<Skill, u16>);
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<Skill, HashMap<Skill, u16>>);
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<SkillGroupKind, SkillGroupDef> = {
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::<u16>()
})
.sum()
})
)
.collect()
};
// Creates a hashmap for the reverse lookup of skill groups from a skill
pub static ref SKILL_GROUP_LOOKUP: HashMap<Skill, SkillGroupKind> = {
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<Skill, u16> = {
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<Skill, HashMap<Skill, u16>> = {
SkillPrerequisitesMap::load_expect_cloned(
"common.skill_trees.skill_prerequisites",
).0
};
pub static ref SKILL_GROUP_HASHES: HashMap<SkillGroupKind, Vec<u8>> = {
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<Skill>,
}
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<u16> {
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<SkillGroupKind, SkillGroup>,
skills: HashMap<Skill, u16>,
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<SkillsPersistenceError>,
}
impl Component for SkillSet {
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
}
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<Skill, u16> {
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<SkillGroupKind, SkillGroup>,
mut all_skills: HashMap<SkillGroupKind, Result<Vec<Skill>, 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<Item = &SkillGroup> { 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<u16> {
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<u16, SkillError> {
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,
}

View File

@ -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<Item = (Skill, u16)> + '_ {
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<SkillGroupKind> {
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,
},
}
}
}

View File

@ -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<SkillGroupKind>,
},
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<f32>,

View File

@ -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<u16>)),
Skill((Skill, u16)),
Group(SkillGroupKind),
}
#[must_use]
fn skills_from_asset_expect(asset_specifier: &str) -> Vec<(Skill, Option<u16>)> {
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<u16>)> {
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<u16>)> {
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<u16>) -> 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<u16>) -> 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 {

View File

@ -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

View File

@ -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());
}

View File

@ -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<ServerEvent>>,
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<Outcome>>,
);
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::<HashSet<_>>();
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;

View File

@ -2854,8 +2854,8 @@ fn handle_skill_point(
}
}
fn parse_skill_tree(skill_tree: &str) -> CmdResult<comp::skills::SkillGroupKind> {
use comp::{item::tool::ToolKind, skills::SkillGroupKind};
fn parse_skill_tree(skill_tree: &str) -> CmdResult<comp::skillset::SkillGroupKind> {
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(())

View File

@ -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<Outcome>,
) {
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,
});
}

View File

@ -301,7 +301,7 @@ fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<&
}
#[derive(Deserialize)]
struct ResourceExperienceManifest(HashMap<String, i32>);
struct ResourceExperienceManifest(HashMap<String, u32>);
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::<Vec<Outcome>>()
.push(Outcome::ExpChange {
let skill_group = SkillGroupKind::Weapon(tool);
let mut outcomes = state.ecs().write_resource::<Vec<Outcome>>();
let positions = state.ecs().read_component::<comp::Pos>();
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))
};

View File

@ -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;

View File

@ -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::<Vec<Skill>>();
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::<Vec<Value>>(),
);
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(

View File

@ -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, PersistenceError> {
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, PersistenceError> {
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<skills::SkillGroup> {
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<skillset::SkillGroupKind, skillset::SkillGroup>,
//
HashMap<skillset::SkillGroupKind, Result<Vec<skills::Skill>, 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::<Vec<skills::Skill>>(&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<skills::Skill, Option<u16>> {
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<Item = &'a skillset::SkillGroup>>(
entity_id: CharacterId,
skill_groups: Vec<skills::SkillGroup>,
skill_groups: I,
) -> Vec<SkillGroup> {
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<skills::Skill, Option<u16>>,
) -> Vec<Skill> {
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()
}

View File

@ -63,250 +63,8 @@ pub struct CharacterPosition {
pub waypoint: Vec3<f32>,
}
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),

View File

@ -20,18 +20,13 @@ pub struct Body {
pub body_data: String,
}
pub struct Skill {
pub entity_id: i64,
pub skill: String,
pub level: Option<i32>,
}
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<u8>,
}
pub struct Pet {

View File

@ -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));

View File

@ -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 { .. }

View File

@ -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<str
"hud.skill.sw_dash_speed",
modifiers.dash.forward_speed,
),
SwordSkill::DInfinite => localize(
SwordSkill::DChargeThrough => localize(
i18n,
"hud.skill.sw_dash_charge_through_title",
"hud.skill.sw_dash_charge_through",

View File

@ -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<SkillGroupKind>,
@ -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<Event>,
outcome_via_keypress: Option<bool>,
}
@ -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::<comp::Poise>();
// 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)
{

View File

@ -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)

View File

@ -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();
},
}
}