mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/sceptre-overhaul' into 'master'
Overhaul sceptre Closes #768 See merge request veloren/veloren!1845
This commit is contained in:
commit
9b198b5cda
@ -71,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Improved static light rendering and illumination
|
||||
- Improved the tree spawning model to allow for overlapping forests
|
||||
- Changed sunlight (and, in general, static light) propagation through blocks to allow for more material properties
|
||||
- Overhauled the sceptre
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -1,15 +1,11 @@
|
||||
BasicBeam(
|
||||
HealingBeam(
|
||||
buildup_duration: 0.25,
|
||||
recover_duration: 0.25,
|
||||
beam_duration: 1.0,
|
||||
base_hps: 60,
|
||||
base_dps: 60,
|
||||
heal: 40,
|
||||
tick_rate: 2.0,
|
||||
range: 25.0,
|
||||
max_angle: 1.0,
|
||||
lifesteal_eff: 0.15,
|
||||
energy_regen: 25,
|
||||
energy_cost: 50,
|
||||
energy_drain: 0,
|
||||
orientation_behavior: Normal,
|
||||
energy_cost: 75,
|
||||
specifier: HealingBeam,
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
BasicRanged(
|
||||
energy_cost: 450,
|
||||
buildup_duration: 0.8,
|
||||
recover_duration: 0.05,
|
||||
projectile: Heal(
|
||||
heal: 80.0,
|
||||
damage: 60.0,
|
||||
poise_damage: 0,
|
||||
radius: 6.0,
|
||||
),
|
||||
projectile_body: Object(BoltNature),
|
||||
/*projectile_light: Some(LightEmitter {
|
||||
col: (0.0, 1.0, 0.0).into(),
|
||||
..Default::default()
|
||||
}),*/
|
||||
projectile_gravity: Some(Gravity(0.5)),
|
||||
projectile_speed: 40.0,
|
||||
can_continue: false,
|
||||
)
|
14
assets/common/abilities/sceptre/lifestealbeam.ron
Normal file
14
assets/common/abilities/sceptre/lifestealbeam.ron
Normal file
@ -0,0 +1,14 @@
|
||||
BasicBeam(
|
||||
buildup_duration: 0.25,
|
||||
recover_duration: 0.25,
|
||||
beam_duration: 1.0,
|
||||
damage: 40,
|
||||
tick_rate: 2.0,
|
||||
range: 25.0,
|
||||
max_angle: 1.0,
|
||||
lifesteal_eff: 0.15,
|
||||
energy_regen: 50,
|
||||
energy_drain: 0,
|
||||
orientation_behavior: Normal,
|
||||
specifier: LifestealBeam
|
||||
)
|
15
assets/common/abilities/sceptre/wardingaura.ron
Normal file
15
assets/common/abilities/sceptre/wardingaura.ron
Normal file
@ -0,0 +1,15 @@
|
||||
BasicAura(
|
||||
buildup_duration: 0.25,
|
||||
cast_duration: 0.5,
|
||||
recover_duration: 0.25,
|
||||
targets: InGroup,
|
||||
aura: (
|
||||
kind: ProtectingWard,
|
||||
strength: 0.33,
|
||||
duration: Some(10.0),
|
||||
category: Magical,
|
||||
),
|
||||
aura_duration: 1.0,
|
||||
range: 25.0,
|
||||
energy_cost: 400,
|
||||
)
|
@ -2,14 +2,13 @@ BasicBeam(
|
||||
buildup_duration: 0.25,
|
||||
recover_duration: 0.25,
|
||||
beam_duration: 1.0,
|
||||
base_hps: 0,
|
||||
base_dps: 150,
|
||||
damage: 50,
|
||||
tick_rate: 3.0,
|
||||
range: 20.0,
|
||||
max_angle: 10.0,
|
||||
lifesteal_eff: 0.0,
|
||||
energy_regen: 0,
|
||||
energy_cost: 1,
|
||||
energy_drain: 350,
|
||||
orientation_behavior: Normal,
|
||||
)
|
||||
specifier: Flamethrower,
|
||||
)
|
@ -2,14 +2,13 @@ BasicBeam(
|
||||
buildup_duration: 0.5,
|
||||
recover_duration: 0.5,
|
||||
beam_duration: 1.0,
|
||||
base_hps: 0,
|
||||
base_dps: 150,
|
||||
damage: 50,
|
||||
tick_rate: 3.0,
|
||||
range: 20.0,
|
||||
max_angle: 0.1,
|
||||
lifesteal_eff: 0.0,
|
||||
energy_regen: 0,
|
||||
energy_cost: 1,
|
||||
energy_drain: 350,
|
||||
orientation_behavior: Normal,
|
||||
specifier: Flamethrower,
|
||||
)
|
||||
|
@ -2,14 +2,13 @@ BasicBeam(
|
||||
buildup_duration: 0.25,
|
||||
recover_duration: 0.25,
|
||||
beam_duration: 1.0,
|
||||
base_hps: 60,
|
||||
base_dps: 60,
|
||||
damage: 30,
|
||||
tick_rate: 2.0,
|
||||
range: 25.0,
|
||||
max_angle: 1.0,
|
||||
lifesteal_eff: 0.15,
|
||||
energy_regen: 25,
|
||||
energy_cost: 50,
|
||||
energy_drain: 0,
|
||||
orientation_behavior: Normal,
|
||||
specifier: HealingBeam,
|
||||
)
|
@ -2,14 +2,13 @@ BasicBeam(
|
||||
buildup_duration: 0.4,
|
||||
recover_duration: 0.25,
|
||||
beam_duration: 0.5,
|
||||
base_hps: 0,
|
||||
base_dps: 150,
|
||||
damage: 50,
|
||||
tick_rate: 3.0,
|
||||
range: 15.0,
|
||||
max_angle: 22.5,
|
||||
lifesteal_eff: 0.0,
|
||||
energy_regen: 0,
|
||||
energy_cost: 0,
|
||||
energy_drain: 0,
|
||||
orientation_behavior: Normal,
|
||||
specifier: Flamethrower,
|
||||
)
|
@ -2,14 +2,13 @@ BasicBeam(
|
||||
buildup_duration: 0.25,
|
||||
recover_duration: 0.25,
|
||||
beam_duration: 0.5,
|
||||
base_hps: 0,
|
||||
base_dps: 9001,
|
||||
damage: 3000,
|
||||
tick_rate: 3.0,
|
||||
range: 30.0,
|
||||
max_angle: 1.0,
|
||||
lifesteal_eff: 0.0,
|
||||
energy_regen: 0,
|
||||
energy_cost: 0,
|
||||
energy_drain: 0,
|
||||
orientation_behavior: Turret,
|
||||
specifier: Flamethrower,
|
||||
)
|
@ -70,9 +70,11 @@
|
||||
],
|
||||
),
|
||||
Sceptre: (
|
||||
primary: "common.abilities.sceptre.healingbeam",
|
||||
secondary: "common.abilities.sceptre.healingbomb",
|
||||
abilities: [],
|
||||
primary: "common.abilities.sceptre.lifestealbeam",
|
||||
secondary: "common.abilities.sceptre.healingbeam",
|
||||
abilities: [
|
||||
(Some(Sceptre(UnlockAura)), "common.abilities.sceptre.wardingaura"),
|
||||
],
|
||||
),
|
||||
Dagger: (
|
||||
primary: "common.abilities.dagger.tempbasic",
|
||||
|
@ -58,17 +58,17 @@
|
||||
Staff(SKnockback): Some(2),
|
||||
Staff(SRange): Some(2),
|
||||
Staff(SCost): Some(2),
|
||||
Sceptre(BHeal): Some(3),
|
||||
Sceptre(BDamage): Some(2),
|
||||
Sceptre(BRange): Some(2),
|
||||
Sceptre(BLifesteal): Some(2),
|
||||
Sceptre(BRegen): Some(2),
|
||||
Sceptre(BCost): Some(2),
|
||||
Sceptre(PHeal): Some(3),
|
||||
Sceptre(PDamage): Some(2),
|
||||
Sceptre(PRadius): Some(2),
|
||||
Sceptre(PCost): Some(2),
|
||||
Sceptre(PProjSpeed): Some(2),
|
||||
Sceptre(LDamage): Some(3),
|
||||
Sceptre(LRange): Some(2),
|
||||
Sceptre(LLifesteal): Some(3),
|
||||
Sceptre(LRegen): Some(2),
|
||||
Sceptre(HHeal): Some(3),
|
||||
Sceptre(HCost): Some(2),
|
||||
Sceptre(HRange): 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),
|
||||
|
@ -1,68 +1,27 @@
|
||||
({
|
||||
Roll(Cost): {Roll(ImmuneMelee): None},
|
||||
Roll(Strength): {Roll(ImmuneMelee): None},
|
||||
Roll(Duration): {Roll(ImmuneMelee): None},
|
||||
Sword(TsDamage): {Sword(TsCombo): None},
|
||||
Sword(TsRegen): {Sword(TsCombo): None},
|
||||
Sword(TsSpeed): {Sword(TsCombo): None},
|
||||
Sword(DDrain): {Sword(DDamage): Some(1)},
|
||||
Sword(DCost): {Sword(DDamage): Some(1)},
|
||||
Sword(DSpeed): {Sword(DDamage): Some(1)},
|
||||
Sword(DInfinite): {Sword(DDamage): Some(1)},
|
||||
Sword(DScaling): {Sword(DDamage): Some(1)},
|
||||
Sword(SDamage): {Sword(UnlockSpin): None},
|
||||
Sword(SSpeed): {Sword(UnlockSpin): None},
|
||||
Sword(SCost): {Sword(UnlockSpin): None},
|
||||
Sword(SSpins): {Sword(UnlockSpin): None},
|
||||
Axe(DsDamage): {Axe(DsCombo): None},
|
||||
Axe(DsSpeed): {Axe(DsCombo): None},
|
||||
Axe(DsRegen): {Axe(DsCombo): None},
|
||||
Axe(SHelicopter): {Axe(SInfinite): None},
|
||||
Axe(SDamage): {Axe(SInfinite): None},
|
||||
Axe(SSpeed): {Axe(SInfinite): None},
|
||||
Axe(SCost): {Axe(SInfinite): None},
|
||||
Axe(LDamage): {Axe(UnlockLeap): None},
|
||||
Axe(LKnockback): {Axe(UnlockLeap): None},
|
||||
Axe(LCost): {Axe(UnlockLeap): None},
|
||||
Axe(LDistance): {Axe(UnlockLeap): None},
|
||||
Hammer(SsDamage): {Hammer(SsKnockback): Some(1)},
|
||||
Hammer(SsRegen): {Hammer(SsKnockback): Some(1)},
|
||||
Hammer(SsSpeed): {Hammer(SsKnockback): Some(1)},
|
||||
Hammer(CDamage): {Hammer(CKnockback): Some(1)},
|
||||
Hammer(CDrain): {Hammer(CKnockback): Some(1)},
|
||||
Hammer(CSpeed): {Hammer(CKnockback): Some(1)},
|
||||
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(BRegen): {Bow(BDamage): Some(1)},
|
||||
Bow(CKnockback): {Bow(CDamage): Some(1)},
|
||||
Bow(CProjSpeed): {Bow(CDamage): Some(1)},
|
||||
Bow(CDrain): {Bow(CDamage): Some(1)},
|
||||
Bow(CSpeed): {Bow(CDamage): Some(1)},
|
||||
Bow(CMove): {Bow(CDamage): Some(1)},
|
||||
Bow(RDamage): {Bow(UnlockRepeater): None},
|
||||
Bow(RGlide): {Bow(UnlockRepeater): None},
|
||||
Bow(RArrows): {Bow(UnlockRepeater): None},
|
||||
Bow(RCost): {Bow(UnlockRepeater): None},
|
||||
Staff(BDamage): {Staff(BExplosion): None},
|
||||
Staff(BRegen): {Staff(BExplosion): None},
|
||||
Staff(BRadius): {Staff(BExplosion): None},
|
||||
Staff(FRange): {Staff(FDamage): Some(1)},
|
||||
Staff(FDrain): {Staff(FDamage): Some(1)},
|
||||
Staff(FVelocity): {Staff(FDamage): Some(1)},
|
||||
Staff(SDamage): {Staff(UnlockShockwave): None},
|
||||
Staff(SKnockback): {Staff(UnlockShockwave): None},
|
||||
Staff(SRange): {Staff(UnlockShockwave): None},
|
||||
Staff(SCost): {Staff(UnlockShockwave): None},
|
||||
Sceptre(BDamage): {Sceptre(BHeal): Some(1)},
|
||||
Sceptre(BRange): {Sceptre(BHeal): Some(1)},
|
||||
Sceptre(BLifesteal): {Sceptre(BHeal): Some(1)},
|
||||
Sceptre(BRegen): {Sceptre(BHeal): Some(1)},
|
||||
Sceptre(BCost): {Sceptre(BHeal): Some(1)},
|
||||
Sceptre(PDamage): {Sceptre(PHeal): Some(1)},
|
||||
Sceptre(PRadius): {Sceptre(PHeal): Some(1)},
|
||||
Sceptre(PCost): {Sceptre(PHeal): Some(1)},
|
||||
Sceptre(PProjSpeed): {Sceptre(PHeal): Some(1)},
|
||||
Sceptre(AStrength): {Sceptre(UnlockAura): None},
|
||||
Sceptre(ADuration): {Sceptre(UnlockAura): None},
|
||||
Sceptre(ARange): {Sceptre(UnlockAura): None},
|
||||
Sceptre(ACost): {Sceptre(UnlockAura): None},
|
||||
})
|
@ -95,16 +95,17 @@
|
||||
Staff(SCost),
|
||||
],
|
||||
Weapon(Sceptre): [
|
||||
Sceptre(BHeal),
|
||||
Sceptre(BDamage),
|
||||
Sceptre(BRange),
|
||||
Sceptre(BLifesteal),
|
||||
Sceptre(BRegen),
|
||||
Sceptre(BCost),
|
||||
Sceptre(PHeal),
|
||||
Sceptre(PDamage),
|
||||
Sceptre(PRadius),
|
||||
Sceptre(PCost),
|
||||
Sceptre(PProjSpeed),
|
||||
Sceptre(LDamage),
|
||||
Sceptre(LRange),
|
||||
Sceptre(LLifesteal),
|
||||
Sceptre(LRegen),
|
||||
Sceptre(HHeal),
|
||||
Sceptre(HCost),
|
||||
Sceptre(HRange),
|
||||
Sceptre(UnlockAura),
|
||||
Sceptre(AStrength),
|
||||
Sceptre(ADuration),
|
||||
Sceptre(ARange),
|
||||
Sceptre(ACost),
|
||||
],
|
||||
})
|
BIN
assets/voxygen/element/icons/de_buffs/buff_damage_reduce_0.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/de_buffs/buff_damage_reduce_0.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/lifesteal.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/lifesteal.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/icons/sceptre_protection.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/icons/sceptre_protection.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -17,6 +17,8 @@
|
||||
"buff.desc.campfire_heal": "Resting at a campfire heals 1% per second.",
|
||||
"buff.title.invulnerability": "Invulnerability",
|
||||
"buff.desc.invulnerability": "You cannot be damaged by any attack.",
|
||||
"buff.title.protectingward": "Protecting Ward",
|
||||
"buff.desc.protectingward": "You are protected, somewhat, from attacks.",
|
||||
// Debuffs
|
||||
"buff.title.bleed": "Bleeding",
|
||||
"buff.desc.bleed": "Inflicts regular damage.",
|
||||
|
@ -34,32 +34,34 @@
|
||||
"hud.skill.roll_dur_title": "Rolling Duration",
|
||||
"hud.skill.roll_dur": "Roll for 20% more time{SP}",
|
||||
// Sceptre
|
||||
"hud.skill.sc_healbomb_title" : "Heal Bomb",
|
||||
"hud.skill.sc_healbomb" : "Killing with kindness",
|
||||
"hud.skill.sc_projectile_speed_title": "Projectile Speed",
|
||||
"hud.skill.sc_projectile_speed": "Projectile threw 25% faster{SP}",
|
||||
"hud.skill.sc_energy_cost_title" : "Energy Cost",
|
||||
"hud.skill.sc_energy_cost" : "Decreases energy cost of bomb by 15%{SP}",
|
||||
"hud.skill.sc_radius_title" : "Radius",
|
||||
"hud.skill.sc_radius" : "Increases radius by 30%{SP}",
|
||||
"hud.skill.sc_damage_title" : "Damage",
|
||||
"hud.skill.sc_damage" : "Increases damage of bomb by 20%{SP}",
|
||||
"hud.skill.sc_heal_title" : "Heal",
|
||||
"hud.skill.sc_heal" : "Increases healing of bomb by 15%{SP}",
|
||||
"hud.skill.sc_heal_cost_title" : "Heal Cost",
|
||||
"hud.skill.sc_heal_cost" : "Use 10% less energy when healing{SP}",
|
||||
"hud.skill.sc_lifesteal_efficiency_title" : "Lifesteal Efficiency",
|
||||
"hud.skill.sc_lifesteal_efficiency" : "Thieve 25% more health{SP}",
|
||||
"hud.skill.sc_range_title" : "Range",
|
||||
"hud.skill.sc_range" : "Longer beam, by 20%{SP}",
|
||||
"hud.skill.sc_energy_regen_title" : "Energy Regen",
|
||||
"hud.skill.sc_energy_regen" : "Increases energy regen from dealing damage by 10%{SP}",
|
||||
"hud.skill.sc_beam_title" : "Heal Beam",
|
||||
"hud.skill.sc_beam" : "Heal your party, but unheal the enemies",
|
||||
"hud.skill.sc_beam_damage_title" : "Beam Damage",
|
||||
"hud.skill.sc_beam_damage" : "Increases damage from the beam by 25%{SP}",
|
||||
"hud.skill.sc_beam_heal_title" : "Beam Heal",
|
||||
"hud.skill.sc_beam_heal" : "Increased healing from the beam by 15%{SP}",
|
||||
"hud.skill.sc_lifesteal_title": "Lifesteal Beam",
|
||||
"hud.skill.sc_lifesteal": "Drain the life from your enemies",
|
||||
"hud.skill.sc_lifesteal_damage_title": "Damage",
|
||||
"hud.skill.sc_lifesteal_damage": "Deal 20% more damage{SP}",
|
||||
"hud.skill.sc_lifesteal_range_title": "Range",
|
||||
"hud.skill.sc_lifesteal_range": "Your beam reaches 25% further{SP}",
|
||||
"hud.skill.sc_lifesteal_lifesteal_title": "Lifesteal",
|
||||
"hud.skill.sc_lifesteal_lifesteal": "Convert an additional 30% of damage into health{SP}",
|
||||
"hud.skill.sc_lifesteal_regen_title": "Stamina Regen",
|
||||
"hud.skill.sc_lifesteal_regen": "Replenish your stamina by an additional 25%{SP}",
|
||||
"hud.skill.sc_heal_title": "Healing Beam",
|
||||
"hud.skill.sc_heal": "Heal your allies using the blood of your enemies",
|
||||
"hud.skill.sc_heal_heal_title": "Heal",
|
||||
"hud.skill.sc_heal_heal": "Increases the amount you heal others by 20%{SP}",
|
||||
"hud.skill.sc_heal_cost_title": "Stamina Cost",
|
||||
"hud.skill.sc_heal_cost": "Healing others requires 20% less stamina{SP}",
|
||||
"hud.skill.sc_heal_range_title": "Range",
|
||||
"hud.skill.sc_heal_range": "Your beam reachs 25% further{SP}",
|
||||
"hud.skill.sc_wardaura_unlock_title": "Warding Aura Unlock",
|
||||
"hud.skill.sc_wardaura_unlock": "Allows you to ward your allies against enemy attacks{SP}",
|
||||
"hud.skill.sc_wardaura_strength_title": "Strength",
|
||||
"hud.skill.sc_wardaura_strength": "The strength of your protection increases by 20%{SP}",
|
||||
"hud.skill.sc_wardaura_duration_title": "Duration",
|
||||
"hud.skill.sc_wardaura_duration": "The effects of your ward last 30% longer{SP}",
|
||||
"hud.skill.sc_wardaura_range_title": "Radius",
|
||||
"hud.skill.sc_wardaura_range": "Your ward reaches 25% further{SP}",
|
||||
"hud.skill.sc_wardaura_cost_title": "Stamina Cost",
|
||||
"hud.skill.sc_wardaura_cost": "Creating the ward requries 20% less energy{SP}",
|
||||
// Staff
|
||||
"hud.skill.st_shockwave_range_title" : "Shockwave Range",
|
||||
"hud.skill.st_shockwave_range" : "throw things that used to be out of reach, range increased 20%{SP}",
|
||||
|
@ -60,6 +60,7 @@ const int FIRE_BOWL = 18;
|
||||
const int SNOW = 19;
|
||||
const int EXPLOSION = 20;
|
||||
const int ICE = 21;
|
||||
const int LIFESTEAL_BEAM = 22;
|
||||
|
||||
// meters per second squared (acceleration)
|
||||
const float earth_gravity = 9.807;
|
||||
@ -340,13 +341,23 @@ void main() {
|
||||
vec4(vec3(0.4, 1.6 + 0.3 * sin(tick.x * 10 - lifetime * 3 + 4), 1.0 + 0.15 * sin(tick.x * 5 - lifetime * 5)), 1 /*0.3*/),
|
||||
spin_in_axis(inst_dir, tick.z)
|
||||
);
|
||||
} else if (inst_mode == LIFESTEAL_BEAM) {
|
||||
f_reflect = 0.0;
|
||||
float green_col = 0.2 + 1.4 * sin(tick.x * 5 + lifetime * 5);
|
||||
float purple_col = 1.2 + 0.1 * sin(tick.x * 3 - lifetime * 3) - max(green_col, 1) + 1;
|
||||
attr = Attr(
|
||||
spiral_motion(inst_dir, 0.3 * (floor(2 * rand0 + 0.5) - 0.5) * min(linear_scale(10), 1), lifetime / inst_lifespan, 10.0, inst_time),
|
||||
vec3((1.7 - 0.7 * abs(floor(2 * rand0 - 0.5) + 0.5)) * (1.5 + 0.5 * sin(tick.x * 10 - lifetime * 4))),
|
||||
vec4(vec3(purple_col, green_col, 0.75 * purple_col), 1),
|
||||
spin_in_axis(inst_dir, tick.z)
|
||||
);
|
||||
} else if (inst_mode == ENERGY_NATURE) {
|
||||
f_reflect = 0.0;
|
||||
float spiral_radius = start_end(1 - pow(abs(rand5), 5), 1) * length(inst_dir);
|
||||
attr = Attr(
|
||||
inst_dir * slow_end(0.03) + spiral_motion(vec3(rand1, rand2, rand3),
|
||||
0.2 * (rand4 + 1.3) * slow_end(0.02), percent() * 3 * (rand4 + 4.0) + rand0, 1.0, 0.0),
|
||||
vec3(1.0),
|
||||
vec4(vec3(0, 2.5, 1.5 + rand7 * 0.7), 1),
|
||||
spiral_motion(vec3(0, 0, rand3 + 1), spiral_radius, lifetime, abs(rand0), rand1 * 2 * PI) + vec3(0, 0, rand2),
|
||||
vec3(6 * abs(rand4) * (1 - slow_start(2)) * pow(spiral_radius / length(inst_dir), 0.5)),
|
||||
vec4(vec3(0, 1.7, 1.3), 1),
|
||||
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3)
|
||||
);
|
||||
} else if (inst_mode == FLAMETHROWER) {
|
||||
|
@ -13,8 +13,8 @@ use crate::{
|
||||
},
|
||||
poise::PoiseChange,
|
||||
skills::{SkillGroupKind, SkillSet},
|
||||
Body, Energy, EnergyChange, EnergySource, Health, HealthChange, HealthSource, Inventory,
|
||||
Stats,
|
||||
Body, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange, HealthSource,
|
||||
Inventory, Stats,
|
||||
},
|
||||
event::ServerEvent,
|
||||
uid::Uid,
|
||||
@ -45,6 +45,7 @@ pub struct AttackerInfo<'a> {
|
||||
pub entity: EcsEntity,
|
||||
pub uid: Uid,
|
||||
pub energy: Option<&'a Energy>,
|
||||
pub combo: Option<&'a Combo>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -227,9 +228,9 @@ impl Attack {
|
||||
.filter(|e| e.target.map_or(true, |t| t == target_group))
|
||||
.filter(|e| !(matches!(e.target, Some(GroupTarget::OutOfGroup)) && target_dodging))
|
||||
{
|
||||
if match &effect.requirement {
|
||||
Some(CombatRequirement::AnyDamage) => accumulated_damage > 0.0,
|
||||
Some(CombatRequirement::SufficientEnergy(r)) => {
|
||||
if effect.requirements.iter().all(|req| match req {
|
||||
CombatRequirement::AnyDamage => accumulated_damage > 0.0,
|
||||
CombatRequirement::Energy(r) => {
|
||||
if let Some(AttackerInfo {
|
||||
entity,
|
||||
energy: Some(e),
|
||||
@ -252,8 +253,27 @@ impl Attack {
|
||||
false
|
||||
}
|
||||
},
|
||||
None => true,
|
||||
} {
|
||||
CombatRequirement::Combo(r) => {
|
||||
if let Some(AttackerInfo {
|
||||
entity,
|
||||
combo: Some(c),
|
||||
..
|
||||
}) = attacker
|
||||
{
|
||||
let sufficient_combo = c.counter() >= *r;
|
||||
if sufficient_combo {
|
||||
emit(ServerEvent::ComboChange {
|
||||
entity,
|
||||
change: -(*r as i32),
|
||||
});
|
||||
}
|
||||
|
||||
sufficient_combo
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
}) {
|
||||
match effect.effect {
|
||||
CombatEffect::Knockback(kb) => {
|
||||
let impulse = kb.calculate_impulse(dir);
|
||||
@ -368,7 +388,7 @@ impl AttackDamage {
|
||||
pub struct AttackEffect {
|
||||
target: Option<GroupTarget>,
|
||||
effect: CombatEffect,
|
||||
requirement: Option<CombatRequirement>,
|
||||
requirements: Vec<CombatRequirement>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -377,12 +397,12 @@ impl AttackEffect {
|
||||
Self {
|
||||
target,
|
||||
effect,
|
||||
requirement: None,
|
||||
requirements: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_requirement(mut self, requirement: CombatRequirement) -> Self {
|
||||
self.requirement = Some(requirement);
|
||||
self.requirements.push(requirement);
|
||||
self
|
||||
}
|
||||
|
||||
@ -405,7 +425,8 @@ pub enum CombatEffect {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum CombatRequirement {
|
||||
AnyDamage,
|
||||
SufficientEnergy(f32),
|
||||
Energy(f32),
|
||||
Combo(u32),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::{
|
||||
assets::{self, Asset},
|
||||
combat,
|
||||
comp::{
|
||||
inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills, Body,
|
||||
CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate,
|
||||
aura, beam, inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills,
|
||||
Body, CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate,
|
||||
},
|
||||
states::{
|
||||
behavior::JoinData,
|
||||
@ -13,7 +14,6 @@ use crate::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use vek::Vec3;
|
||||
|
||||
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum CharacterAbilityType {
|
||||
@ -216,16 +216,36 @@ pub enum CharacterAbility {
|
||||
buildup_duration: f32,
|
||||
recover_duration: f32,
|
||||
beam_duration: f32,
|
||||
base_hps: f32,
|
||||
base_dps: f32,
|
||||
damage: f32,
|
||||
tick_rate: f32,
|
||||
range: f32,
|
||||
max_angle: f32,
|
||||
lifesteal_eff: f32,
|
||||
energy_regen: f32,
|
||||
energy_cost: f32,
|
||||
energy_drain: f32,
|
||||
orientation_behavior: basic_beam::MovementBehavior,
|
||||
specifier: beam::FrontendSpecifier,
|
||||
},
|
||||
BasicAura {
|
||||
buildup_duration: f32,
|
||||
cast_duration: f32,
|
||||
recover_duration: f32,
|
||||
targets: combat::GroupTarget,
|
||||
aura: aura::AuraBuffConstructor,
|
||||
aura_duration: f32,
|
||||
range: f32,
|
||||
energy_cost: f32,
|
||||
},
|
||||
HealingBeam {
|
||||
buildup_duration: f32,
|
||||
recover_duration: f32,
|
||||
beam_duration: f32,
|
||||
heal: f32,
|
||||
tick_rate: f32,
|
||||
range: f32,
|
||||
max_angle: f32,
|
||||
energy_cost: f32,
|
||||
specifier: beam::FrontendSpecifier,
|
||||
},
|
||||
}
|
||||
|
||||
@ -264,34 +284,14 @@ impl CharacterAbility {
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok()
|
||||
},
|
||||
CharacterAbility::DashMelee { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::BasicMelee { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::BasicRanged { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::LeapMelee { energy_cost, .. } => {
|
||||
update.vel.0.z >= 0.0
|
||||
&& update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok()
|
||||
},
|
||||
CharacterAbility::SpinMelee { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::ChargedRanged { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::ChargedMelee { energy_cost, .. } => update
|
||||
CharacterAbility::DashMelee { energy_cost, .. }
|
||||
| CharacterAbility::BasicMelee { energy_cost, .. }
|
||||
| CharacterAbility::BasicRanged { energy_cost, .. }
|
||||
| CharacterAbility::SpinMelee { energy_cost, .. }
|
||||
| CharacterAbility::ChargedRanged { energy_cost, .. }
|
||||
| CharacterAbility::ChargedMelee { energy_cost, .. }
|
||||
| CharacterAbility::Shockwave { energy_cost, .. }
|
||||
| CharacterAbility::BasicAura { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
@ -304,10 +304,14 @@ impl CharacterAbility {
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok()
|
||||
},
|
||||
CharacterAbility::Shockwave { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::LeapMelee { energy_cost, .. } => {
|
||||
update.vel.0.z >= 0.0
|
||||
&& update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok()
|
||||
},
|
||||
CharacterAbility::HealingBeam { .. } => data.combo.counter() > 0,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
@ -348,7 +352,7 @@ impl CharacterAbility {
|
||||
} => {
|
||||
*buildup_duration /= speed;
|
||||
*recover_duration /= speed;
|
||||
*projectile = projectile.modified_projectile(power, 1_f32, 1_f32, power);
|
||||
*projectile = projectile.modified_projectile(power, 1_f32, 1_f32);
|
||||
},
|
||||
RepeaterRanged {
|
||||
ref mut movement_duration,
|
||||
@ -362,7 +366,7 @@ impl CharacterAbility {
|
||||
*buildup_duration /= speed;
|
||||
*shoot_duration /= speed;
|
||||
*recover_duration /= speed;
|
||||
*projectile = projectile.modified_projectile(power, 1_f32, 1_f32, power);
|
||||
*projectile = projectile.modified_projectile(power, 1_f32, 1_f32);
|
||||
},
|
||||
Boost {
|
||||
ref mut movement_duration,
|
||||
@ -488,16 +492,37 @@ impl CharacterAbility {
|
||||
BasicBeam {
|
||||
ref mut buildup_duration,
|
||||
ref mut recover_duration,
|
||||
ref mut base_hps,
|
||||
ref mut base_dps,
|
||||
ref mut damage,
|
||||
ref mut tick_rate,
|
||||
..
|
||||
} => {
|
||||
*buildup_duration /= speed;
|
||||
*recover_duration /= speed;
|
||||
// hps and dps adjusted by speed as they are normalized by tick rate already
|
||||
*base_hps *= power * speed;
|
||||
*base_dps *= power * speed;
|
||||
*damage *= power;
|
||||
*tick_rate *= speed;
|
||||
},
|
||||
BasicAura {
|
||||
ref mut buildup_duration,
|
||||
ref mut cast_duration,
|
||||
ref mut recover_duration,
|
||||
ref mut aura,
|
||||
..
|
||||
} => {
|
||||
*buildup_duration /= speed;
|
||||
*cast_duration /= speed;
|
||||
*recover_duration /= speed;
|
||||
aura.strength *= power;
|
||||
},
|
||||
HealingBeam {
|
||||
ref mut buildup_duration,
|
||||
ref mut recover_duration,
|
||||
ref mut heal,
|
||||
ref mut tick_rate,
|
||||
..
|
||||
} => {
|
||||
*buildup_duration /= speed;
|
||||
*recover_duration /= speed;
|
||||
*heal *= power;
|
||||
*tick_rate *= speed;
|
||||
},
|
||||
}
|
||||
@ -517,7 +542,15 @@ impl CharacterAbility {
|
||||
| ChargedMelee { energy_cost, .. }
|
||||
| ChargedRanged { energy_cost, .. }
|
||||
| Shockwave { energy_cost, .. }
|
||||
| BasicBeam { energy_cost, .. } => *energy_cost as u32,
|
||||
| HealingBeam { energy_cost, .. }
|
||||
| BasicAura { energy_cost, .. } => *energy_cost as u32,
|
||||
BasicBeam { energy_drain, .. } => {
|
||||
if *energy_drain > f32::EPSILON {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
},
|
||||
BasicBlock | Boost { .. } | ComboMelee { .. } => 0,
|
||||
}
|
||||
}
|
||||
@ -827,7 +860,7 @@ impl CharacterAbility {
|
||||
.unwrap_or(0);
|
||||
let power = 1.20_f32.powi(damage_level.into());
|
||||
let regen = 1.4_f32.powi(regen_level.into());
|
||||
*projectile = projectile.modified_projectile(power, regen, 1_f32, 1_f32);
|
||||
*projectile = projectile.modified_projectile(power, regen, 1_f32);
|
||||
},
|
||||
ChargedRanged {
|
||||
ref mut scaled_damage,
|
||||
@ -874,8 +907,7 @@ impl CharacterAbility {
|
||||
}
|
||||
if let Ok(Some(level)) = skillset.skill_level(Bow(RDamage)) {
|
||||
let power = 1.4_f32.powi(level.into());
|
||||
*projectile =
|
||||
projectile.modified_projectile(power, 1_f32, 1_f32, 1_f32);
|
||||
*projectile = projectile.modified_projectile(power, 1_f32, 1_f32);
|
||||
}
|
||||
if !skillset.has_skill(Bow(RGlide)) {
|
||||
*buildup_duration = 0.001;
|
||||
@ -914,17 +946,17 @@ impl CharacterAbility {
|
||||
let power = 1.2_f32.powi(damage_level.into());
|
||||
let regen = 1.2_f32.powi(regen_level.into());
|
||||
let range = 1.1_f32.powi(range_level.into());
|
||||
*projectile = projectile.modified_projectile(power, regen, range, 1_f32);
|
||||
*projectile = projectile.modified_projectile(power, regen, range);
|
||||
},
|
||||
BasicBeam {
|
||||
ref mut base_dps,
|
||||
ref mut damage,
|
||||
ref mut range,
|
||||
ref mut energy_drain,
|
||||
ref mut beam_duration,
|
||||
..
|
||||
} => {
|
||||
if let Ok(Some(level)) = skillset.skill_level(Staff(FDamage)) {
|
||||
*base_dps *= 1.3_f32.powi(level.into());
|
||||
*damage *= 1.3_f32.powi(level.into());
|
||||
}
|
||||
if let Ok(Some(level)) = skillset.skill_level(Staff(FRange)) {
|
||||
let range_mod = 1.25_f32.powi(level.into());
|
||||
@ -968,66 +1000,66 @@ impl CharacterAbility {
|
||||
use skills::SceptreSkill::*;
|
||||
match self {
|
||||
BasicBeam {
|
||||
ref mut base_hps,
|
||||
ref mut base_dps,
|
||||
ref mut lifesteal_eff,
|
||||
ref mut damage,
|
||||
ref mut range,
|
||||
ref mut energy_regen,
|
||||
ref mut energy_cost,
|
||||
ref mut beam_duration,
|
||||
ref mut lifesteal_eff,
|
||||
ref mut energy_regen,
|
||||
..
|
||||
} => {
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(BHeal)) {
|
||||
*base_hps *= 1.15_f32.powi(level.into());
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(LDamage)) {
|
||||
*damage *= 1.2_f32.powi(level.into());
|
||||
}
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(BDamage)) {
|
||||
*base_dps *= 1.25_f32.powi(level.into());
|
||||
}
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(BRange)) {
|
||||
let range_mod = 1.20_f32.powi(level.into());
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(LRange)) {
|
||||
let range_mod = 1.25_f32.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(BLifesteal)) {
|
||||
*lifesteal_eff *= 1.25_f32.powi(level.into());
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(LRegen)) {
|
||||
*energy_regen *= 1.25_f32.powi(level.into());
|
||||
}
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(BRegen)) {
|
||||
*energy_regen *= 1.1_f32.powi(level.into());
|
||||
}
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(BCost)) {
|
||||
*energy_cost *= 0.9_f32.powi(level.into());
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(LLifesteal)) {
|
||||
*lifesteal_eff *= 1.3_f32.powi(level.into());
|
||||
}
|
||||
},
|
||||
BasicRanged {
|
||||
HealingBeam {
|
||||
ref mut heal,
|
||||
ref mut energy_cost,
|
||||
ref mut projectile,
|
||||
ref mut projectile_speed,
|
||||
ref mut range,
|
||||
ref mut beam_duration,
|
||||
..
|
||||
} => {
|
||||
{
|
||||
let heal_level = skillset
|
||||
.skill_level(Sceptre(PHeal))
|
||||
.unwrap_or(None)
|
||||
.unwrap_or(0);
|
||||
let damage_level = skillset
|
||||
.skill_level(Sceptre(PDamage))
|
||||
.unwrap_or(None)
|
||||
.unwrap_or(0);
|
||||
let range_level = skillset
|
||||
.skill_level(Sceptre(PRadius))
|
||||
.unwrap_or(None)
|
||||
.unwrap_or(0);
|
||||
let heal = 1.15_f32.powi(heal_level.into());
|
||||
let power = 1.2_f32.powi(damage_level.into());
|
||||
let range = 1.3_f32.powi(range_level.into());
|
||||
*projectile = projectile.modified_projectile(power, 1_f32, range, heal);
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(HHeal)) {
|
||||
*heal *= 1.2_f32.powi(level.into());
|
||||
}
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(PCost)) {
|
||||
*energy_cost *= 0.85_f32.powi(level.into());
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(HRange)) {
|
||||
let range_mod = 1.25_f32.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(PProjSpeed)) {
|
||||
*projectile_speed *= 1.25_f32.powi(level.into());
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(HCost)) {
|
||||
*energy_cost *= 0.8_f32.powi(level.into());
|
||||
}
|
||||
},
|
||||
BasicAura {
|
||||
ref mut aura,
|
||||
ref mut range,
|
||||
ref mut energy_cost,
|
||||
..
|
||||
} => {
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(AStrength)) {
|
||||
aura.strength *= 1.2_f32.powi(level.into());
|
||||
}
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(ADuration)) {
|
||||
aura.duration.map(|dur| dur * 1.3_f32.powi(level.into()));
|
||||
}
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(ARange)) {
|
||||
*range *= 1.25_f32.powi(level.into());
|
||||
}
|
||||
if let Ok(Some(level)) = skillset.skill_level(Sceptre(ACost)) {
|
||||
*energy_cost *= 0.8_f32.powi(level.into());
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
@ -1436,36 +1468,82 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
|
||||
buildup_duration,
|
||||
recover_duration,
|
||||
beam_duration,
|
||||
base_hps,
|
||||
base_dps,
|
||||
damage,
|
||||
tick_rate,
|
||||
range,
|
||||
max_angle,
|
||||
lifesteal_eff,
|
||||
energy_regen,
|
||||
energy_cost,
|
||||
energy_drain,
|
||||
orientation_behavior,
|
||||
specifier,
|
||||
} => CharacterState::BasicBeam(basic_beam::Data {
|
||||
static_data: basic_beam::StaticData {
|
||||
buildup_duration: Duration::from_secs_f32(*buildup_duration),
|
||||
recover_duration: Duration::from_secs_f32(*recover_duration),
|
||||
beam_duration: Duration::from_secs_f32(*beam_duration),
|
||||
base_hps: *base_hps,
|
||||
base_dps: *base_dps,
|
||||
damage: *damage,
|
||||
tick_rate: *tick_rate,
|
||||
range: *range,
|
||||
max_angle: *max_angle,
|
||||
lifesteal_eff: *lifesteal_eff,
|
||||
energy_regen: *energy_regen,
|
||||
energy_cost: *energy_cost,
|
||||
energy_drain: *energy_drain,
|
||||
ability_info,
|
||||
orientation_behavior: *orientation_behavior,
|
||||
specifier: *specifier,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
}),
|
||||
CharacterAbility::BasicAura {
|
||||
buildup_duration,
|
||||
cast_duration,
|
||||
recover_duration,
|
||||
targets,
|
||||
aura,
|
||||
aura_duration,
|
||||
range,
|
||||
energy_cost: _,
|
||||
} => CharacterState::BasicAura(basic_aura::Data {
|
||||
static_data: basic_aura::StaticData {
|
||||
buildup_duration: Duration::from_secs_f32(*buildup_duration),
|
||||
cast_duration: Duration::from_secs_f32(*cast_duration),
|
||||
recover_duration: Duration::from_secs_f32(*recover_duration),
|
||||
targets: *targets,
|
||||
aura: *aura,
|
||||
aura_duration: Duration::from_secs_f32(*aura_duration),
|
||||
range: *range,
|
||||
ability_info,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
}),
|
||||
CharacterAbility::HealingBeam {
|
||||
buildup_duration,
|
||||
recover_duration,
|
||||
beam_duration,
|
||||
heal,
|
||||
tick_rate,
|
||||
range,
|
||||
max_angle,
|
||||
energy_cost,
|
||||
specifier,
|
||||
} => CharacterState::HealingBeam(healing_beam::Data {
|
||||
static_data: healing_beam::StaticData {
|
||||
buildup_duration: Duration::from_secs_f32(*buildup_duration),
|
||||
recover_duration: Duration::from_secs_f32(*recover_duration),
|
||||
beam_duration: Duration::from_secs_f32(*beam_duration),
|
||||
heal: *heal,
|
||||
tick_rate: *tick_rate,
|
||||
range: *range,
|
||||
max_angle: *max_angle,
|
||||
energy_cost: *energy_cost,
|
||||
ability_info,
|
||||
specifier: *specifier,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
offset: Vec3::zero(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
combat::GroupTarget,
|
||||
comp::buff::{BuffCategory, BuffData, BuffKind, BuffSource},
|
||||
uid::Uid,
|
||||
};
|
||||
@ -62,11 +63,24 @@ pub enum AuraTarget {
|
||||
/// Targets the group of the entity specified by the `Uid`. This is useful
|
||||
/// for auras which should only affect a player's party.
|
||||
GroupOf(Uid),
|
||||
|
||||
/// Targets everyone not in the group of the entity specified by the `Uid`.
|
||||
/// This is useful for auras which should only affect a player's
|
||||
/// enemies.
|
||||
NotGroupOf(Uid),
|
||||
/// Targets all entities. This is for auras which are global or neutral.
|
||||
All,
|
||||
}
|
||||
|
||||
impl From<(Option<GroupTarget>, Option<&Uid>)> for AuraTarget {
|
||||
fn from((target, uid): (Option<GroupTarget>, Option<&Uid>)) -> Self {
|
||||
match (target, uid) {
|
||||
(Some(GroupTarget::InGroup), Some(uid)) => Self::GroupOf(*uid),
|
||||
(Some(GroupTarget::OutOfGroup), Some(uid)) => Self::NotGroupOf(*uid),
|
||||
_ => Self::All,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Aura {
|
||||
/// Creates a new Aura to be assigned to an entity
|
||||
pub fn new(
|
||||
@ -104,6 +118,35 @@ impl Auras {
|
||||
pub fn remove(&mut self, key: AuraKey) { self.auras.remove(key); }
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AuraBuffConstructor {
|
||||
pub kind: BuffKind,
|
||||
pub strength: f32,
|
||||
pub duration: Option<f32>,
|
||||
pub category: BuffCategory,
|
||||
}
|
||||
|
||||
impl AuraBuffConstructor {
|
||||
pub fn to_aura(
|
||||
self,
|
||||
uid: &Uid,
|
||||
radius: f32,
|
||||
duration: Option<Duration>,
|
||||
target: AuraTarget,
|
||||
) -> Aura {
|
||||
let aura_kind = AuraKind::Buff {
|
||||
kind: self.kind,
|
||||
data: BuffData {
|
||||
strength: self.strength,
|
||||
duration: self.duration.map(Duration::from_secs_f32),
|
||||
},
|
||||
category: self.category,
|
||||
source: BuffSource::Character { by: *uid },
|
||||
};
|
||||
Aura::new(aura_kind, radius, duration, target)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Auras {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ pub struct Properties {
|
||||
pub speed: f32,
|
||||
pub duration: Duration,
|
||||
pub owner: Option<Uid>,
|
||||
pub specifier: FrontendSpecifier,
|
||||
}
|
||||
|
||||
// TODO: Separate components out for cheaper network syncing
|
||||
@ -44,3 +45,10 @@ pub struct Beam {
|
||||
impl Component for Beam {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum FrontendSpecifier {
|
||||
Flamethrower,
|
||||
LifestealBeam,
|
||||
HealingBeam,
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ pub enum BuffKind {
|
||||
IncreaseMaxHealth,
|
||||
/// Makes you immune to attacks
|
||||
Invulnerability,
|
||||
/// Reduces incoming damage
|
||||
ProtectingWard,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -49,6 +51,7 @@ impl BuffKind {
|
||||
BuffKind::IncreaseMaxEnergy => true,
|
||||
BuffKind::IncreaseMaxHealth => true,
|
||||
BuffKind::Invulnerability => true,
|
||||
BuffKind::ProtectingWard => true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,16 +104,11 @@ pub enum BuffEffect {
|
||||
kind: ModifierKind,
|
||||
},
|
||||
/// Changes maximum health by a certain amount
|
||||
MaxHealthModifier {
|
||||
value: f32,
|
||||
kind: ModifierKind,
|
||||
},
|
||||
MaxHealthModifier { value: f32, kind: ModifierKind },
|
||||
/// Changes maximum stamina by a certain amount
|
||||
MaxEnergyModifier {
|
||||
value: f32,
|
||||
kind: ModifierKind,
|
||||
},
|
||||
ImmuneToAttacks,
|
||||
MaxEnergyModifier { value: f32, kind: ModifierKind },
|
||||
/// Reduces damage after armor is accounted for by this fraction
|
||||
DamageReduction(f32),
|
||||
}
|
||||
|
||||
/// Actual de/buff.
|
||||
@ -213,7 +211,16 @@ impl Buff {
|
||||
}],
|
||||
data.duration,
|
||||
),
|
||||
BuffKind::Invulnerability => (vec![BuffEffect::ImmuneToAttacks], data.duration),
|
||||
BuffKind::Invulnerability => (vec![BuffEffect::DamageReduction(1.0)], data.duration),
|
||||
BuffKind::ProtectingWard => (
|
||||
vec![BuffEffect::DamageReduction(
|
||||
// Causes non-linearity in effect strength, but necessary to allow for tool
|
||||
// power and other things to affect the strength. 0.5 also still provides 50%
|
||||
// damage reduction.
|
||||
data.strength / (0.5 + data.strength),
|
||||
)],
|
||||
data.duration,
|
||||
),
|
||||
};
|
||||
Buff {
|
||||
kind,
|
||||
|
@ -81,6 +81,13 @@ pub enum CharacterState {
|
||||
/// A continuous attack that affects all creatures in a cone originating
|
||||
/// from the source
|
||||
BasicBeam(basic_beam::Data),
|
||||
/// Creates an aura that persists as long as you are actively casting
|
||||
BasicAura(basic_aura::Data),
|
||||
/// A directed beam that heals targets in range. This is separate from basic
|
||||
/// beam as a large amount of functionality needed to be special cased
|
||||
/// specifically for the healing beam. There was also functionality present
|
||||
/// on basic beam which was unnecessary for the healing beam.
|
||||
HealingBeam(healing_beam::Data),
|
||||
}
|
||||
|
||||
impl CharacterState {
|
||||
@ -100,6 +107,8 @@ impl CharacterState {
|
||||
| CharacterState::RepeaterRanged(_)
|
||||
| CharacterState::Shockwave(_)
|
||||
| CharacterState::BasicBeam(_)
|
||||
| CharacterState::BasicAura(_)
|
||||
| CharacterState::HealingBeam(_)
|
||||
)
|
||||
}
|
||||
|
||||
@ -121,6 +130,8 @@ impl CharacterState {
|
||||
| CharacterState::RepeaterRanged(_)
|
||||
| CharacterState::Shockwave(_)
|
||||
| CharacterState::BasicBeam(_)
|
||||
| CharacterState::BasicAura(_)
|
||||
| CharacterState::HealingBeam(_)
|
||||
)
|
||||
}
|
||||
|
||||
@ -141,6 +152,7 @@ impl CharacterState {
|
||||
| CharacterState::Stunned(_)
|
||||
| CharacterState::Wielding
|
||||
| CharacterState::Talk
|
||||
| CharacterState::HealingBeam(_)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
|
||||
pub const COMBO_DECAY_START: f64 = 5.0; // seconds
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Combo {
|
||||
counter: u32,
|
||||
@ -24,14 +26,14 @@ impl Combo {
|
||||
|
||||
pub fn reset(&mut self) { self.counter = 0; }
|
||||
|
||||
pub fn increase_by(&mut self, amount: u32, time: f64) {
|
||||
self.counter = self.counter.saturating_add(amount);
|
||||
pub fn change_by(&mut self, amount: i32, time: f64) {
|
||||
if amount > 0 {
|
||||
self.counter = self.counter.saturating_add(amount as u32);
|
||||
} else {
|
||||
self.counter = self.counter.saturating_sub(amount.abs() as u32);
|
||||
}
|
||||
self.last_increase = time;
|
||||
}
|
||||
|
||||
pub fn decrease_by(&mut self, amount: u32) {
|
||||
self.counter = self.counter.saturating_sub(amount);
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Combo {
|
||||
|
@ -58,11 +58,6 @@ pub enum ProjectileConstructor {
|
||||
damage: f32,
|
||||
energy_regen: f32,
|
||||
},
|
||||
Heal {
|
||||
heal: f32,
|
||||
damage: f32,
|
||||
radius: f32,
|
||||
},
|
||||
Possess,
|
||||
}
|
||||
|
||||
@ -103,7 +98,8 @@ impl ProjectileConstructor {
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(energy)
|
||||
.with_effect(knockback);
|
||||
.with_effect(knockback)
|
||||
.with_combo_increment();
|
||||
|
||||
Projectile {
|
||||
hit_solid: vec![Effect::Stick],
|
||||
@ -130,7 +126,8 @@ impl ProjectileConstructor {
|
||||
let attack = Attack::default()
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(energy);
|
||||
.with_effect(energy)
|
||||
.with_combo_increment();
|
||||
let explosion = Explosion {
|
||||
effects: vec![
|
||||
RadiusEffect::Attack(attack),
|
||||
@ -157,7 +154,8 @@ impl ProjectileConstructor {
|
||||
);
|
||||
let attack = Attack::default()
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult);
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_combo_increment();
|
||||
let explosion = Explosion {
|
||||
effects: vec![RadiusEffect::Attack(attack)],
|
||||
radius,
|
||||
@ -187,7 +185,8 @@ impl ProjectileConstructor {
|
||||
let attack = Attack::default()
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(energy);
|
||||
.with_effect(energy)
|
||||
.with_combo_increment();
|
||||
|
||||
Projectile {
|
||||
hit_solid: vec![Effect::Vanish],
|
||||
@ -197,36 +196,6 @@ impl ProjectileConstructor {
|
||||
ignore_group: true,
|
||||
}
|
||||
},
|
||||
Heal {
|
||||
heal,
|
||||
damage,
|
||||
radius,
|
||||
} => {
|
||||
let damage = AttackDamage::new(
|
||||
Damage {
|
||||
source: DamageSource::Explosion,
|
||||
value: damage,
|
||||
},
|
||||
Some(GroupTarget::OutOfGroup),
|
||||
);
|
||||
let heal = AttackEffect::new(Some(GroupTarget::InGroup), CombatEffect::Heal(heal));
|
||||
let attack = Attack::default()
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(heal);
|
||||
let explosion = Explosion {
|
||||
effects: vec![RadiusEffect::Attack(attack)],
|
||||
radius,
|
||||
reagent: Some(Reagent::Green),
|
||||
};
|
||||
Projectile {
|
||||
hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish],
|
||||
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
|
||||
time_left: Duration::from_secs(10),
|
||||
owner,
|
||||
ignore_group: false,
|
||||
}
|
||||
},
|
||||
Possess => Projectile {
|
||||
hit_solid: vec![Effect::Stick],
|
||||
hit_entity: vec![Effect::Stick, Effect::Possess],
|
||||
@ -237,13 +206,7 @@ impl ProjectileConstructor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modified_projectile(
|
||||
mut self,
|
||||
power: f32,
|
||||
regen: f32,
|
||||
range: f32,
|
||||
heal_power: f32,
|
||||
) -> Self {
|
||||
pub fn modified_projectile(mut self, power: f32, regen: f32, range: f32) -> Self {
|
||||
use ProjectileConstructor::*;
|
||||
match self {
|
||||
Arrow {
|
||||
@ -280,16 +243,6 @@ impl ProjectileConstructor {
|
||||
*damage *= power;
|
||||
*energy_regen *= regen;
|
||||
},
|
||||
Heal {
|
||||
ref mut damage,
|
||||
ref mut heal,
|
||||
ref mut radius,
|
||||
..
|
||||
} => {
|
||||
*damage *= power;
|
||||
*heal *= heal_power;
|
||||
*radius *= range;
|
||||
},
|
||||
Possess => {},
|
||||
}
|
||||
self
|
||||
|
@ -219,19 +219,21 @@ pub enum StaffSkill {
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SceptreSkill {
|
||||
// Beam upgrades
|
||||
BHeal,
|
||||
BDamage,
|
||||
BRange,
|
||||
BLifesteal,
|
||||
BRegen,
|
||||
BCost,
|
||||
// Projectile upgrades
|
||||
PHeal,
|
||||
PDamage,
|
||||
PRadius,
|
||||
PCost,
|
||||
PProjSpeed,
|
||||
// Lifesteal beam upgrades
|
||||
LDamage,
|
||||
LRange,
|
||||
LLifesteal,
|
||||
LRegen,
|
||||
// Healing beam upgrades
|
||||
HHeal,
|
||||
HCost,
|
||||
HRange,
|
||||
// Warding aura upgrades
|
||||
UnlockAura,
|
||||
AStrength,
|
||||
ADuration,
|
||||
ARange,
|
||||
ACost,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{comp, uid::Uid};
|
||||
use comp::item::Reagent;
|
||||
use comp::{beam, item::Reagent};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::*;
|
||||
|
||||
@ -24,7 +24,7 @@ pub enum Outcome {
|
||||
},
|
||||
Beam {
|
||||
pos: Vec3<f32>,
|
||||
heal: bool,
|
||||
specifier: beam::FrontendSpecifier,
|
||||
},
|
||||
ExpChange {
|
||||
uid: Uid,
|
||||
@ -37,16 +37,20 @@ pub enum Outcome {
|
||||
// TODO: Access ECS to get position from Uid to conserve bandwidth
|
||||
pos: Vec3<f32>,
|
||||
},
|
||||
ComboChange {
|
||||
uid: Uid,
|
||||
combo: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Outcome {
|
||||
pub fn get_pos(&self) -> Option<Vec3<f32>> {
|
||||
match self {
|
||||
Outcome::Explosion { pos, .. } => Some(*pos),
|
||||
Outcome::ProjectileShot { pos, .. } => Some(*pos),
|
||||
Outcome::Beam { pos, .. } => Some(*pos),
|
||||
Outcome::ExpChange { .. } => None,
|
||||
Outcome::SkillPointGain { pos, .. } => Some(*pos),
|
||||
Outcome::Explosion { pos, .. }
|
||||
| Outcome::ProjectileShot { pos, .. }
|
||||
| Outcome::Beam { pos, .. }
|
||||
| Outcome::SkillPointGain { pos, .. } => Some(*pos),
|
||||
Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
137
common/src/states/basic_aura.rs
Normal file
137
common/src/states/basic_aura.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use crate::{
|
||||
combat::GroupTarget,
|
||||
comp::{
|
||||
aura::{AuraBuffConstructor, AuraChange, AuraTarget},
|
||||
CharacterState, StateUpdate,
|
||||
},
|
||||
event::ServerEvent,
|
||||
states::{
|
||||
behavior::{CharacterBehavior, JoinData},
|
||||
utils::*,
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Separated out to condense update portions of character state
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StaticData {
|
||||
/// How long until state should create the aura
|
||||
pub buildup_duration: Duration,
|
||||
/// How long the state is creating an aura
|
||||
pub cast_duration: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// Determines how the aura selects its targets
|
||||
pub targets: GroupTarget,
|
||||
/// Has information used to construct the aura
|
||||
pub aura: AuraBuffConstructor,
|
||||
/// How long aura lasts
|
||||
pub aura_duration: Duration,
|
||||
/// Radius of aura
|
||||
pub range: f32,
|
||||
/// What key is used to press ability
|
||||
pub ability_info: AbilityInfo,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Struct containing data that does not change over the course of the
|
||||
/// character state
|
||||
pub static_data: StaticData,
|
||||
/// Timer for each stage
|
||||
pub timer: Duration,
|
||||
/// What section the character stage is in
|
||||
pub stage_section: StageSection,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||
let mut update = StateUpdate::from(data);
|
||||
|
||||
handle_move(data, &mut update, 0.8);
|
||||
handle_jump(data, &mut update);
|
||||
if !ability_key_is_pressed(data, self.static_data.ability_info.key) {
|
||||
handle_interrupt(data, &mut update, false);
|
||||
match update.character {
|
||||
CharacterState::BasicAura(_) => {},
|
||||
_ => {
|
||||
return update;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
match self.stage_section {
|
||||
StageSection::Buildup => {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::BasicAura(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Creates aura
|
||||
let targets =
|
||||
AuraTarget::from((Some(self.static_data.targets), Some(data.uid)));
|
||||
let aura = self.static_data.aura.to_aura(
|
||||
data.uid,
|
||||
self.static_data.range,
|
||||
Some(self.static_data.aura_duration),
|
||||
targets,
|
||||
);
|
||||
update.server_events.push_front(ServerEvent::Aura {
|
||||
entity: data.entity,
|
||||
aura_change: AuraChange::Add(aura),
|
||||
});
|
||||
// Build up
|
||||
update.character = CharacterState::BasicAura(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Cast,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Cast => {
|
||||
if self.timer < self.static_data.cast_duration {
|
||||
// Cast
|
||||
update.character = CharacterState::BasicAura(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
update.character = CharacterState::BasicAura(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Recover => {
|
||||
if self.timer < self.static_data.recover_duration {
|
||||
update.character = CharacterState::BasicAura(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// If it somehow ends up in an incorrect stage section
|
||||
update.character = CharacterState::Wielding;
|
||||
},
|
||||
}
|
||||
|
||||
update
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ use crate::{
|
||||
Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage, DamageSource,
|
||||
GroupTarget,
|
||||
},
|
||||
comp::{beam, Body, CharacterState, EnergyChange, EnergySource, Ori, Pos, StateUpdate},
|
||||
comp::{beam, CharacterState, EnergyChange, EnergySource, Ori, Pos, StateUpdate},
|
||||
event::ServerEvent,
|
||||
states::{
|
||||
behavior::{CharacterBehavior, JoinData},
|
||||
@ -24,11 +24,9 @@ pub struct StaticData {
|
||||
pub recover_duration: Duration,
|
||||
/// How long each beam segment persists for
|
||||
pub beam_duration: Duration,
|
||||
/// Base healing per second
|
||||
pub base_hps: f32,
|
||||
/// Base damage per second
|
||||
pub base_dps: f32,
|
||||
/// Ticks of damage/healing per second
|
||||
/// Base damage per tick
|
||||
pub damage: f32,
|
||||
/// Ticks per second
|
||||
pub tick_rate: f32,
|
||||
/// Max range
|
||||
pub range: f32,
|
||||
@ -37,16 +35,16 @@ pub struct StaticData {
|
||||
/// Lifesteal efficiency (0 gives 0% conversion of damage to health, 1 gives
|
||||
/// 100% conversion of damage to health)
|
||||
pub lifesteal_eff: f32,
|
||||
/// Energy regened per second for damage ticks
|
||||
/// Energy regenerated per tick
|
||||
pub energy_regen: f32,
|
||||
/// Energy consumed per second for heal ticks
|
||||
pub energy_cost: f32,
|
||||
/// Energy drained per
|
||||
/// Energy drained per second
|
||||
pub energy_drain: f32,
|
||||
/// Used to dictate how orientation functions in this state
|
||||
pub orientation_behavior: MovementBehavior,
|
||||
/// What key is used to press ability
|
||||
pub ability_info: AbilityInfo,
|
||||
/// Used to specify the beam to the frontend
|
||||
pub specifier: beam::FrontendSpecifier,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -58,8 +56,6 @@ pub struct Data {
|
||||
pub timer: Duration,
|
||||
/// What section the character stage is in
|
||||
pub stage_section: StageSection,
|
||||
/// Used to offset beam and particles
|
||||
pub offset: Vec3<f32>,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
@ -103,17 +99,10 @@ impl CharacterBehavior for Data {
|
||||
tick_dur: Duration::from_secs_f32(1.0 / self.static_data.tick_rate),
|
||||
timer: Duration::default(),
|
||||
});
|
||||
// Gets offsets
|
||||
let body_offsets = Vec3::new(
|
||||
(data.body.radius() + 1.0) * data.inputs.look_dir.x,
|
||||
(data.body.radius() + 1.0) * data.inputs.look_dir.y,
|
||||
data.body.eye_height(),
|
||||
) * 0.55;
|
||||
// Build up
|
||||
update.character = CharacterState::BasicBeam(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Cast,
|
||||
offset: body_offsets,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
@ -135,27 +124,18 @@ impl CharacterBehavior for Data {
|
||||
let damage = AttackDamage::new(
|
||||
Damage {
|
||||
source: DamageSource::Energy,
|
||||
value: self.static_data.base_dps as f32 / self.static_data.tick_rate,
|
||||
value: self.static_data.damage,
|
||||
},
|
||||
Some(GroupTarget::OutOfGroup),
|
||||
)
|
||||
.with_effect(lifesteal);
|
||||
let heal = AttackEffect::new(
|
||||
Some(GroupTarget::InGroup),
|
||||
CombatEffect::Heal(
|
||||
self.static_data.base_hps as f32 / self.static_data.tick_rate,
|
||||
),
|
||||
)
|
||||
.with_requirement(CombatRequirement::SufficientEnergy(
|
||||
self.static_data.energy_cost,
|
||||
));
|
||||
let (crit_chance, crit_mult) =
|
||||
get_crit_data(data, self.static_data.ability_info);
|
||||
let attack = Attack::default()
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(energy)
|
||||
.with_effect(heal);
|
||||
.with_combo_increment();
|
||||
|
||||
let properties = beam::Properties {
|
||||
attack,
|
||||
@ -163,20 +143,14 @@ impl CharacterBehavior for Data {
|
||||
speed,
|
||||
duration: self.static_data.beam_duration,
|
||||
owner: Some(*data.uid),
|
||||
specifier: self.static_data.specifier,
|
||||
};
|
||||
// Gets offsets
|
||||
let body_offsets = match data.body {
|
||||
Body::Humanoid(_) => Vec3::new(
|
||||
(data.body.radius() + 2.0) * data.inputs.look_dir.x,
|
||||
(data.body.radius() + 2.0) * data.inputs.look_dir.y,
|
||||
data.body.eye_height() * 0.55,
|
||||
),
|
||||
_ => Vec3::new(
|
||||
(data.body.radius() + 3.0) * data.inputs.look_dir.x,
|
||||
(data.body.radius() + 3.0) * data.inputs.look_dir.y,
|
||||
data.body.eye_height() * 0.55,
|
||||
),
|
||||
};
|
||||
let body_offsets = Vec3::new(
|
||||
(data.body.radius() + 1.0) * data.inputs.look_dir.x,
|
||||
(data.body.radius() + 1.0) * data.inputs.look_dir.y,
|
||||
data.body.eye_height() * 0.6,
|
||||
);
|
||||
let pos = Pos(data.pos.0 + body_offsets);
|
||||
// Create beam segment
|
||||
update.server_events.push_front(ServerEvent::BeamSegment {
|
||||
@ -189,7 +163,6 @@ impl CharacterBehavior for Data {
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
offset: body_offsets,
|
||||
..*self
|
||||
});
|
||||
|
||||
|
@ -121,7 +121,8 @@ impl CharacterBehavior for Data {
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(energy)
|
||||
.with_effect(poise)
|
||||
.with_effect(knockback);
|
||||
.with_effect(knockback)
|
||||
.with_combo_increment();
|
||||
|
||||
// Hit attempt
|
||||
data.updater.insert(data.entity, Melee {
|
||||
|
@ -186,7 +186,8 @@ impl CharacterBehavior for Data {
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(poise)
|
||||
.with_effect(knockback);
|
||||
.with_effect(knockback)
|
||||
.with_combo_increment();
|
||||
|
||||
// Hit attempt
|
||||
data.updater.insert(data.entity, Melee {
|
||||
|
@ -129,7 +129,8 @@ impl CharacterBehavior for Data {
|
||||
let attack = Attack::default()
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(knockback);
|
||||
.with_effect(knockback)
|
||||
.with_combo_increment();
|
||||
|
||||
// Fire
|
||||
let projectile = Projectile {
|
||||
|
@ -166,7 +166,8 @@ impl CharacterBehavior for Data {
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(poise)
|
||||
.with_effect(knockback);
|
||||
.with_effect(knockback)
|
||||
.with_combo_increment();
|
||||
|
||||
data.updater.insert(data.entity, Melee {
|
||||
attack,
|
||||
|
167
common/src/states/healing_beam.rs
Normal file
167
common/src/states/healing_beam.rs
Normal file
@ -0,0 +1,167 @@
|
||||
use crate::{
|
||||
combat::{Attack, AttackEffect, CombatEffect, CombatRequirement, GroupTarget},
|
||||
comp::{beam, CharacterState, Ori, Pos, StateUpdate},
|
||||
event::ServerEvent,
|
||||
states::{
|
||||
behavior::{CharacterBehavior, JoinData},
|
||||
utils::*,
|
||||
},
|
||||
uid::Uid,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
/// Separated out to condense update portions of character state
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct StaticData {
|
||||
/// How long until state should deal damage or heal
|
||||
pub buildup_duration: Duration,
|
||||
/// How long the state has until exiting
|
||||
pub recover_duration: Duration,
|
||||
/// How long each beam segment persists for
|
||||
pub beam_duration: Duration,
|
||||
/// Base healing per tick
|
||||
pub heal: f32,
|
||||
/// Ticks of healing per second
|
||||
pub tick_rate: f32,
|
||||
/// Max range
|
||||
pub range: f32,
|
||||
/// Max angle (45.0 will give you a 90.0 angle window)
|
||||
pub max_angle: f32,
|
||||
/// Energy consumed per second for heal ticks
|
||||
pub energy_cost: f32,
|
||||
/// What key is used to press ability
|
||||
pub ability_info: AbilityInfo,
|
||||
/// Used to specify the beam to the frontend
|
||||
pub specifier: beam::FrontendSpecifier,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
/// Struct containing data that does not change over the course of the
|
||||
/// character state
|
||||
pub static_data: StaticData,
|
||||
/// Timer for each stage
|
||||
pub timer: Duration,
|
||||
/// What section the character stage is in
|
||||
pub stage_section: StageSection,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||
let mut update = StateUpdate::from(data);
|
||||
|
||||
handle_move(data, &mut update, 0.4);
|
||||
handle_jump(data, &mut update);
|
||||
if !ability_key_is_pressed(data, self.static_data.ability_info.key) {
|
||||
handle_interrupt(data, &mut update, false);
|
||||
match update.character {
|
||||
CharacterState::HealingBeam(_) => {},
|
||||
_ => {
|
||||
return update;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
match self.stage_section {
|
||||
StageSection::Buildup => {
|
||||
if self.timer < self.static_data.buildup_duration {
|
||||
// Build up
|
||||
update.character = CharacterState::HealingBeam(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Creates beam
|
||||
data.updater.insert(data.entity, beam::Beam {
|
||||
hit_entities: Vec::<Uid>::new(),
|
||||
tick_dur: Duration::from_secs_f32(1.0 / self.static_data.tick_rate),
|
||||
timer: Duration::default(),
|
||||
});
|
||||
// Build up
|
||||
update.character = CharacterState::HealingBeam(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Cast,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Cast => {
|
||||
if ability_key_is_pressed(data, self.static_data.ability_info.key) {
|
||||
let speed =
|
||||
self.static_data.range / self.static_data.beam_duration.as_secs_f32();
|
||||
let heal = AttackEffect::new(
|
||||
Some(GroupTarget::InGroup),
|
||||
CombatEffect::Heal(self.static_data.heal),
|
||||
)
|
||||
.with_requirement(CombatRequirement::Energy(self.static_data.energy_cost))
|
||||
.with_requirement(CombatRequirement::Combo(1));
|
||||
let attack = Attack::default().with_effect(heal);
|
||||
|
||||
let properties = beam::Properties {
|
||||
attack,
|
||||
angle: self.static_data.max_angle.to_radians(),
|
||||
speed,
|
||||
duration: self.static_data.beam_duration,
|
||||
owner: Some(*data.uid),
|
||||
specifier: self.static_data.specifier,
|
||||
};
|
||||
// Gets offsets
|
||||
let body_offsets = Vec3::new(
|
||||
(data.body.radius() + 1.0) * data.inputs.look_dir.x,
|
||||
(data.body.radius() + 1.0) * data.inputs.look_dir.y,
|
||||
data.body.eye_height() * 0.6,
|
||||
);
|
||||
let pos = Pos(data.pos.0 + body_offsets);
|
||||
// Create beam segment
|
||||
update.server_events.push_front(ServerEvent::BeamSegment {
|
||||
properties,
|
||||
pos,
|
||||
ori: Ori::from(data.inputs.look_dir),
|
||||
});
|
||||
update.character = CharacterState::HealingBeam(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
update.character = CharacterState::HealingBeam(Data {
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Recover,
|
||||
..*self
|
||||
});
|
||||
}
|
||||
},
|
||||
StageSection::Recover => {
|
||||
if self.timer < self.static_data.recover_duration {
|
||||
update.character = CharacterState::HealingBeam(Data {
|
||||
timer: self
|
||||
.timer
|
||||
.checked_add(Duration::from_secs_f32(data.dt.0))
|
||||
.unwrap_or_default(),
|
||||
..*self
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
update.character = CharacterState::Wielding;
|
||||
// Make sure attack component is removed
|
||||
data.updater.remove::<beam::Beam>(data.entity);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// If it somehow ends up in an incorrect stage section
|
||||
update.character = CharacterState::Wielding;
|
||||
// Make sure attack component is removed
|
||||
data.updater.remove::<beam::Beam>(data.entity);
|
||||
},
|
||||
}
|
||||
|
||||
update
|
||||
}
|
||||
}
|
@ -176,7 +176,8 @@ impl CharacterBehavior for Data {
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(poise)
|
||||
.with_effect(knockback);
|
||||
.with_effect(knockback)
|
||||
.with_combo_increment();
|
||||
|
||||
// Hit attempt, when animation plays
|
||||
data.updater.insert(data.entity, Melee {
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod basic_aura;
|
||||
pub mod basic_beam;
|
||||
pub mod basic_block;
|
||||
pub mod basic_melee;
|
||||
@ -13,6 +14,7 @@ pub mod dash_melee;
|
||||
pub mod equipping;
|
||||
pub mod glide;
|
||||
pub mod glide_wield;
|
||||
pub mod healing_beam;
|
||||
pub mod idle;
|
||||
pub mod leap_melee;
|
||||
pub mod repeater_ranged;
|
||||
|
@ -106,7 +106,8 @@ impl CharacterBehavior for Data {
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(poise)
|
||||
.with_effect(knockback);
|
||||
.with_effect(knockback)
|
||||
.with_combo_increment();
|
||||
let properties = shockwave::Properties {
|
||||
angle: self.static_data.shockwave_angle,
|
||||
vertical_angle: self.static_data.shockwave_vertical_angle,
|
||||
|
@ -141,7 +141,8 @@ impl CharacterBehavior for Data {
|
||||
.with_damage(damage)
|
||||
.with_crit(crit_chance, crit_mult)
|
||||
.with_effect(poise)
|
||||
.with_effect(knockback);
|
||||
.with_effect(knockback)
|
||||
.with_combo_increment();
|
||||
|
||||
// Hit attempt
|
||||
data.updater.insert(data.entity, Melee {
|
||||
|
@ -7,7 +7,7 @@ use common::{
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
resources::DeltaTime,
|
||||
uid::UidAllocator,
|
||||
uid::{Uid, UidAllocator},
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
@ -26,6 +26,7 @@ pub struct ReadData<'a> {
|
||||
char_states: ReadStorage<'a, CharacterState>,
|
||||
healths: ReadStorage<'a, Health>,
|
||||
groups: ReadStorage<'a, Group>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -79,11 +80,12 @@ impl<'a> System<'a> for Sys {
|
||||
expired_auras.push(key);
|
||||
}
|
||||
}
|
||||
for (target, target_pos, mut target_buffs, health) in (
|
||||
for (target, target_pos, mut target_buffs, health, target_uid) in (
|
||||
&read_data.entities,
|
||||
&read_data.positions,
|
||||
&mut buffs,
|
||||
&read_data.healths,
|
||||
&read_data.uids,
|
||||
)
|
||||
.join()
|
||||
{
|
||||
@ -96,7 +98,8 @@ impl<'a> System<'a> for Sys {
|
||||
.and_then(|e| read_data.groups.get(e))
|
||||
.map_or(false, |owner_group| {
|
||||
Some(owner_group) == read_data.groups.get(target)
|
||||
});
|
||||
})
|
||||
|| *target_uid == uid;
|
||||
|
||||
if !same_group {
|
||||
continue;
|
||||
@ -125,13 +128,16 @@ impl<'a> System<'a> for Sys {
|
||||
if apply_buff {
|
||||
// Checks that target is not already receiving a buff from an
|
||||
// aura, where the buff is of the same kind, and is of at least
|
||||
// the same strength
|
||||
// the same strength and of at least the same duration
|
||||
// If no such buff is present, adds the buff
|
||||
let emit_buff = !target_buffs.buffs.iter().any(|(_, buff)| {
|
||||
buff.cat_ids.iter().any(|cat_id| {
|
||||
matches!(cat_id, BuffCategory::FromAura(_))
|
||||
}) && buff.kind == kind
|
||||
&& buff.data.strength >= data.strength
|
||||
&& buff.time.map_or(true, |dur| {
|
||||
data.duration.map_or(false, |dur_2| dur >= dur_2)
|
||||
})
|
||||
});
|
||||
if emit_buff {
|
||||
use buff::*;
|
||||
|
@ -1,8 +1,8 @@
|
||||
use common::{
|
||||
combat::{AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
Beam, BeamSegment, Body, Energy, Group, Health, HealthSource, Inventory, Last, Ori, Pos,
|
||||
Scale, Stats,
|
||||
Beam, BeamSegment, Body, Combo, Energy, Group, Health, HealthSource, Inventory, Last, Ori,
|
||||
Pos, Scale, Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
resources::{DeltaTime, Time},
|
||||
@ -35,6 +35,7 @@ pub struct ReadData<'a> {
|
||||
groups: ReadStorage<'a, Group>,
|
||||
energies: ReadStorage<'a, Energy>,
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
}
|
||||
|
||||
/// This system is responsible for handling beams that heal or do damage
|
||||
@ -162,6 +163,7 @@ impl<'a> System<'a> for Sys {
|
||||
entity,
|
||||
uid,
|
||||
energy: read_data.energies.get(entity),
|
||||
combo: read_data.combos.get(entity),
|
||||
});
|
||||
|
||||
let target_info = TargetInfo {
|
||||
|
@ -165,8 +165,8 @@ impl<'a> System<'a> for Sys {
|
||||
energy.set_maximum(new_max);
|
||||
},
|
||||
},
|
||||
BuffEffect::ImmuneToAttacks => {
|
||||
stat.damage_reduction = 1.0;
|
||||
BuffEffect::DamageReduction(dr) => {
|
||||
stat.damage_reduction = dr.min(1.0);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -303,6 +303,8 @@ impl<'a> System<'a> for Sys {
|
||||
CharacterState::RepeaterRanged(data) => data.handle_event(&j, action),
|
||||
CharacterState::Shockwave(data) => data.handle_event(&j, action),
|
||||
CharacterState::BasicBeam(data) => data.handle_event(&j, action),
|
||||
CharacterState::BasicAura(data) => data.handle_event(&j, action),
|
||||
CharacterState::HealingBeam(data) => data.handle_event(&j, action),
|
||||
};
|
||||
local_emitter.append(&mut state_update.local_events);
|
||||
server_emitter.append(&mut state_update.server_events);
|
||||
@ -342,6 +344,8 @@ impl<'a> System<'a> for Sys {
|
||||
CharacterState::RepeaterRanged(data) => data.behavior(&j),
|
||||
CharacterState::Shockwave(data) => data.behavior(&j),
|
||||
CharacterState::BasicBeam(data) => data.behavior(&j),
|
||||
CharacterState::BasicAura(data) => data.behavior(&j),
|
||||
CharacterState::HealingBeam(data) => data.behavior(&j),
|
||||
};
|
||||
|
||||
local_emitter.append(&mut state_update.local_events);
|
||||
|
@ -1,6 +1,9 @@
|
||||
use common::{
|
||||
combat::{AttackerInfo, TargetInfo},
|
||||
comp::{Body, CharacterState, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale, Stats},
|
||||
comp::{
|
||||
Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale,
|
||||
Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
uid::Uid,
|
||||
util::Dir,
|
||||
@ -27,6 +30,7 @@ pub struct ReadData<'a> {
|
||||
char_states: ReadStorage<'a, CharacterState>,
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
}
|
||||
|
||||
/// This system is responsible for handling accepted inputs like moving or
|
||||
@ -113,6 +117,7 @@ impl<'a> System<'a> for Sys {
|
||||
entity: attacker,
|
||||
uid: *uid,
|
||||
energy: read_data.energies.get(attacker),
|
||||
combo: read_data.combos.get(attacker),
|
||||
});
|
||||
|
||||
let target_info = TargetInfo {
|
||||
|
@ -1,8 +1,8 @@
|
||||
use common::{
|
||||
combat::{AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
projectile, Energy, Group, HealthSource, Inventory, Ori, PhysicsState, Pos, Projectile,
|
||||
Stats, Vel,
|
||||
projectile, Combo, Energy, Group, HealthSource, Inventory, Ori, PhysicsState, Pos,
|
||||
Projectile, Stats, Vel,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
resources::DeltaTime,
|
||||
@ -30,6 +30,7 @@ pub struct ReadData<'a> {
|
||||
groups: ReadStorage<'a, Group>,
|
||||
energies: ReadStorage<'a, Energy>,
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
}
|
||||
|
||||
/// This system is responsible for handling projectile effect triggers
|
||||
@ -110,6 +111,7 @@ impl<'a> System<'a> for Sys {
|
||||
entity,
|
||||
uid,
|
||||
energy: read_data.energies.get(entity),
|
||||
combo: read_data.combos.get(entity),
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use common::{
|
||||
combat::{AttackerInfo, TargetInfo},
|
||||
comp::{
|
||||
Body, Energy, Group, Health, HealthSource, Inventory, Last, Ori, PhysicsState, Pos, Scale,
|
||||
Shockwave, ShockwaveHitEntities, Stats,
|
||||
Body, Combo, Energy, Group, Health, HealthSource, Inventory, Last, Ori, PhysicsState, Pos,
|
||||
Scale, Shockwave, ShockwaveHitEntities, Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
resources::{DeltaTime, Time},
|
||||
@ -36,6 +36,7 @@ pub struct ReadData<'a> {
|
||||
physics_states: ReadStorage<'a, PhysicsState>,
|
||||
energies: ReadStorage<'a, Energy>,
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
combos: ReadStorage<'a, Combo>,
|
||||
}
|
||||
|
||||
/// This system is responsible for handling accepted inputs like moving or
|
||||
@ -184,6 +185,7 @@ impl<'a> System<'a> for Sys {
|
||||
entity,
|
||||
uid,
|
||||
energy: read_data.energies.get(entity),
|
||||
combo: read_data.combos.get(entity),
|
||||
});
|
||||
|
||||
let target_info = TargetInfo {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use common::{
|
||||
comp::{
|
||||
self,
|
||||
skills::{GeneralSkill, Skill},
|
||||
Body, CharacterState, Combo, Energy, EnergyChange, EnergySource, Health, Poise,
|
||||
PoiseChange, PoiseSource, Pos, Stats,
|
||||
@ -18,7 +19,6 @@ use vek::Vec3;
|
||||
|
||||
const ENERGY_REGEN_ACCEL: f32 = 10.0;
|
||||
const POISE_REGEN_ACCEL: f32 = 2.0;
|
||||
const COMBO_DECAY_START: f64 = 5.0; // seconds
|
||||
|
||||
#[derive(SystemData)]
|
||||
pub struct ReadData<'a> {
|
||||
@ -232,7 +232,9 @@ impl<'a> System<'a> for Sys {
|
||||
| CharacterState::ChargedRanged { .. }
|
||||
| CharacterState::RepeaterRanged { .. }
|
||||
| CharacterState::Shockwave { .. }
|
||||
| CharacterState::BasicBeam { .. } => {
|
||||
| CharacterState::BasicBeam { .. }
|
||||
| CharacterState::BasicAura { .. }
|
||||
| CharacterState::HealingBeam { .. } => {
|
||||
if energy.get_unchecked().regen_rate != 0.0 {
|
||||
energy.get_mut_unchecked().regen_rate = 0.0
|
||||
}
|
||||
@ -262,7 +264,9 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Decay combo
|
||||
for (_, mut combo) in (&read_data.entities, &mut combos).join() {
|
||||
if combo.counter() > 0 && read_data.time.0 - combo.last_increase() > COMBO_DECAY_START {
|
||||
if combo.counter() > 0
|
||||
&& read_data.time.0 - combo.last_increase() > comp::combo::COMBO_DECAY_START
|
||||
{
|
||||
combo.reset();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::{sys, Server, StateExt};
|
||||
use common::{
|
||||
character::CharacterId,
|
||||
combat,
|
||||
comp::{
|
||||
self,
|
||||
aura::{Aura, AuraKind, AuraTarget},
|
||||
@ -175,10 +174,7 @@ pub fn handle_beam(server: &mut Server, properties: beam::Properties, pos: Pos,
|
||||
let ecs = state.ecs();
|
||||
ecs.write_resource::<Vec<Outcome>>().push(Outcome::Beam {
|
||||
pos: pos.0,
|
||||
heal: properties
|
||||
.attack
|
||||
.effects()
|
||||
.any(|e| matches!(e.effect(), combat::CombatEffect::Heal(h) if *h > 0.0)),
|
||||
specifier: properties.specifier,
|
||||
});
|
||||
state.create_beam(properties, pos, ori).build();
|
||||
}
|
||||
|
@ -676,6 +676,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
},
|
||||
RadiusEffect::Attack(attack) => {
|
||||
let energies = &ecs.read_storage::<comp::Energy>();
|
||||
let combos = &ecs.read_storage::<comp::Combo>();
|
||||
for (entity_b, pos_b, _health_b, inventory_b_maybe, stats_b_maybe) in (
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage::<comp::Pos>(),
|
||||
@ -715,6 +716,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
||||
entity,
|
||||
uid,
|
||||
energy: energies.get(entity),
|
||||
combo: combos.get(entity),
|
||||
});
|
||||
|
||||
let target_info = combat::TargetInfo {
|
||||
@ -893,11 +895,14 @@ fn handle_exp_gain(
|
||||
pub fn handle_combo_change(server: &Server, entity: EcsEntity, change: i32) {
|
||||
let ecs = &server.state.ecs();
|
||||
if let Some(mut combo) = ecs.write_storage::<comp::Combo>().get_mut(entity) {
|
||||
if change > 0 {
|
||||
let time = ecs.read_resource::<Time>();
|
||||
combo.increase_by(change as u32, time.0);
|
||||
} else {
|
||||
combo.decrease_by(change.abs() as u32);
|
||||
let time = ecs.read_resource::<Time>();
|
||||
let mut outcomes = ecs.write_resource::<Vec<Outcome>>();
|
||||
combo.change_by(change, time.0);
|
||||
if let Some(uid) = ecs.read_storage::<Uid>().get(entity) {
|
||||
outcomes.push(Outcome::ComboChange {
|
||||
uid: *uid,
|
||||
combo: combo.counter(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
-- This file should undo anything in `up.sql`
|
@ -0,0 +1,16 @@
|
||||
-- Resets sceptre skill tree by deleting scetpre skills and setting available skill points to earned skill points
|
||||
-- Deletes all sceptre skills, does not delete unlock sceptre skill
|
||||
DELETE FROM skill WHERE skill = 'Sceptre BHeal';
|
||||
DELETE FROM skill WHERE skill = 'Sceptre BDamage';
|
||||
DELETE FROM skill WHERE skill = 'Sceptre BRange';
|
||||
DELETE FROM skill WHERE skill = 'Sceptre BLifesteal';
|
||||
DELETE FROM skill WHERE skill = 'Sceptre BRegen';
|
||||
DELETE FROM skill WHERE skill = 'Sceptre BCost';
|
||||
DELETE FROM skill WHERE skill = 'Sceptre PHeal';
|
||||
DELETE FROM skill WHERE skill = 'Sceptre PDamage';
|
||||
DELETE FROM skill WHERE skill = 'Sceptre PRadius';
|
||||
DELETE FROM skill WHERE skill = 'Sceptre PCost';
|
||||
DELETE FROM skill WHERE skill = 'Sceptre PProjSpeed';
|
||||
-- Resets available skill points to earned skill points for sceptre skill tree
|
||||
UPDATE skill_group
|
||||
SET available_sp = earned_sp WHERE skill_group_kind = 'Weapon Sceptre';
|
@ -118,17 +118,18 @@ pub fn skill_to_db_string(skill: comp::skills::Skill) -> String {
|
||||
Staff(StaffSkill::SKnockback) => "Staff SKnockback",
|
||||
Staff(StaffSkill::SRange) => "Staff SRange",
|
||||
Staff(StaffSkill::SCost) => "Staff SCost",
|
||||
Sceptre(SceptreSkill::BHeal) => "Sceptre BHeal",
|
||||
Sceptre(SceptreSkill::BDamage) => "Sceptre BDamage",
|
||||
Sceptre(SceptreSkill::BRange) => "Sceptre BRange",
|
||||
Sceptre(SceptreSkill::BLifesteal) => "Sceptre BLifesteal",
|
||||
Sceptre(SceptreSkill::BRegen) => "Sceptre BRegen",
|
||||
Sceptre(SceptreSkill::BCost) => "Sceptre BCost",
|
||||
Sceptre(SceptreSkill::PHeal) => "Sceptre PHeal",
|
||||
Sceptre(SceptreSkill::PDamage) => "Sceptre PDamage",
|
||||
Sceptre(SceptreSkill::PRadius) => "Sceptre PRadius",
|
||||
Sceptre(SceptreSkill::PCost) => "Sceptre PCost",
|
||||
Sceptre(SceptreSkill::PProjSpeed) => "Sceptre PProjSpeed",
|
||||
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::HCost) => "Sceptre HCost",
|
||||
Sceptre(SceptreSkill::HRange) => "Sceptre HRange",
|
||||
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::ImmuneMelee) => "Roll ImmuneMelee",
|
||||
Roll(RollSkill::Cost) => "Roll Cost",
|
||||
Roll(RollSkill::Strength) => "Roll Strength",
|
||||
@ -240,17 +241,18 @@ pub fn db_string_to_skill(skill_string: &str) -> comp::skills::Skill {
|
||||
"Staff SKnockback" => Staff(StaffSkill::SKnockback),
|
||||
"Staff SRange" => Staff(StaffSkill::SRange),
|
||||
"Staff SCost" => Staff(StaffSkill::SCost),
|
||||
"Sceptre BHeal" => Sceptre(SceptreSkill::BHeal),
|
||||
"Sceptre BDamage" => Sceptre(SceptreSkill::BDamage),
|
||||
"Sceptre BRange" => Sceptre(SceptreSkill::BRange),
|
||||
"Sceptre BLifesteal" => Sceptre(SceptreSkill::BLifesteal),
|
||||
"Sceptre BRegen" => Sceptre(SceptreSkill::BRegen),
|
||||
"Sceptre BCost" => Sceptre(SceptreSkill::BCost),
|
||||
"Sceptre PHeal" => Sceptre(SceptreSkill::PHeal),
|
||||
"Sceptre PDamage" => Sceptre(SceptreSkill::PDamage),
|
||||
"Sceptre PRadius" => Sceptre(SceptreSkill::PRadius),
|
||||
"Sceptre PCost" => Sceptre(SceptreSkill::PCost),
|
||||
"Sceptre PProjSpeed" => Sceptre(SceptreSkill::PProjSpeed),
|
||||
"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 HCost" => Sceptre(SceptreSkill::HCost),
|
||||
"Sceptre HRange" => Sceptre(SceptreSkill::HRange),
|
||||
"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 ImmuneMelee" => Roll(RollSkill::ImmuneMelee),
|
||||
"Roll Cost" => Roll(RollSkill::Cost),
|
||||
"Roll Strength" => Roll(RollSkill::Strength),
|
||||
|
@ -184,6 +184,7 @@ impl StateExt for State {
|
||||
.with(inventory)
|
||||
.with(comp::Buffs::default())
|
||||
.with(comp::Combo::default())
|
||||
.with(comp::Auras::default())
|
||||
}
|
||||
|
||||
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder {
|
||||
@ -275,6 +276,7 @@ impl StateExt for State {
|
||||
comp::Alignment::Owned(self.read_component_copied(entity).unwrap()),
|
||||
);
|
||||
self.write_component(entity, comp::Buffs::default());
|
||||
self.write_component(entity, comp::Auras::default());
|
||||
self.write_component(entity, comp::Combo::default());
|
||||
|
||||
// Make sure physics components are updated
|
||||
|
@ -36,7 +36,7 @@ impl Animation for ShockwaveAnimation {
|
||||
|
||||
let (move1, move2, move3) = match stage_section {
|
||||
Some(StageSection::Buildup) => (anim_time, 0.0, 0.0),
|
||||
Some(StageSection::Swing) => (1.0, anim_time, 0.0),
|
||||
Some(StageSection::Swing) | Some(StageSection::Cast) => (1.0, anim_time, 0.0),
|
||||
Some(StageSection::Recover) => (1.0, 1.0, anim_time),
|
||||
_ => (0.0, 0.0, 0.0),
|
||||
};
|
||||
|
@ -91,6 +91,7 @@ use client::Client;
|
||||
use common::{
|
||||
assets::{self, AssetExt, AssetHandle},
|
||||
comp::{
|
||||
beam,
|
||||
item::{ItemKind, Reagent, ToolKind},
|
||||
object, Body, CharacterAbilityType, InventoryUpdateEvent,
|
||||
},
|
||||
@ -353,16 +354,17 @@ impl SfxMgr {
|
||||
let file_ref = "voxygen.audio.sfx.character.level_up_sound_-_shorter_wind_up";
|
||||
audio.play_sfx(file_ref, *pos, None);
|
||||
},
|
||||
Outcome::Beam { pos, heal } => {
|
||||
if *heal {
|
||||
Outcome::Beam { pos, specifier } => match specifier {
|
||||
beam::FrontendSpecifier::LifestealBeam | beam::FrontendSpecifier::HealingBeam => {
|
||||
let file_ref = "voxygen.audio.sfx.abilities.staff_channeling";
|
||||
audio.play_sfx(file_ref, *pos, None);
|
||||
} else {
|
||||
},
|
||||
beam::FrontendSpecifier::Flamethrower => {
|
||||
let file_ref = "voxygen.audio.sfx.abilities.flame_thrower";
|
||||
audio.play_sfx(file_ref, *pos, None);
|
||||
}
|
||||
},
|
||||
},
|
||||
Outcome::ExpChange { .. } => {},
|
||||
Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => {},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,19 +135,20 @@ widget_ids! {
|
||||
skill_staff_shockwave_3,
|
||||
skill_staff_shockwave_4,
|
||||
sceptre_render,
|
||||
skill_sceptre_beam_0,
|
||||
skill_sceptre_beam_1,
|
||||
skill_sceptre_beam_2,
|
||||
skill_sceptre_beam_3,
|
||||
skill_sceptre_beam_4,
|
||||
skill_sceptre_beam_5,
|
||||
skill_sceptre_beam_6,
|
||||
skill_sceptre_bomb_0,
|
||||
skill_sceptre_bomb_1,
|
||||
skill_sceptre_bomb_2,
|
||||
skill_sceptre_bomb_3,
|
||||
skill_sceptre_bomb_4,
|
||||
skill_sceptre_bomb_5,
|
||||
skill_sceptre_lifesteal_0,
|
||||
skill_sceptre_lifesteal_1,
|
||||
skill_sceptre_lifesteal_2,
|
||||
skill_sceptre_lifesteal_3,
|
||||
skill_sceptre_lifesteal_4,
|
||||
skill_sceptre_heal_0,
|
||||
skill_sceptre_heal_1,
|
||||
skill_sceptre_heal_2,
|
||||
skill_sceptre_heal_3,
|
||||
skill_sceptre_aura_0,
|
||||
skill_sceptre_aura_1,
|
||||
skill_sceptre_aura_2,
|
||||
skill_sceptre_aura_3,
|
||||
skill_sceptre_aura_4,
|
||||
general_combat_render_0,
|
||||
general_combat_render_1,
|
||||
skill_general_stat_0,
|
||||
@ -547,7 +548,7 @@ impl<'a> Widget for Diary<'a> {
|
||||
SelectedSkillTree::Weapon(ToolKind::Hammer) => 5,
|
||||
SelectedSkillTree::Weapon(ToolKind::Bow) => 3,
|
||||
SelectedSkillTree::Weapon(ToolKind::Staff) => 5,
|
||||
SelectedSkillTree::Weapon(ToolKind::Sceptre) => 7,
|
||||
SelectedSkillTree::Weapon(ToolKind::Sceptre) => 5,
|
||||
_ => 0,
|
||||
};
|
||||
let skills_top_r = match sel_tab {
|
||||
@ -557,7 +558,7 @@ impl<'a> Widget for Diary<'a> {
|
||||
SelectedSkillTree::Weapon(ToolKind::Hammer) => 5,
|
||||
SelectedSkillTree::Weapon(ToolKind::Bow) => 7,
|
||||
SelectedSkillTree::Weapon(ToolKind::Staff) => 5,
|
||||
SelectedSkillTree::Weapon(ToolKind::Sceptre) => 6,
|
||||
SelectedSkillTree::Weapon(ToolKind::Sceptre) => 4,
|
||||
_ => 0,
|
||||
};
|
||||
let skills_bot_l = match sel_tab {
|
||||
@ -567,6 +568,7 @@ impl<'a> Widget for Diary<'a> {
|
||||
SelectedSkillTree::Weapon(ToolKind::Hammer) => 6,
|
||||
SelectedSkillTree::Weapon(ToolKind::Bow) => 5,
|
||||
SelectedSkillTree::Weapon(ToolKind::Staff) => 5,
|
||||
SelectedSkillTree::Weapon(ToolKind::Sceptre) => 5,
|
||||
_ => 0,
|
||||
};
|
||||
let skills_bot_r = match sel_tab {
|
||||
@ -3261,20 +3263,20 @@ impl<'a> Widget for Diary<'a> {
|
||||
// 5 1 6
|
||||
// 3 0 4
|
||||
// 8 2 7
|
||||
Button::image(self.imgs.heal_0)
|
||||
Button::image(self.imgs.skill_sceptre_lifesteal)
|
||||
.w_h(74.0, 74.0)
|
||||
.mid_top_with_margin_on(state.skills_top_l[0], 3.0)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_beam_title"),
|
||||
&self.localized_strings.get("hud.skill.sc_beam"),
|
||||
&self.localized_strings.get("hud.skill.sc_lifesteal_title"),
|
||||
&self.localized_strings.get("hud.skill.sc_lifesteal"),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_beam_0, ui);
|
||||
let skill = Skill::Sceptre(BHeal);
|
||||
.set(state.skill_sceptre_lifesteal_0, ui);
|
||||
let skill = Skill::Sceptre(LDamage);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_heal_skill,
|
||||
self.imgs.magic_damage_skill,
|
||||
state.skills_top_l[1],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
@ -3283,9 +3285,11 @@ impl<'a> Widget for Diary<'a> {
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_beam_heal_title"),
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_lifesteal_damage_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_beam_heal"),
|
||||
&self.localized_strings.get("hud.skill.sc_lifesteal_damage"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
@ -3293,14 +3297,14 @@ impl<'a> Widget for Diary<'a> {
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_beam_1, ui)
|
||||
.set(state.skill_sceptre_lifesteal_1, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(BDamage);
|
||||
let skill = Skill::Sceptre(LRange);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_damage_skill,
|
||||
self.imgs.magic_distance_skill,
|
||||
state.skills_top_l[2],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
@ -3309,9 +3313,11 @@ impl<'a> Widget for Diary<'a> {
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_beam_damage_title"),
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_lifesteal_range_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_beam_damage"),
|
||||
&self.localized_strings.get("hud.skill.sc_lifesteal_range"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
@ -3319,14 +3325,14 @@ impl<'a> Widget for Diary<'a> {
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_beam_2, ui)
|
||||
.set(state.skill_sceptre_lifesteal_2, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(BRegen);
|
||||
let skill = Skill::Sceptre(LLifesteal);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_energy_regen_skill,
|
||||
self.imgs.magic_lifesteal_skill,
|
||||
state.skills_top_l[3],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
@ -3337,9 +3343,11 @@ impl<'a> Widget for Diary<'a> {
|
||||
self.tooltip_manager,
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_energy_regen_title"),
|
||||
.get("hud.skill.sc_lifesteal_lifesteal_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_energy_regen"),
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_lifesteal_lifesteal"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
@ -3347,55 +3355,27 @@ impl<'a> Widget for Diary<'a> {
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_beam_3, ui)
|
||||
.set(state.skill_sceptre_lifesteal_3, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(BRange);
|
||||
let skill = Skill::Sceptre(LRegen);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_radius_skill,
|
||||
self.imgs.magic_energy_regen_skill,
|
||||
state.skills_top_l[4],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
&get_skill_label(skill, &self.stats.skill_set),
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_range_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_range"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_beam_4, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(BLifesteal);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_lifesteal_skill,
|
||||
state.skills_top_l[5],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
&get_skill_label(skill, &self.stats.skill_set),
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_lifesteal_efficiency_title"),
|
||||
.get("hud.skill.sc_lifesteal_regen_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_lifesteal_efficiency"),
|
||||
&self.localized_strings.get("hud.skill.sc_lifesteal_regen"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
@ -3403,15 +3383,53 @@ impl<'a> Widget for Diary<'a> {
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_beam_5, ui)
|
||||
.set(state.skill_sceptre_lifesteal_4, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(BCost);
|
||||
// Top right skills
|
||||
Button::image(self.imgs.skill_sceptre_heal)
|
||||
.w_h(74.0, 74.0)
|
||||
.mid_top_with_margin_on(state.skills_top_r[0], 3.0)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_heal_title"),
|
||||
&self.localized_strings.get("hud.skill.sc_heal"),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_heal_0, ui);
|
||||
let skill = Skill::Sceptre(HHeal);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_heal_skill,
|
||||
state.skills_top_r[1],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
&get_skill_label(skill, &self.stats.skill_set),
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_heal_heal_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_heal_heal"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_heal_1, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(HCost);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_cost_skill,
|
||||
state.skills_top_l[6],
|
||||
state.skills_top_r[2],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
@ -3429,78 +3447,14 @@ impl<'a> Widget for Diary<'a> {
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_beam_6, ui)
|
||||
.set(state.skill_sceptre_heal_2, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
Button::image(self.imgs.heal_bomb)
|
||||
.w_h(74.0, 74.0)
|
||||
.mid_top_with_margin_on(state.skills_top_r[0], 3.0)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_healbomb_title"),
|
||||
&self.localized_strings.get("hud.skill.sc_healbomb"),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_bomb_0, ui);
|
||||
// Top right skills
|
||||
let skill = Skill::Sceptre(PHeal);
|
||||
let skill = Skill::Sceptre(HRange);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_heal_skill,
|
||||
state.skills_top_r[1],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
&get_skill_label(skill, &self.stats.skill_set),
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_heal_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_heal"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_bomb_1, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(PDamage);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_damage_skill,
|
||||
state.skills_top_r[2],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
&get_skill_label(skill, &self.stats.skill_set),
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_damage_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_damage"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_bomb_2, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(PRadius);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_radius_skill,
|
||||
self.imgs.heal_distance_skill,
|
||||
state.skills_top_r[3],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
@ -3509,9 +3463,9 @@ impl<'a> Widget for Diary<'a> {
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_radius_title"),
|
||||
&self.localized_strings.get("hud.skill.sc_heal_range_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_radius"),
|
||||
&self.localized_strings.get("hud.skill.sc_heal_range"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
@ -3519,41 +3473,16 @@ impl<'a> Widget for Diary<'a> {
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_bomb_3, ui)
|
||||
.set(state.skill_sceptre_heal_3, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(PCost);
|
||||
// Bottom left skills
|
||||
let skill = Skill::Sceptre(UnlockAura);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_cost_skill,
|
||||
state.skills_top_r[4],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
&get_skill_label(skill, &self.stats.skill_set),
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self.localized_strings.get("hud.skill.sc_energy_cost_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_energy_cost"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_bomb_4, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(PProjSpeed);
|
||||
if create_skill_button(
|
||||
self.imgs.heal_projectile_speed_skill,
|
||||
state.skills_top_r[5],
|
||||
self.imgs.skill_sceptre_aura,
|
||||
state.skills_bot_l[0],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
@ -3563,9 +3492,9 @@ impl<'a> Widget for Diary<'a> {
|
||||
self.tooltip_manager,
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_projectile_speed_title"),
|
||||
.get("hud.skill.sc_wardaura_unlock_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_projectile_speed"),
|
||||
&self.localized_strings.get("hud.skill.sc_wardaura_unlock"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
@ -3573,7 +3502,119 @@ impl<'a> Widget for Diary<'a> {
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_bomb_5, ui)
|
||||
.set(state.skill_sceptre_aura_0, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(AStrength);
|
||||
if create_skill_button(
|
||||
self.imgs.buff_damage_skill,
|
||||
state.skills_bot_l[1],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
&get_skill_label(skill, &self.stats.skill_set),
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_wardaura_strength_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_wardaura_strength"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_aura_1, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(ADuration);
|
||||
if create_skill_button(
|
||||
self.imgs.buff_speed_skill,
|
||||
state.skills_bot_l[2],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
&get_skill_label(skill, &self.stats.skill_set),
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_wardaura_duration_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_wardaura_duration"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_aura_2, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(ARange);
|
||||
if create_skill_button(
|
||||
self.imgs.buff_radius_skill,
|
||||
state.skills_bot_l[3],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
&get_skill_label(skill, &self.stats.skill_set),
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_wardaura_range_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_wardaura_range"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_aura_3, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
};
|
||||
let skill = Skill::Sceptre(ACost);
|
||||
if create_skill_button(
|
||||
self.imgs.buff_cost_skill,
|
||||
state.skills_bot_l[4],
|
||||
&self.stats.skill_set,
|
||||
skill,
|
||||
self.fonts,
|
||||
&get_skill_label(skill, &self.stats.skill_set),
|
||||
)
|
||||
.with_tooltip(
|
||||
self.tooltip_manager,
|
||||
&self
|
||||
.localized_strings
|
||||
.get("hud.skill.sc_wardaura_cost_title"),
|
||||
&add_sp_cost_tooltip(
|
||||
&self.localized_strings.get("hud.skill.sc_wardaura_cost"),
|
||||
skill,
|
||||
&self.stats.skill_set,
|
||||
&self.localized_strings,
|
||||
),
|
||||
&diary_tooltip,
|
||||
TEXT_COLOR,
|
||||
)
|
||||
.set(state.skill_sceptre_aura_4, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::UnlockSkill(skill));
|
||||
|
@ -446,12 +446,13 @@ image_ids! {
|
||||
|
||||
// Icons
|
||||
snake_arrow_0: "voxygen.element.icons.snake",
|
||||
heal_0: "voxygen.element.icons.heal_0",
|
||||
skill_sceptre_lifesteal: "voxygen.element.icons.lifesteal",
|
||||
sword_whirlwind: "voxygen.element.icons.sword_whirlwind",
|
||||
heal_bomb: "voxygen.element.icons.heal_bomb",
|
||||
skill_sceptre_heal: "voxygen.element.icons.heal_0",
|
||||
hammerleap: "voxygen.element.icons.skill_hammerleap",
|
||||
skill_axe_leap_slash: "voxygen.element.icons.skill_axe_leap_slash",
|
||||
skill_bow_jump_burst: "voxygen.element.icons.skill_bow_jump_burst",
|
||||
skill_sceptre_aura: "voxygen.element.icons.sceptre_protection",
|
||||
missing_icon: "voxygen.element.icons.missing_icon_grey",
|
||||
|
||||
// Buttons
|
||||
@ -541,6 +542,7 @@ image_ids! {
|
||||
buff_energyplus_0: "voxygen.element.icons.de_buffs.buff_energyplus_0",
|
||||
buff_healthplus_0: "voxygen.element.icons.de_buffs.buff_healthplus_0",
|
||||
buff_invincibility_0: "voxygen.element.icons.de_buffs.buff_invincibility_0",
|
||||
buff_dmg_red_0: "voxygen.element.icons.de_buffs.buff_damage_reduce_0",
|
||||
|
||||
// Debuffs
|
||||
debuff_skull_0: "voxygen.element.icons.de_buffs.debuff_skull_0",
|
||||
|
@ -61,7 +61,6 @@ use crate::{
|
||||
GlobalState,
|
||||
};
|
||||
use client::Client;
|
||||
|
||||
use common::{
|
||||
combat,
|
||||
comp::{
|
||||
@ -309,6 +308,13 @@ pub struct SkillPointGain {
|
||||
pub timer: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ComboFloater {
|
||||
pub owner: Uid,
|
||||
pub combo: u32,
|
||||
pub timer: f64,
|
||||
}
|
||||
|
||||
pub struct DebugInfo {
|
||||
pub tps: f64,
|
||||
pub frame_time: Duration,
|
||||
@ -732,6 +738,7 @@ pub struct Hud {
|
||||
crosshair_opacity: f32,
|
||||
exp_floaters: Vec<ExpFloater>,
|
||||
skill_point_displays: Vec<SkillPointGain>,
|
||||
combo_floaters: VecDeque<ComboFloater>,
|
||||
}
|
||||
|
||||
impl Hud {
|
||||
@ -840,6 +847,7 @@ impl Hud {
|
||||
crosshair_opacity: 0.0,
|
||||
exp_floaters: Vec::new(),
|
||||
skill_point_displays: Vec::new(),
|
||||
combo_floaters: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -2158,6 +2166,19 @@ impl Hud {
|
||||
let controllers = ecs.read_storage::<comp::Controller>();
|
||||
let ability_map = ecs.fetch::<comp::item::tool::AbilityMap>();
|
||||
let bodies = ecs.read_storage::<comp::Body>();
|
||||
// Combo floater stuffs
|
||||
for combo_floater in self.combo_floaters.iter_mut() {
|
||||
combo_floater.timer -= dt.as_secs_f64();
|
||||
}
|
||||
self.combo_floaters.retain(|f| f.timer > 0_f64);
|
||||
let combo = if let Some(uid) = ecs.read_storage::<Uid>().get(entity) {
|
||||
self.combo_floaters
|
||||
.iter()
|
||||
.find(|c| c.owner == *uid)
|
||||
.copied()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let (
|
||||
Some(health),
|
||||
@ -2190,6 +2211,7 @@ impl Hud {
|
||||
i18n,
|
||||
&ability_map,
|
||||
&msm,
|
||||
combo,
|
||||
)
|
||||
.set(self.ids.skillbar, ui_widgets);
|
||||
}
|
||||
@ -3203,6 +3225,11 @@ impl Hud {
|
||||
total_points: *total_points,
|
||||
timer: 5.0,
|
||||
}),
|
||||
Outcome::ComboChange { uid, combo } => self.combo_floaters.push_front(ComboFloater {
|
||||
owner: *uid,
|
||||
combo: *combo,
|
||||
timer: comp::combo::COMBO_DECAY_START,
|
||||
}),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
@ -3278,6 +3305,7 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
|
||||
BuffKind::IncreaseMaxEnergy { .. } => imgs.buff_energyplus_0,
|
||||
BuffKind::IncreaseMaxHealth { .. } => imgs.buff_healthplus_0,
|
||||
BuffKind::Invulnerability => imgs.buff_invincibility_0,
|
||||
BuffKind::ProtectingWard => imgs.buff_dmg_red_0,
|
||||
// Debuffs
|
||||
BuffKind::Bleeding { .. } => imgs.debuff_bleed_0,
|
||||
BuffKind::Cursed { .. } => imgs.debuff_skull_0,
|
||||
@ -3294,6 +3322,7 @@ pub fn get_buff_title(buff: BuffKind, localized_strings: &Localization) -> &str
|
||||
BuffKind::IncreaseMaxHealth { .. } => localized_strings.get("buff.title.IncreaseMaxHealth"),
|
||||
BuffKind::IncreaseMaxEnergy { .. } => localized_strings.get("buff.title.staminaup"),
|
||||
BuffKind::Invulnerability => localized_strings.get("buff.title.invulnerability"),
|
||||
BuffKind::ProtectingWard => localized_strings.get("buff.title.protectingward"),
|
||||
// Debuffs
|
||||
BuffKind::Bleeding { .. } => localized_strings.get("buff.title.bleed"),
|
||||
BuffKind::Cursed { .. } => localized_strings.get("buff.title.cursed"),
|
||||
@ -3310,6 +3339,7 @@ pub fn get_buff_desc(buff: BuffKind, localized_strings: &Localization) -> &str {
|
||||
BuffKind::IncreaseMaxHealth { .. } => localized_strings.get("buff.desc.IncreaseMaxHealth"),
|
||||
BuffKind::IncreaseMaxEnergy { .. } => localized_strings.get("buff.desc.IncreaseMaxEnergy"),
|
||||
BuffKind::Invulnerability => localized_strings.get("buff.desc.invulnerability"),
|
||||
BuffKind::ProtectingWard => localized_strings.get("buff.desc.protectingward"),
|
||||
// Debuffs
|
||||
BuffKind::Bleeding { .. } => localized_strings.get("buff.desc.bleed"),
|
||||
BuffKind::Cursed { .. } => localized_strings.get("buff.desc.cursed"),
|
||||
|
@ -6,6 +6,7 @@ use super::{
|
||||
STAMINA_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0,
|
||||
};
|
||||
use crate::{
|
||||
hud::ComboFloater,
|
||||
i18n::Localization,
|
||||
ui::{
|
||||
fonts::Fonts,
|
||||
@ -16,6 +17,7 @@ use crate::{
|
||||
GlobalState,
|
||||
};
|
||||
use common::comp::{
|
||||
self,
|
||||
inventory::slot::EquipSlot,
|
||||
item::{
|
||||
tool::{AbilityMap, Tool, ToolKind},
|
||||
@ -74,6 +76,10 @@ widget_ids! {
|
||||
stamina_txt_alignment,
|
||||
stamina_txt_bg,
|
||||
stamina_txt,
|
||||
// Combo Counter
|
||||
combo_align,
|
||||
combo_bg,
|
||||
combo,
|
||||
// Slots
|
||||
m1_slot,
|
||||
m1_slot_bg,
|
||||
@ -141,6 +147,7 @@ pub struct Skillbar<'a> {
|
||||
common: widget::CommonBuilder,
|
||||
ability_map: &'a AbilityMap,
|
||||
msm: &'a MaterialStatManifest,
|
||||
combo: Option<ComboFloater>,
|
||||
}
|
||||
|
||||
impl<'a> Skillbar<'a> {
|
||||
@ -163,6 +170,7 @@ impl<'a> Skillbar<'a> {
|
||||
localized_strings: &'a Localization,
|
||||
ability_map: &'a AbilityMap,
|
||||
msm: &'a MaterialStatManifest,
|
||||
combo: Option<ComboFloater>,
|
||||
) -> Self {
|
||||
Self {
|
||||
global_state,
|
||||
@ -183,6 +191,7 @@ impl<'a> Skillbar<'a> {
|
||||
localized_strings,
|
||||
ability_map,
|
||||
msm,
|
||||
combo,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -575,7 +584,7 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
ToolKind::Hammer => self.imgs.twohhammer_m1,
|
||||
ToolKind::Axe => self.imgs.twohaxe_m1,
|
||||
ToolKind::Bow => self.imgs.bow_m1,
|
||||
ToolKind::Sceptre => self.imgs.heal_0,
|
||||
ToolKind::Sceptre => self.imgs.skill_sceptre_lifesteal,
|
||||
ToolKind::Staff => self.imgs.fireball,
|
||||
ToolKind::Debug => self.imgs.flyingrod_m1,
|
||||
_ => self.imgs.nothing,
|
||||
@ -622,7 +631,7 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
Some(ToolKind::Hammer) => self.imgs.hammergolf,
|
||||
Some(ToolKind::Axe) => self.imgs.axespin,
|
||||
Some(ToolKind::Bow) => self.imgs.bow_m2,
|
||||
Some(ToolKind::Sceptre) => self.imgs.heal_bomb,
|
||||
Some(ToolKind::Sceptre) => self.imgs.skill_sceptre_heal,
|
||||
Some(ToolKind::Staff) => self.imgs.flamethrower,
|
||||
Some(ToolKind::Debug) => self.imgs.flyingrod_m2,
|
||||
_ => self.imgs.nothing,
|
||||
@ -909,6 +918,44 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
.w_h(16.0, 18.0)
|
||||
.mid_bottom_with_margin_on(state.ids.m2_content, -11.0)
|
||||
.set(state.ids.m2_ico, ui);
|
||||
|
||||
// Combo Counter
|
||||
if let Some(combo) = self.combo {
|
||||
if combo.combo > 0 {
|
||||
let combo_txt = format!("{} Combo", combo.combo);
|
||||
let combo_cnt = combo.combo as f32;
|
||||
let time_since_last_update = comp::combo::COMBO_DECAY_START - combo.timer;
|
||||
let alpha = (1.0 - time_since_last_update * 0.2).min(1.0) as f32;
|
||||
let fnt_col = Color::Rgba(
|
||||
// White -> Yellow -> Red text color gradient depending on count
|
||||
(1.0 - combo_cnt / (combo_cnt + 20.0)).max(0.79),
|
||||
(1.0 - combo_cnt / (combo_cnt + 80.0)).max(0.19),
|
||||
(1.0 - combo_cnt / (combo_cnt + 5.0)).max(0.17),
|
||||
alpha,
|
||||
);
|
||||
|
||||
let fnt_size = ((14.0 + combo.timer as f32 * 0.8).min(30.0)) as u32
|
||||
+ if (time_since_last_update) < 0.1 { 2 } else { 0 }; // Increase size for higher counts, "flash" on update by increasing the font size by 2
|
||||
Rectangle::fill_with([10.0, 10.0], color::TRANSPARENT)
|
||||
.middle_of(ui.window)
|
||||
.set(state.ids.combo_align, ui);
|
||||
Text::new(combo_txt.as_str())
|
||||
.mid_bottom_with_margin_on(
|
||||
state.ids.combo_align,
|
||||
-350.0 + time_since_last_update * -8.0,
|
||||
)
|
||||
.font_size(self.fonts.cyri.scale(fnt_size))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(Color::Rgba(0.0, 0.0, 0.0, alpha))
|
||||
.set(state.ids.combo_bg, ui);
|
||||
Text::new(combo_txt.as_str())
|
||||
.bottom_right_with_margins_on(state.ids.combo_bg, 1.0, 1.0)
|
||||
.font_size(self.fonts.cyri.scale(fnt_size))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(fnt_col)
|
||||
.set(state.ids.combo, ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -936,6 +983,10 @@ fn ability_description(tool: &ToolKind) -> Option<(&str, &str)> {
|
||||
"Possessing Arrow",
|
||||
"\nShoots a poisonous arrow.\nLets you control your target.",
|
||||
)),
|
||||
ToolKind::Sceptre => Some((
|
||||
"Thorn Bulwark",
|
||||
"\nProtects you and your group with thorns\nfor a short amount of time.",
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +116,7 @@ pub enum HotbarImage {
|
||||
HammerLeap,
|
||||
AxeLeapSlash,
|
||||
BowJumpBurst,
|
||||
SceptreAura,
|
||||
}
|
||||
|
||||
type HotbarSource<'a> = (
|
||||
@ -239,6 +240,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
|
||||
HotbarImage::HammerLeap => vec![imgs.hammerleap],
|
||||
HotbarImage::AxeLeapSlash => vec![imgs.skill_axe_leap_slash],
|
||||
HotbarImage::BowJumpBurst => vec![imgs.skill_bow_jump_burst],
|
||||
HotbarImage::SceptreAura => vec![imgs.skill_sceptre_aura],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -268,6 +270,7 @@ fn hotbar_image(tool: ToolKind) -> Option<HotbarImage> {
|
||||
ToolKind::Bow => Some(HotbarImage::BowJumpBurst),
|
||||
ToolKind::Debug => Some(HotbarImage::SnakeArrow),
|
||||
ToolKind::Sword => Some(HotbarImage::SwordWhirlwind),
|
||||
ToolKind::Sceptre => Some(HotbarImage::SceptreAura),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,10 @@ fn consumable_desc(effects: &[Effect], desc: &str) -> String {
|
||||
format!("Raises Maximum Health by {}", strength)
|
||||
},
|
||||
BuffKind::Invulnerability => "Grants invulnerability".to_string(),
|
||||
BuffKind::Bleeding | BuffKind::CampfireHeal | BuffKind::Cursed => continue,
|
||||
BuffKind::Bleeding
|
||||
| BuffKind::CampfireHeal
|
||||
| BuffKind::Cursed
|
||||
| BuffKind::ProtectingWard => continue,
|
||||
};
|
||||
|
||||
write!(&mut description, "\n\n{}", buff_desc).unwrap();
|
||||
@ -125,7 +128,8 @@ fn consumable_desc(effects: &[Effect], desc: &str) -> String {
|
||||
BuffKind::Bleeding
|
||||
| BuffKind::Potion
|
||||
| BuffKind::CampfireHeal
|
||||
| BuffKind::Cursed => continue,
|
||||
| BuffKind::Cursed
|
||||
| BuffKind::ProtectingWard => continue,
|
||||
}
|
||||
} else if let BuffKind::Saturation | BuffKind::Regeneration = buff.kind {
|
||||
"every second".to_string()
|
||||
|
@ -118,6 +118,7 @@ pub enum ParticleMode {
|
||||
Snow = 19,
|
||||
Explosion = 20,
|
||||
Ice = 21,
|
||||
LifestealBeam = 22,
|
||||
}
|
||||
|
||||
impl ParticleMode {
|
||||
|
@ -1099,6 +1099,34 @@ impl FigureMgr {
|
||||
skeleton_attr,
|
||||
)
|
||||
},
|
||||
CharacterState::BasicAura(s) => {
|
||||
let stage_time = s.timer.as_secs_f32();
|
||||
let stage_progress = match s.stage_section {
|
||||
StageSection::Buildup => {
|
||||
stage_time / s.static_data.buildup_duration.as_secs_f32()
|
||||
},
|
||||
StageSection::Cast => {
|
||||
stage_time / s.static_data.cast_duration.as_secs_f32()
|
||||
},
|
||||
StageSection::Recover => {
|
||||
stage_time / s.static_data.recover_duration.as_secs_f32()
|
||||
},
|
||||
_ => 0.0,
|
||||
};
|
||||
anim::character::ShockwaveAnimation::update_skeleton(
|
||||
&target_base,
|
||||
(
|
||||
active_tool_kind,
|
||||
second_tool_kind,
|
||||
time,
|
||||
vel.0.magnitude(),
|
||||
Some(s.stage_section),
|
||||
),
|
||||
stage_progress,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
)
|
||||
},
|
||||
CharacterState::LeapMelee(s) => {
|
||||
let stage_progress = match active_tool_kind {
|
||||
Some(ToolKind::Axe | ToolKind::Hammer) => {
|
||||
@ -1252,6 +1280,32 @@ impl FigureMgr {
|
||||
skeleton_attr,
|
||||
)
|
||||
},
|
||||
CharacterState::HealingBeam(s) => {
|
||||
let stage_time = s.timer.as_secs_f32();
|
||||
let stage_progress = match s.stage_section {
|
||||
StageSection::Buildup => {
|
||||
stage_time / s.static_data.buildup_duration.as_secs_f32()
|
||||
},
|
||||
StageSection::Cast => s.timer.as_secs_f32(),
|
||||
StageSection::Recover => {
|
||||
stage_time / s.static_data.recover_duration.as_secs_f32()
|
||||
},
|
||||
_ => 0.0,
|
||||
};
|
||||
anim::character::BeamAnimation::update_skeleton(
|
||||
&target_base,
|
||||
(
|
||||
active_tool_kind,
|
||||
second_tool_kind,
|
||||
time,
|
||||
vel.0.magnitude(),
|
||||
Some(s.stage_section),
|
||||
),
|
||||
stage_progress,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
)
|
||||
},
|
||||
CharacterState::ComboMelee(s) => {
|
||||
let stage_index = (s.stage - 1) as usize;
|
||||
let stage_time = s.timer.as_secs_f32();
|
||||
|
@ -8,8 +8,10 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
assets::{AssetExt, DotVoxAsset},
|
||||
combat::CombatEffect,
|
||||
comp::{item::Reagent, object, BeamSegment, Body, CharacterState, Ori, Pos, Shockwave},
|
||||
comp::{
|
||||
self, aura, beam, buff, item::Reagent, object, BeamSegment, Body, CharacterState, Ori, Pos,
|
||||
Shockwave,
|
||||
},
|
||||
figure::Segment,
|
||||
outcome::Outcome,
|
||||
resources::DeltaTime,
|
||||
@ -180,6 +182,7 @@ impl ParticleMgr {
|
||||
self.maintain_beam_particles(scene_data, lights);
|
||||
self.maintain_block_particles(scene_data, terrain);
|
||||
self.maintain_shockwave_particles(scene_data);
|
||||
self.maintain_aura_particles(scene_data);
|
||||
} else {
|
||||
// remove all particle lifespans
|
||||
self.particles.clear();
|
||||
@ -401,104 +404,113 @@ impl ParticleMgr {
|
||||
.join()
|
||||
.filter(|(_, _, b)| b.creation.map_or(true, |c| (c + dt as f64) >= time))
|
||||
{
|
||||
//
|
||||
// TODO: Handle this less hackily. Done this way as beam segments are created
|
||||
// every server tick, which is approximately 33 ms. Heartbeat scheduler used to
|
||||
// account for clients with less than 30 fps because they start the creation
|
||||
// time when the segments are received and could receive 2 at once
|
||||
let beam_tick_count = 33.max(self.scheduler.heartbeats(Duration::from_millis(1)));
|
||||
let range = beam.properties.speed * beam.properties.duration.as_secs_f32();
|
||||
if beam
|
||||
.properties
|
||||
.attack
|
||||
.effects()
|
||||
.any(|e| matches!(e.effect(), CombatEffect::Heal(h) if *h > 0.0))
|
||||
{
|
||||
// Emit a light when using healing
|
||||
lights.push(Light::new(pos.0, Rgb::new(0.1, 1.0, 0.15), 1.0));
|
||||
for i in 0..self.scheduler.heartbeats(Duration::from_millis(1)) {
|
||||
self.particles.push(Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
time + i as f64 / 1000.0,
|
||||
ParticleMode::HealingBeam,
|
||||
match beam.properties.specifier {
|
||||
beam::FrontendSpecifier::Flamethrower => {
|
||||
let mut rng = thread_rng();
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
// Emit a light when using flames
|
||||
lights.push(Light::new(
|
||||
pos.0,
|
||||
pos.0 + *ori.look_dir() * range,
|
||||
Rgb::new(1.0, 0.25, 0.05).map(|e| e * rng.gen_range(0.8..1.2)),
|
||||
2.0,
|
||||
));
|
||||
/*
|
||||
if let CharacterState::BasicBeam(b) = character_state {
|
||||
if b.stage_section == StageSection::Cast {
|
||||
if b.static_data.base_hps > 0.0 {//
|
||||
// Emit a light when using healing
|
||||
lights.push(Light::new(pos.0 + b.offset, Rgb::new(0.1, 1.0, 0.15), 1.0));
|
||||
for i in 0..self.scheduler.heartbeats(Duration::from_millis(1)) {
|
||||
self.particles.push(Particle::new_directed(
|
||||
b.static_data.beam_duration,
|
||||
time + i as f64 / 1000.0,
|
||||
ParticleMode::HealingBeam,
|
||||
pos.0 + particle_ori * 0.5 + b.offset,
|
||||
pos.0 + particle_ori * b.static_data.range + b.offset,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
let mut rng = thread_rng();
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), particle_ori);
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
// Emit a light when using flames
|
||||
lights.push(Light::new(
|
||||
pos.0 + b.offset,
|
||||
Rgb::new(1.0, 0.25, 0.05).map(|e| e * rng.gen_range(0.8..1.2)),
|
||||
2.0,
|
||||
));
|
||||
self.particles.resize_with(
|
||||
self.particles.len()
|
||||
+ 2 * usize::from(
|
||||
self.scheduler.heartbeats(Duration::from_millis(1)),
|
||||
),
|
||||
|| {
|
||||
let phi: f32 =
|
||||
rng.gen_range(0.0..b.static_data.max_angle.to_radians());
|
||||
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
|
||||
let offset_z = Vec3::new(
|
||||
phi.sin() * theta.cos(),
|
||||
phi.sin() * theta.sin(),
|
||||
phi.cos(),
|
||||
);
|
||||
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
|
||||
Particle::new_directed(
|
||||
b.static_data.beam_duration,
|
||||
time,
|
||||
ParticleMode::FlameThrower,
|
||||
pos.0 + random_ori * 0.5 + b.offset,
|
||||
pos.0 + random_ori * b.static_data.range + b.offset,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
let mut rng = thread_rng();
|
||||
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
|
||||
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
|
||||
// Emit a light when using flames
|
||||
lights.push(Light::new(
|
||||
pos.0,
|
||||
Rgb::new(1.0, 0.25, 0.05).map(|e| e * rng.gen_range(0.8..1.2)),
|
||||
2.0,
|
||||
));
|
||||
self.particles.resize_with(
|
||||
self.particles.len()
|
||||
+ 2 * usize::from(self.scheduler.heartbeats(Duration::from_millis(1))),
|
||||
|| {
|
||||
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
|
||||
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
|
||||
let offset_z =
|
||||
Vec3::new(phi.sin() * theta.cos(), phi.sin() * theta.sin(), phi.cos());
|
||||
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
|
||||
Particle::new_directed(
|
||||
self.particles.resize_with(
|
||||
self.particles.len() + 2 * usize::from(beam_tick_count),
|
||||
|| {
|
||||
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
|
||||
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
|
||||
let offset_z = Vec3::new(
|
||||
phi.sin() * theta.cos(),
|
||||
phi.sin() * theta.sin(),
|
||||
phi.cos(),
|
||||
);
|
||||
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
|
||||
Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
time,
|
||||
ParticleMode::FlameThrower,
|
||||
pos.0,
|
||||
pos.0 + random_ori * range,
|
||||
)
|
||||
},
|
||||
);
|
||||
},
|
||||
beam::FrontendSpecifier::HealingBeam => {
|
||||
// Emit a light when using healing
|
||||
lights.push(Light::new(pos.0, Rgb::new(0.1, 1.0, 0.15), 1.0));
|
||||
for i in 0..beam_tick_count {
|
||||
self.particles.push(Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
time,
|
||||
ParticleMode::FlameThrower,
|
||||
pos.0, /* + random_ori */
|
||||
pos.0 + random_ori * range,
|
||||
)
|
||||
time + i as f64 / 1000.0,
|
||||
ParticleMode::HealingBeam,
|
||||
pos.0,
|
||||
pos.0 + *ori.look_dir() * range,
|
||||
));
|
||||
}
|
||||
},
|
||||
beam::FrontendSpecifier::LifestealBeam => {
|
||||
// Emit a light when using lifesteal beam
|
||||
lights.push(Light::new(pos.0, Rgb::new(0.8, 1.0, 0.5), 1.0));
|
||||
for i in 0..beam_tick_count {
|
||||
self.particles.push(Particle::new_directed(
|
||||
beam.properties.duration,
|
||||
time + i as f64 / 1000.0,
|
||||
ParticleMode::LifestealBeam,
|
||||
pos.0,
|
||||
pos.0 + *ori.look_dir() * range,
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maintain_aura_particles(&mut self, scene_data: &SceneData) {
|
||||
let state = scene_data.state;
|
||||
let ecs = state.ecs();
|
||||
let time = state.get_time();
|
||||
|
||||
for (pos, auras) in (
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<comp::Auras>(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
for (_, aura) in auras.auras.iter() {
|
||||
#[allow(clippy::single_match)]
|
||||
match aura.aura_kind {
|
||||
aura::AuraKind::Buff {
|
||||
kind: buff::BuffKind::ProtectingWard,
|
||||
..
|
||||
} => {
|
||||
let mut rng = thread_rng();
|
||||
let heartbeats = self.scheduler.heartbeats(Duration::from_millis(5));
|
||||
self.particles.resize_with(
|
||||
self.particles.len()
|
||||
+ aura.radius.powi(2) as usize * usize::from(heartbeats) / 300,
|
||||
|| {
|
||||
let rand_dist = aura.radius * (1.0 - rng.gen::<f32>().powi(100));
|
||||
let init_pos = Vec3::new(rand_dist, 0_f32, 0_f32);
|
||||
let max_dur = Duration::from_secs(1);
|
||||
Particle::new_directed(
|
||||
aura.duration.map_or(max_dur, |dur| dur.min(max_dur)),
|
||||
time,
|
||||
ParticleMode::EnergyNature,
|
||||
pos.0,
|
||||
pos.0 + init_pos,
|
||||
)
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user