mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/skill-persistence-fixes' into 'master'
Skill persistence fixes See merge request veloren/veloren!3006
This commit is contained in:
commit
380265bec2
48
Cargo.lock
generated
48
Cargo.lock
generated
@ -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",
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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},
|
||||
})
|
@ -26,7 +26,7 @@
|
||||
Sword(DDamage),
|
||||
Sword(DScaling),
|
||||
Sword(DSpeed),
|
||||
Sword(DInfinite),
|
||||
Sword(DChargeThrough),
|
||||
Sword(UnlockSpin),
|
||||
Sword(SDamage),
|
||||
Sword(SSpeed),
|
||||
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -1,4 +1,4 @@
|
||||
([
|
||||
Skill((General(HealthIncrease), Some(2))),
|
||||
Skill((General(EnergyIncrease), Some(1))),
|
||||
Skill((General(HealthIncrease), 2)),
|
||||
Skill((General(EnergyIncrease), 1)),
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -1,4 +1,4 @@
|
||||
([
|
||||
Skill((General(HealthIncrease), Some(4))),
|
||||
Skill((General(EnergyIncrease), Some(2))),
|
||||
Skill((General(HealthIncrease), 4)),
|
||||
Skill((General(EnergyIncrease), 2)),
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -1,4 +1,4 @@
|
||||
([
|
||||
Skill((General(HealthIncrease), Some(6))),
|
||||
Skill((General(EnergyIncrease), Some(3))),
|
||||
Skill((General(HealthIncrease), 6)),
|
||||
Skill((General(EnergyIncrease), 3)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
|
||||
])
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -1,4 +1,4 @@
|
||||
([
|
||||
Skill((General(HealthIncrease), Some(8))),
|
||||
Skill((General(EnergyIncrease), Some(4))),
|
||||
Skill((General(HealthIncrease), 8)),
|
||||
Skill((General(EnergyIncrease), 4)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -1,4 +1,4 @@
|
||||
([
|
||||
Skill((General(HealthIncrease), Some(10))),
|
||||
Skill((General(EnergyIncrease), Some(5))),
|
||||
Skill((General(HealthIncrease), 10)),
|
||||
Skill((General(EnergyIncrease), 5)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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)),
|
||||
])
|
||||
|
@ -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),
|
||||
|
@ -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(
|
||||
|
@ -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"] }
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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());
|
||||
}
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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
574
common/src/comp/skillset/mod.rs
Normal file
574
common/src/comp/skillset/mod.rs
Normal 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,
|
||||
}
|
593
common/src/comp/skillset/skills.rs
Normal file
593
common/src/comp/skillset/skills.rs
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -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>,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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(())
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
@ -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))
|
||||
};
|
||||
|
61
server/src/migrations/V46__skill_persistence.sql
Normal file
61
server/src/migrations/V46__skill_persistence.sql
Normal 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;
|
@ -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(
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
@ -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 { .. }
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user