Merge branch 'qsto/phoenix' into 'master'

Phoenix overhaul

See merge request veloren/veloren!4073
This commit is contained in:
Samuel Keiffer 2023-11-28 11:13:18 +00:00
commit 233f1d6bac
80 changed files with 1981 additions and 371 deletions

View File

@ -716,10 +716,14 @@
], ],
), ),
Custom("Bird Large Fire"): ( Custom("Bird Large Fire"): (
primary: Simple(None, "common.abilities.custom.birdlargefire.firebomb"), primary: Simple(None, "common.abilities.custom.birdlargefire.longstrike"),
secondary: Simple(None, "common.abilities.custom.birdlargefire.triplestrike"), secondary: Simple(None, "common.abilities.custom.birdlargefire.shortstrike"),
abilities: [ abilities: [
Simple(None, "common.abilities.custom.birdlargefire.fireshockwave"), Simple(None, "common.abilities.custom.birdlargefire.legstrike"),
Simple(None, "common.abilities.custom.birdlargefire.summontornadoes"),
Simple(None, "common.abilities.custom.birdlargefire.firerain"),
Simple(None, "common.abilities.custom.birdlargefire.heat_laser"),
Simple(None, "common.abilities.custom.birdlargefire.from_the_ashes"),
], ],
), ),
Custom("Flame Wyvern"): ( Custom("Flame Wyvern"): (
@ -807,6 +811,11 @@
secondary: Simple(None, "common.abilities.empty.basic"), secondary: Simple(None, "common.abilities.empty.basic"),
abilities: [], abilities: [],
), ),
Custom("FieryTornado"): (
primary: Simple(None, "common.abilities.custom.fiery_tornado.fiery_spin"),
secondary: Simple(None, "common.abilities.custom.fiery_tornado.fiery_aura"),
abilities: [],
),
Custom("Golf Club"): ( Custom("Golf Club"): (
primary: Simple(None, "common.abilities.hammer.singlestrike"), primary: Simple(None, "common.abilities.hammer.singlestrike"),
secondary: Simple(None, "common.abilities.tool.golf_club.charged"), secondary: Simple(None, "common.abilities.tool.golf_club.charged"),

View File

@ -1,20 +0,0 @@
BasicRanged(
energy_cost: 0,
buildup_duration: 1.0,
recover_duration: 0.7,
projectile: Fireball(
damage: 20.0,
radius: 5.0,
energy_regen: 5.0,
min_falloff: 0.5,
),
projectile_body: Object(BoltFire),
/*projectile_light: Some(LightEmitter {
col: (1.0, 0.75, 0.11).into(),
..Default::default()
}),*/
projectile_speed: 60.0,
num_projectiles: 1,
projectile_spread: 0.0,
move_efficiency: 0.3,
)

View File

@ -0,0 +1,32 @@
RepeaterRanged(
energy_cost: 0.0,
buildup_duration: 0.2,
shoot_duration: 0.3,
recover_duration: 0.5,
max_speed: 8.0,
half_speed_at: 5,
projectile: FireDroplet(
damage: 30.0,
radius: 12.0,
min_falloff: 0.5,
energy_regen: 0,
reagent: Some(Phoenix),
),
projectile_body: Object(FireRainDrop),
projectile_light: Some(LightEmitter(
col: Rgb(
r: 1.0,
g: 0.8,
b: 0.3,
),
strength: 10.0,
flicker: 5.0,
animated: true,
)),
projectile_speed: 0.0,
properties_of_aoe: Some(ProjectileOffset(
radius: 30,
height: 20,
)),
specifier: Some(FireRain),
)

View File

@ -1,19 +0,0 @@
BasicBeam(
buildup_duration: 0.8,
recover_duration: 0.5,
beam_duration: 0.5,
damage: 10.0,
tick_rate: 3.0,
range: 15.0,
max_angle: 22.5,
damage_effect: Some(Buff((
kind: Burning,
dur_secs: 10.0,
strength: DamageFraction(0.5),
chance: 0.25,
))),
energy_regen: 0,
energy_drain: 0,
ori_rate: 0.3,
specifier: Flamethrower,
)

View File

@ -0,0 +1,10 @@
SelfBuff(
buildup_duration: 1.25,
cast_duration: 1.8,
recover_duration: 1.25,
buff_kind: Regeneration,
buff_strength: 1500.0,
buff_duration: Some(3.0),
energy_cost: 0,
specifier: Some(FromTheAshes),
)

View File

@ -0,0 +1,19 @@
BasicBeam(
buildup_duration: 0.5,
recover_duration: 0.3,
beam_duration: 1.0,
damage: 30.0,
tick_rate: 1.0,
range: 120.0,
max_angle: 1.0,
damage_effect: Some(Buff((
kind: Burning,
dur_secs: 5.0,
strength: DamageFraction(0.75),
chance: 0.75,
))),
energy_regen: 0,
energy_drain: 0,
ori_rate: 0.07,
specifier: PhoenixLaser,
)

View File

@ -0,0 +1,26 @@
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Slash(
damage: 35,
poise: 0,
knockback: 15,
energy_regen: 0,
),
range: 6.0,
angle: 90.0,
),
buildup_duration: 0.1,
swing_duration: 0.1,
hit_timing: 0.5,
recover_duration: 0.1,
movement: (
swing: Some(Forward(0.5)),
),
ori_modifier: 0.7,
),
],
energy_cost_per_strike: 0,
auto_progress: false,
)

View File

@ -0,0 +1,26 @@
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Slash(
damage: 30,
poise: 0,
knockback: 10,
energy_regen: 0,
),
range: 6.0,
angle: 90.0,
),
buildup_duration: 0.6,
swing_duration: 0.3,
hit_timing: 0.5,
recover_duration: 0.1,
movement: (
swing: Some(Forward(0.5)),
),
ori_modifier: 0.7,
),
],
energy_cost_per_strike: 0,
auto_progress: true,
)

View File

@ -0,0 +1,26 @@
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Slash(
damage: 20,
poise: 0,
knockback: 5,
energy_regen: 0,
),
range: 6.0,
angle: 90.0,
),
buildup_duration: 0.2,
swing_duration: 0.1,
hit_timing: 0.5,
recover_duration: 0.1,
movement: (
swing: Some(Forward(0.5)),
),
ori_modifier: 0.7,
),
],
energy_cost_per_strike: 0,
auto_progress: true,
)

View File

@ -0,0 +1,18 @@
BasicSummon(
buildup_duration: 0.75,
cast_duration: 0.25,
recover_duration: 0.5,
summon_amount: 12,
summon_distance: (8, 10),
summon_info: (
body: Object(FieryTornado),
scale: None,
has_health: false,
loadout_config: None,
skillset_config: None,
),
duration: Some((
secs: 10,
nanos: 0,
)),
)

View File

@ -11,7 +11,7 @@ BasicAura(
category: Magical, category: Magical,
), ),
], ],
aura_duration: 2.0, aura_duration: Some(2.0),
range: 10.0, range: 10.0,
energy_cost: 0.0, energy_cost: 0.0,
scales_with_combo: false, scales_with_combo: false,

View File

@ -11,7 +11,7 @@ BasicAura(
category: Magical, category: Magical,
), ),
], ],
aura_duration: 2.0, aura_duration: Some(2.0),
range: 10.0, range: 10.0,
energy_cost: 0.0, energy_cost: 0.0,
scales_with_combo: false, scales_with_combo: false,

View File

@ -0,0 +1,19 @@
BasicAura(
buildup_duration: 0.0,
cast_duration: 0.0,
recover_duration: 0.0,
targets: OutOfGroup,
auras: [
(
kind: Heatstroke,
strength: 1.0,
duration: Some(5.0),
category: Magical,
),
],
aura_duration: None,
range: 20.0,
energy_cost: 0.0,
scales_with_combo: false,
specifier: Some(FieryAura),
)

View File

@ -0,0 +1,25 @@
RapidMelee(
buildup_duration: 0.0,
swing_duration: 0.5,
recover_duration: 0.0,
melee_constructor: (
kind: Slash(
damage: 80,
poise: 0,
knockback: 50,
energy_regen: 0,
),
range: 3.5,
angle: 360,
multi_target: Some(Normal),
damage_effect: Some(Buff((
kind: Burning,
dur_secs: 8.0,
strength: Value(0.3),
chance: 1.0,
))),
),
energy_cost: 0,
ori_modifier: 1.0,
move_modifier: 1.0,
)

View File

@ -11,7 +11,7 @@ BasicAura(
category: Magical, category: Magical,
), ),
], ],
aura_duration: 34.75, aura_duration: Some(34.75),
range: 18.0, range: 18.0,
energy_cost: 0.0, energy_cost: 0.0,
scales_with_combo: false, scales_with_combo: false,

View File

@ -17,7 +17,7 @@ BasicAura(
category: Magical, category: Magical,
), ),
], ],
aura_duration: 2, aura_duration: Some(2),
range: 50, range: 50,
energy_cost: 0, energy_cost: 0,
scales_with_combo: false, scales_with_combo: false,

View File

@ -11,7 +11,7 @@ BasicAura(
category: Magical, category: Magical,
), ),
], ],
aura_duration: 2, aura_duration: Some(2),
range: 50, range: 50,
energy_cost: 0, energy_cost: 0,
scales_with_combo: false, scales_with_combo: false,

View File

@ -11,7 +11,7 @@ BasicAura(
category: Magical, category: Magical,
), ),
], ],
aura_duration: 2, aura_duration: Some(2),
range: 50, range: 50,
energy_cost: 0, energy_cost: 0,
scales_with_combo: false, scales_with_combo: false,

View File

@ -11,7 +11,7 @@ BasicAura(
category: Magical, category: Magical,
), ),
], ],
aura_duration: 1.0, aura_duration: Some(1.0),
range: 25.0, range: 25.0,
energy_cost: 20.0, energy_cost: 20.0,
scales_with_combo: true, scales_with_combo: true,

View File

@ -11,7 +11,7 @@ BasicAura(
category: Magical, category: Magical,
), ),
], ],
aura_duration: 1.0, aura_duration: Some(1.0),
range: 25.0, range: 25.0,
energy_cost: 35.0, energy_cost: 35.0,
scales_with_combo: false, scales_with_combo: false,

View File

@ -3,7 +3,7 @@
name: Automatic, name: Automatic,
body: RandomWith("oni_blue"), body: RandomWith("oni_blue"),
alignment: Alignment(Enemy), alignment: Alignment(Enemy),
loot: LootTable("common.loot_tables.creature.biped_large.default"), loot: LootTable("common.loot_tables.creature.biped_large.blue_oni"),
inventory: ( inventory: (
loadout: FromBody, loadout: FromBody,
), ),

View File

@ -3,7 +3,7 @@
name: Automatic, name: Automatic,
body: RandomWith("oni_red"), body: RandomWith("oni_red"),
alignment: Alignment(Enemy), alignment: Alignment(Enemy),
loot: LootTable("common.loot_tables.creature.biped_large.default"), loot: LootTable("common.loot_tables.creature.biped_large.red_oni"),
inventory: ( inventory: (
loadout: FromBody, loadout: FromBody,
), ),

View File

@ -0,0 +1,13 @@
ItemDef(
name: "Phoenix Armor",
description: "The thickest feather you have ever seen!",
kind: Armor((
kind: Chest,
stats: Direct((
protection: Some(Normal(60.0)),
poise_resilience: Some(Normal(0.0)),
)),
)),
quality: Epic,
tags: [],
)

View File

@ -1,6 +1,6 @@
ItemDef( ItemDef(
name: "Bird Large Fire", name: "Bird Large Fire",
description: "testing123", description: "Fiery touch of a mighty aerial beast",
kind: Tool(( kind: Tool((
kind: Natural, kind: Natural,
hands: Two, hands: Two,
@ -14,7 +14,7 @@ ItemDef(
buff_strength: 1.0, buff_strength: 1.0,
), ),
)), )),
quality: Low, quality: Epic,
tags: [], tags: [],
ability_spec: Some(Custom("Bird Large Fire")), ability_spec: Some(Custom("Bird Large Fire")),
) )

View File

@ -0,0 +1,20 @@
ItemDef(
name: "FieryTornado",
description: "Fiery Tornado weapon",
kind: Tool((
kind: Natural,
hands: Two,
stats: (
equip_time_secs: 0.01,
power: 1.0,
effect_power: 1.0,
speed: 1.0,
range: 1.0,
energy_efficiency: 1.0,
buff_strength: 1.0,
),
)),
quality: Low,
tags: [],
ability_spec: Some(Custom("FieryTornado")),
)

View File

@ -7,7 +7,7 @@ ItemDef(
stats: ( stats: (
equip_time_secs: 0.01, equip_time_secs: 0.01,
power: 1.0, power: 1.0,
effect_power: 0.0, effect_power: 1.0,
speed: 1.0, speed: 1.0,
range: 1.0, range: 1.0,
energy_efficiency: 1.0, energy_efficiency: 1.0,

View File

@ -0,0 +1,5 @@
[
(1.0, LootTable("common.loot_tables.food.prepared")),
(2.0, LootTable("common.loot_tables.cave_large")),
(0.4, Item("common.items.glider.morpho")),
]

View File

@ -1,9 +1,8 @@
[ [
(1.8, All([ (1.9, All([
MultiDrop(Item("common.items.utility.coins"), 200, 500), MultiDrop(Item("common.items.utility.coins"), 200, 500),
MultiDrop(Item("common.items.mineral.ingot.bloodsteel"), 2, 4), MultiDrop(Item("common.items.mineral.ingot.bloodsteel"), 2, 4),
MultiDrop(Item("common.items.mineral.ingot.silver"), 0, 2), MultiDrop(Item("common.items.mineral.ingot.silver"), 0, 2),
])), ])),
(0.1, Item("common.items.glider.morpho")),
(0.1, Item("common.items.weapons.sword.caladbolg")), (0.1, Item("common.items.weapons.sword.caladbolg")),
] ]

View File

@ -0,0 +1,5 @@
[
(1.0, LootTable("common.loot_tables.food.prepared")),
(2.0, LootTable("common.loot_tables.cave_large")),
(0.4, Item("common.items.glider.monarch")),
]

View File

@ -1,5 +1,4 @@
[ [
(1.2, Nothing), (1.6, Nothing),
(0.4, Item("common.items.glider.monarch")),
(0.4, Item("common.items.weapons.bow.sagitta")), (0.4, Item("common.items.weapons.bow.sagitta")),
] ]

View File

@ -1517,6 +1517,13 @@
threshold: 0.8, threshold: 0.8,
subtitle: "subtitle-portal-teleported", subtitle: "subtitle-portal-teleported",
), ),
FromTheAshes: (
files: [
"voxygen.audio.sfx.abilities.heal",
],
threshold: 1.8,
subtitle: "subtitle-attack-from-the-ashes",
),
// Utterances (NPCs) // Utterances (NPCs)
@ -1557,6 +1564,14 @@
threshold: 1.3, threshold: 1.3,
subtitle: "subtitle-utterance-wyvern-angry", subtitle: "subtitle-utterance-wyvern-angry",
), ),
Utterance(Angry, Phoenix): (
files: [
"voxygen.audio.sfx.utterance.phoenix_angry_0",
"voxygen.audio.sfx.utterance.phoenix_angry_1",
],
threshold: 3.1,
subtitle: "subtitle-utterance-phoenix-angry",
),
Utterance(Angry, Adlet): ( Utterance(Angry, Adlet): (
files: [ files: [
"voxygen.audio.sfx.utterance.adlet_angry1", "voxygen.audio.sfx.utterance.adlet_angry1",
@ -1765,6 +1780,14 @@
threshold: 1.2, threshold: 1.2,
subtitle: "subtitle-utterance-wyvern-hurt", subtitle: "subtitle-utterance-wyvern-hurt",
), ),
Utterance(Hurt, Phoenix): (
files: [
"voxygen.audio.sfx.utterance.phoenix_hurt_0",
"voxygen.audio.sfx.utterance.phoenix_hurt_1",
],
threshold: 1.5,
subtitle: "subtitle-utterance-phoenix-hurt",
),
Utterance(Angry, Wendigo): ( Utterance(Angry, Wendigo): (
files: [ files: [
"voxygen.audio.sfx.utterance.wendigo_angry1", "voxygen.audio.sfx.utterance.wendigo_angry1",

BIN
assets/voxygen/audio/sfx/abilities/heal.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/phoenix_angry_0.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/phoenix_angry_1.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/phoenix_hurt_0.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/phoenix_hurt_1.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/de_buffs/debuff_heatstroke_0.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -109,6 +109,9 @@ buff-desc-bloodfeast = You restore life on attacks against bleeding enemies
## Berserk ## Berserk
buff-title-berserk = Berserk buff-title-berserk = Berserk
buff-desc-berserk = You are in a berserking rage, causing your attacks to be more powerful and swift, and increasing your speed. However, as a result your defensive capability is less. buff-desc-berserk = You are in a berserking rage, causing your attacks to be more powerful and swift, and increasing your speed. However, as a result your defensive capability is less.
## Heatstroke
buff-title-heatstroke = Heatstroke
buff-desc-heatstroke = You were exposed to heat and now suffer from heatstroke, your energy reward and movement speed are cut down. Chill.
## Util ## Util
buff-text-over_seconds = over { $dur_secs } seconds buff-text-over_seconds = over { $dur_secs } seconds
buff-text-for_seconds = for { $dur_secs } seconds buff-text-for_seconds = for { $dur_secs } seconds

View File

@ -111,6 +111,7 @@ subtitle-attack-icy_spikes = Icy spikes
subtitle-attack-ice_crack = Ice crack subtitle-attack-ice_crack = Ice crack
subtitle-attack-steam = Steam subtitle-attack-steam = Steam
subtitle-attack-shovel = Shovel digging subtitle-attack-shovel = Shovel digging
subtitle-attack-from-the-ashes = Heal Sound
subtitle-consume_potion = Drinking potion subtitle-consume_potion = Drinking potion
subtitle-consume_apple = Eating apple subtitle-consume_apple = Eating apple
@ -154,3 +155,5 @@ subtitle-utterance-wolf-angry = Wolf growling
subtitle-utterance-wolf-hurt = Wolf whining subtitle-utterance-wolf-hurt = Wolf whining
subtitle-utterance-wyvern-angry = Wyvern roaring subtitle-utterance-wyvern-angry = Wyvern roaring
subtitle-utterance-wyvern-hurt = Wyvern hurting subtitle-utterance-wyvern-hurt = Wyvern hurting
subtitle-utterance-phoenix-angry = Phoenix screaming
subtitle-utterance-phoenix-hurt = Phoenix hurting

View File

@ -84,6 +84,16 @@ const int CYCLOPS_CHARGE = 43;
const int PORTAL_FIZZ = 45; const int PORTAL_FIZZ = 45;
const int INK = 46; const int INK = 46;
const int WHIRLWIND = 47; const int WHIRLWIND = 47;
const int FIERY_BURST = 48;
const int FIERY_BURST_VORTEX = 49;
const int FIERY_BURST_SPARKS = 50;
const int FIERY_BURST_ASH = 51;
const int FIERY_TORNADO = 52;
const int PHOENIX_CLOUD = 53;
const int FIERY_DROPLET_TRACE = 54;
const int ENERGY_PHOENIX = 55;
const int PHOENIX_BEAM = 56;
const int PHOENIX_BUILD_UP_AIM = 57;
// meters per second squared (acceleration) // meters per second squared (acceleration)
const float earth_gravity = 9.807; const float earth_gravity = 9.807;
@ -727,6 +737,240 @@ void main() {
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
); );
break; break;
case FIERY_BURST:
f_reflect = 0.0;
float fiery_radius = start_end(1.0 - pow(abs(rand5), 5.0), 1.0) * length(inst_dir);
float fiery_color1 = (7.0 + 1.0 * percent()) * min(1.0, percent() * 4.0) * 1.5;
float fiery_color2 = (4.0 - 2.0 * percent() + 1.3 * rand5 * slow_end(0.0)) * min(1.0, percent() * 4.0) * 1.3;
float fiery_color3 = 1.0 + 0.3 * percent();
attr = Attr(
spiral_motion(
vec3(
0.0,
0.0,
(rand3 + 1.0)
* max(
((percent() * 8.0) * (1.0 - step(0.2, percent()))),
((2.0 * (1.0 - percent())) * (step(0.2, percent())))
)
),
fiery_radius,
lifetime,
max(0.1, step(0.6, percent())) * 3.0 * abs(rand0),
rand1 * 2.0 * PI) + vec3(0.0, 0.0, rand2),
vec3(6.0 * abs(rand4) * (1.0 - slow_start(2.0)) * pow(fiery_radius / length(inst_dir), 0.5)),
vec4(fiery_color1, fiery_color2, fiery_color3, slow_end(0.4)),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3.0)
);
break;
case FIERY_BURST_VORTEX:
f_reflect = 0.0;
float fiery_vortex_color1 = (min(1, percent() * 2) * (5 + 1 * percent() + 1 * slow_end(0)) * 1.5);
float fiery_vortex_color2 = (min(1, percent() * 2) * (4 - 2.4 * percent() + 1.3 * rand5 * slow_end(0)) * 1.3);
float fiery_vortex_color3 = 0;
attr = Attr(
spiral_motion(
vec3(
0,
0,
(0 + 0.5 * rand4 ) + 4.0
* max(
((percent() * 8) * (1 - step(0.2, percent()))), // first 20% of lifetime particle moves up, then goes down
((2 * (1 - percent())) * (step(0.2, percent())))// to avoid tearing multi should have same proportion as edge(here: 8 before, 2 after)
)
),
abs(rand0) + 0.5 * 10 * percent(),
percent(),
10.0 * abs(rand2),
rand3),
vec3((2.5 * (1 - slow_start(0.05)))),
vec4(fiery_vortex_color1, fiery_vortex_color2, fiery_vortex_color3, start_end(0.5, 1.5) * abs(rand2)),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case FIERY_BURST_SPARKS:
f_reflect = 0.0;
// sparks should flicker, so it stops glowing for 18% of time 4 times per second, same thing used in 4th float of RGBA vector
float fiery_sparks_color1 = 2 + 1 * rand2 + 2 * step(0.18, fract(tick.x*4));
float fiery_sparks_color2 = 4 + 1 * rand2 + 4 * step(0.18, fract(tick.x*4));
float fiery_sparks_color3 = 4 + 6 * step(0.18, fract(tick.x*4));
attr = Attr(
spiral_motion(vec3(0, 0, 5), abs(rand0) + abs(rand1) * percent() * 4.0, percent(), 8.0 * abs(rand2), rand3),
vec3((2.5 * (1 - slow_start(0.05)))),
vec4(fiery_sparks_color1, fiery_sparks_color2, fiery_sparks_color3, 0.5 + 0.5 * step(0.18, fract(tick.x*4))),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case FIERY_BURST_ASH:
f_reflect = 0.0;
/// inst_dir holds info about:
/// .x: radius of random spawn
float fiery_ash_rand_rad = inst_dir.x;
/// .y:
/// in fract: relative time of "setting on fire"
/// in int: radius of curve
float fiery_ash_radius = floor(inst_dir.y);
float fiery_ash_edge = inst_dir.y - fiery_ash_radius;
/// .z: height of the flight
float fiery_ash_height = inst_dir.z;
// {FOR PHOENIX "from the ashes"}sets ash on fire at 0.4 of lifetime, then makes it lose glow, representing losing heat
float fiery_ash_color1 = (2 + 1 * percent() * slow_end(0))
* (max(
1,
8 * step(fiery_ash_edge, percent()) * (1.4 - percent()))
);
float fiery_ash_color2 = (2 - 1 * percent() + 0.3 * abs(rand5) * slow_end(0.5))
* (max(
1,
6.5 * step(fiery_ash_edge, percent()) * (1.4 - percent()))
);
float fiery_ash_color3 = 1.5;
attr = Attr(
spiral_motion(
vec3(
0.0,
0.0,
fiery_ash_height// {FOR PHOENIX "from the ashes"} 8.58
),
abs(rand0 / 2.0 + 1.0)
* max(1.0, ((percent() * fiery_ash_radius * 0.8) * (1.0 - step(0.2, percent())))) // part of lifetime particle moves to periphery
* max(1.0, (fiery_ash_radius * 0.2 * (1.0 - percent()) * (step(0.2, percent())))),// then back to center
percent(),
6.0 * abs(rand2),
rand3 * 5.0
)
+ vec3((rand6 + rand5) * fiery_ash_rand_rad, (rand8 + rand3) * fiery_ash_rand_rad, abs(rand0)),//makes it apear randomly above base animation (Fiery Burst)
vec3((2.5 * (1 - slow_start(0.0)))),
vec4(fiery_ash_color1, fiery_ash_color2, fiery_ash_color3, abs(rand2) * slow_end(0.3)),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case FIERY_TORNADO:
f_reflect = 0.0;
float fiery_tornado_color1 = (2.6 + 0.5 * percent())
* 4.0 * max(0.5, percent() * 1.2);
float fiery_tornado_color2 = (1.7 - 0.6 * pow(1.0 - percent(), 2.0) + 0.3 * abs(rand5))
* 2.0 * max(0.45, percent() * 1.2);
float fiery_tornado_color3 = 1.5 * max(0.6, percent());
attr = Attr(
spiral_motion(vec3(0, 0, 6.0 + rand3 * 1.5), abs(rand0) + abs(rand1) * percent() * 3.0, percent(), 15.0 * abs(rand2), -inst_time),
vec3((2.5 * (1 - slow_start(0.05)))),
vec4(fiery_tornado_color1, fiery_tornado_color2, fiery_tornado_color3, 0.5),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case PHOENIX_CLOUD:
float PC_spin = floor(inst_dir.x);
float refl = floor(inst_dir.y);
float PC_size = floor(inst_dir.z);
//best is 0.4 - reflects some light but only part as
f_reflect = refl * 0.1;
// modifies by + 5% to -15%, if color is less than 0.5 it will get from +10% to +25% to it's value
float PC_rand_color_factor = rand0 * 0.05;
float PC_R = inst_dir.x - PC_spin;
PC_R += PC_R * PC_rand_color_factor * step(0.05, PC_R) * -abs(PC_rand_color_factor * 2.0)
+ PC_R * (1.0 - step(0.05, PC_R)) * max(abs(PC_rand_color_factor), 0.02) * 5.0;
float PC_G = inst_dir.y - refl;
PC_G += PC_G * PC_rand_color_factor * step(0.05, PC_G) * -abs(PC_rand_color_factor * 2.0)
+ PC_G * (1.0 - step(0.05, PC_G)) * max(abs(PC_rand_color_factor), 0.02) * 5.0;
float PC_B = inst_dir.z - PC_size;
PC_B += PC_B * PC_rand_color_factor * step(0.05, PC_B) * -abs(PC_rand_color_factor * 2.0)
+ PC_B * (1.0 - step(0.05, PC_B)) * max(abs(PC_rand_color_factor), 0.02) * 5.0;
attr = Attr(
linear_motion(
vec3(0.0, 0.0, 0.0),
vec3(rand4, rand5, rand6 * 2.5)
),
vec3(8.0 * min(percent() * 3.0, 1.0) * min((1.0 - percent()) * 2.0, 1.0)),
vec4(
PC_R,
PC_G,
PC_B,
PC_size * 1.2) * 10.0,
spin_in_axis(vec3(rand6 + rand5, rand7 + rand9, rand8 + rand2), percent() * PC_spin)
);
break;
case FIERY_DROPLET_TRACE:
float m_r = 4.0;
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
float prcnt = percent(); //idk if compiler would optimize it or not but as we have a lot of those particles... i'll just try
float droplet_color1 = 1 * (5 + 1 * prcnt + 1 * slow_end(0)) * 1.5;
float droplet_color2 = 1 * (4 - 2.4 * prcnt + 1.3 * rand5 * slow_end(0)) * 1.3;
float droplet_color3 = 0;
attr = Attr(
quadratic_bezier_motion(
vec3(0.0),
vec3(m_r * rand0, m_r * rand1, 0.0),
vec3(m_r * rand0, m_r * rand1, 4.0)
),
vec3(1),
vec4(droplet_color1,
droplet_color2,
droplet_color3,
1 * prcnt * (1 - step(0.5, prcnt)) + (1 - prcnt) * (step(0.5, prcnt))),
spin_in_axis(vec3(1,0,0),0)
);
break;
case ENERGY_PHOENIX:
f_reflect = 0.0;
float fiery_r = (2 + 1 * percent() * slow_end(0))
* 6 * (1.4 - percent());
float fiery_g = (2 - 1 * percent() + 0.3 * abs(rand5) * slow_end(0.5))
* 4.5 * (1.4 - percent());
float fiery_b = 1.5;
spiral_radius = length(inst_dir);
attr = Attr(
spiral_motion(vec3(0.0, 0.0, 0.01), spiral_radius + abs(rand1), lifetime / 0.5, abs(rand0), rand1 * 2.0 * PI) + vec3(0.0, 0.0, rand2),
vec3(6.0 * abs(rand4) * (1 - slow_start(2.0))),
vec4(vec3(fiery_r, fiery_g, fiery_b), 1.0),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3.0)
);
break;
case PHOENIX_BEAM:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
float beam_r = 6.0 - (4.0 * percent()) + 15.0 * fract(percent() * 4 + rand0 * rand0) * (1 - percent());
float beam_g = 2.0 + 6.6 * fract(percent() * 4 + rand0 * rand0) * (1 - percent());
float beam_b = 1.4;
vec3 factor_rand = vec3((rand0 * 0.2) * (rand5 * 0.1) + rand6 * 0.9, (rand1 * 0.2) * (rand4 * 0.1) + rand7 * 0.9, (rand2 * 0.2) * (rand3 * 0.1) + rand8 * 0.9);
start_pos += factor_rand + normalize(inst_dir) * 0.6;
attr = Attr(
spiral_motion(inst_dir - factor_rand * 0.4, 0.3 * ((rand2 + 0.5) * 5.5) * (1.0 - min(linear_scale(1.5), 1.0)), lifetime / inst_lifespan, 24.0, -inst_time * 8.0),
vec3((2.5 * (1 - slow_start(0.2)))),
vec4(beam_r, beam_g, beam_b, 1.0),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10.0 + 3.0 * rand9)
);
break;
case PHOENIX_BUILD_UP_AIM:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
float perc_t = percent(); // in case compiler wont optimize, idk
float aim_r = rand0 * 0.25 + 3.0 + 4.5 * perc_t * (1 - step(0.79, perc_t)) + 8.0 * step(0.81, perc_t) * perc_t;
float aim_g = rand0 * 0.25 + 2.0 - 1.0 * perc_t * (1 - step(0.79, perc_t)) + 2.0 * step(0.81, perc_t) * perc_t;
float aim_b = 1.4 * ((1 - perc_t) + step(0.74, perc_t));
vec3 dir_aim = inst_dir * 1.0;
vec3 rand_pos_aim = (cross(
(1.0 - 2.0 * step(0.0, rand2)) * normalize(inst_dir),
vec3(0.0, 0.0, 1.0)));
vec3 rand_fact = vec3(rand1 * 1, rand0 * 1, rand2 * 1);
start_pos += vec3(0.0, 0.0, 5.0) + rand_fact;
attr = Attr(
spiral_motion(
inst_dir + vec3(0.0, 0.0, -(6.0 - 3.0 * pow(perc_t, 2.5))) - rand_fact,
1.2 * rand9 * max(1.0 - perc_t, 0.0),
perc_t,
6.0,
inst_time * 8.0),
vec3((1.9 * (1 - slow_start(0.2)))),
vec4(aim_r, aim_g, aim_b, 1.0),
spin_in_axis(vec3(rand6, rand7, rand8), perc_t * 10.0 + 3.0 * rand9)
);
break;
default: default:
attr = Attr( attr = Attr(
linear_motion( linear_motion(

View File

@ -629,6 +629,16 @@
central: ("armor.empty"), central: ("armor.empty"),
) )
), ),
FireRainDrop: (
bone0: (
offset: (-2.0, -10.0, -2.0),
central: ("weapon.projectile.firerain_droplet1"),
),
bone1: (
offset: (0.0, 0.0, 0.0),
central: ("armor.empty"),
)
),
TrainingDummy: ( TrainingDummy: (
bone0: ( bone0: (
offset: (-7.0, -5.0, 0.0), offset: (-7.0, -5.0, 0.0),
@ -809,6 +819,16 @@
central: ("armor.empty"), central: ("armor.empty"),
) )
), ),
FieryTornado: (
bone0: (
offset: (0.0, 0.0, 0.0),
central: ("armor.empty"),
),
bone1: (
offset: (0.0, 0.0, 0.0),
central: ("armor.empty"),
)
),
Apple: ( Apple: (
bone0: ( bone0: (
offset: (-3.5, -3.5, 0.0), offset: (-3.5, -3.5, 0.0),

BIN
assets/voxygen/voxel/weapon/projectile/firerain_droplet1.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -183,6 +183,7 @@ lazy_static! {
BuffKind::Defiance => "defiance", BuffKind::Defiance => "defiance",
BuffKind::Bloodfeast => "bloodfeast", BuffKind::Bloodfeast => "bloodfeast",
BuffKind::Berserk => "berserk", BuffKind::Berserk => "berserk",
BuffKind::Heatstroke => "heatstroke"
}; };
let mut buff_parser = HashMap::new(); let mut buff_parser = HashMap::new();
for kind in BuffKind::iter() { for kind in BuffKind::iter() {

View File

@ -721,6 +721,8 @@ pub enum CharacterAbility {
projectile_light: Option<LightEmitter>, projectile_light: Option<LightEmitter>,
projectile_speed: f32, projectile_speed: f32,
damage_effect: Option<CombatEffect>, damage_effect: Option<CombatEffect>,
properties_of_aoe: Option<repeater_ranged::ProjectileOffset>,
specifier: Option<repeater_ranged::FrontendSpecifier>,
#[serde(default)] #[serde(default)]
meta: AbilityMeta, meta: AbilityMeta,
}, },
@ -906,7 +908,7 @@ pub enum CharacterAbility {
recover_duration: f32, recover_duration: f32,
targets: combat::GroupTarget, targets: combat::GroupTarget,
auras: Vec<aura::AuraBuffConstructor>, auras: Vec<aura::AuraBuffConstructor>,
aura_duration: Secs, aura_duration: Option<Secs>,
range: f32, range: f32,
energy_cost: f32, energy_cost: f32,
scales_with_combo: bool, scales_with_combo: bool,
@ -947,6 +949,7 @@ pub enum CharacterAbility {
combo_scaling: Option<ScalingKind>, combo_scaling: Option<ScalingKind>,
#[serde(default)] #[serde(default)]
meta: AbilityMeta, meta: AbilityMeta,
specifier: Option<self_buff::FrontendSpecifier>,
}, },
SpriteSummon { SpriteSummon {
buildup_duration: f32, buildup_duration: f32,
@ -1228,6 +1231,8 @@ impl CharacterAbility {
projectile_light: _, projectile_light: _,
ref mut projectile_speed, ref mut projectile_speed,
damage_effect: _, damage_effect: _,
properties_of_aoe: _,
specifier: _,
meta: _, meta: _,
} => { } => {
*buildup_duration /= stats.speed; *buildup_duration /= stats.speed;
@ -1578,6 +1583,7 @@ impl CharacterAbility {
combo_cost: _, combo_cost: _,
combo_scaling: _, combo_scaling: _,
meta: _, meta: _,
specifier: _,
} => { } => {
*buff_strength *= stats.diminished_buff_strength(); *buff_strength *= stats.diminished_buff_strength();
*buildup_duration /= stats.speed; *buildup_duration /= stats.speed;
@ -2597,6 +2603,8 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
projectile_light, projectile_light,
projectile_speed, projectile_speed,
damage_effect, damage_effect,
properties_of_aoe,
specifier,
meta: _, meta: _,
} => CharacterState::RepeaterRanged(repeater_ranged::Data { } => CharacterState::RepeaterRanged(repeater_ranged::Data {
static_data: repeater_ranged::StaticData { static_data: repeater_ranged::StaticData {
@ -2613,6 +2621,8 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
projectile_speed: *projectile_speed, projectile_speed: *projectile_speed,
ability_info, ability_info,
damage_effect: *damage_effect, damage_effect: *damage_effect,
properties_of_aoe: *properties_of_aoe,
specifier: *specifier,
}, },
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,
@ -2776,6 +2786,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
combo_scaling, combo_scaling,
enforced_limit, enforced_limit,
meta: _, meta: _,
specifier,
} => CharacterState::SelfBuff(self_buff::Data { } => CharacterState::SelfBuff(self_buff::Data {
static_data: self_buff::StaticData { static_data: self_buff::StaticData {
buildup_duration: Duration::from_secs_f32(*buildup_duration), buildup_duration: Duration::from_secs_f32(*buildup_duration),
@ -2789,6 +2800,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
combo_on_use: data.combo.map_or(0, |c| c.counter()), combo_on_use: data.combo.map_or(0, |c| c.counter()),
enforced_limit: *enforced_limit, enforced_limit: *enforced_limit,
ability_info, ability_info,
specifier: *specifier,
}, },
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,

View File

@ -78,6 +78,7 @@ pub enum Specifier {
WardingAura, WardingAura,
HealingAura, HealingAura,
Frozen, Frozen,
FieryAura,
} }
impl From<(Option<GroupTarget>, Option<&Uid>)> for AuraTarget { impl From<(Option<GroupTarget>, Option<&Uid>)> for AuraTarget {

View File

@ -44,4 +44,5 @@ pub enum FrontendSpecifier {
Poison, Poison,
Ink, Ink,
Lightning, Lightning,
PhoenixLaser,
} }

View File

@ -519,7 +519,7 @@ impl Body {
| bird_large::Species::CloudWyvern | bird_large::Species::CloudWyvern
| bird_large::Species::SeaWyvern | bird_large::Species::SeaWyvern
| bird_large::Species::WealdWyvern => Vec3::new(2.5, 9.0, 4.5), | bird_large::Species::WealdWyvern => Vec3::new(2.5, 9.0, 4.5),
_ => Vec3::new(2.0, 6.0, 3.5), _ => Vec3::new(2.0, 6.0, 4.4),
}, },
Body::Dragon(_) => Vec3::new(16.0, 10.0, 16.0), Body::Dragon(_) => Vec3::new(16.0, 10.0, 16.0),
Body::FishMedium(_) => Vec3::new(0.5, 2.0, 0.8), Body::FishMedium(_) => Vec3::new(0.5, 2.0, 0.8),
@ -842,6 +842,7 @@ impl Body {
| bird_large::Species::FrostWyvern | bird_large::Species::FrostWyvern
| bird_large::Species::SeaWyvern | bird_large::Species::SeaWyvern
| bird_large::Species::WealdWyvern => 1000, | bird_large::Species::WealdWyvern => 1000,
bird_large::Species::Phoenix => 2000,
_ => 300, _ => 300,
}, },
Body::BirdMedium(bird_medium) => match bird_medium.species { Body::BirdMedium(bird_medium) => match bird_medium.species {
@ -940,7 +941,7 @@ impl Body {
quadruped_low::Species::Maneater => 130, quadruped_low::Species::Maneater => 130,
quadruped_low::Species::Sandshark => 110, quadruped_low::Species::Sandshark => 110,
quadruped_low::Species::Hakulaq => 120, quadruped_low::Species::Hakulaq => 120,
quadruped_low::Species::Dagon => 1200, quadruped_low::Species::Dagon => 1500,
quadruped_low::Species::Lavadrake => 160, quadruped_low::Species::Lavadrake => 160,
quadruped_low::Species::Basilisk => 200, quadruped_low::Species::Basilisk => 200,
quadruped_low::Species::Deadwood => 120, quadruped_low::Species::Deadwood => 120,

View File

@ -112,6 +112,8 @@ make_case_elim!(
SpearIcicle = 97, SpearIcicle = 97,
Portal = 98, Portal = 98,
PortalActive = 99, PortalActive = 99,
FieryTornado = 100,
FireRainDrop = 101,
} }
); );
@ -122,7 +124,7 @@ impl Body {
} }
} }
pub const ALL_OBJECTS: [Body; 100] = [ pub const ALL_OBJECTS: [Body; 102] = [
Body::Arrow, Body::Arrow,
Body::Bomb, Body::Bomb,
Body::Scarecrow, Body::Scarecrow,
@ -223,6 +225,8 @@ pub const ALL_OBJECTS: [Body; 100] = [
Body::SpearIcicle, Body::SpearIcicle,
Body::Portal, Body::Portal,
Body::PortalActive, Body::PortalActive,
Body::FieryTornado,
Body::FireRainDrop,
]; ];
impl From<Body> for super::Body { impl From<Body> for super::Body {
@ -332,6 +336,8 @@ impl Body {
Body::SpearIcicle => "spear_icicle", Body::SpearIcicle => "spear_icicle",
Body::Portal => "portal", Body::Portal => "portal",
Body::PortalActive => "portal_active", Body::PortalActive => "portal_active",
Body::FieryTornado => "fiery_tornado",
Body::FireRainDrop => "fire_rain_drop",
} }
} }
@ -343,6 +349,7 @@ impl Body {
Reagent::Red => Body::FireworkRed, Reagent::Red => Body::FireworkRed,
Reagent::White => Body::FireworkWhite, Reagent::White => Body::FireworkWhite,
Reagent::Yellow => Body::FireworkYellow, Reagent::Yellow => Body::FireworkYellow,
Reagent::Phoenix => Body::FireRainDrop,
} }
} }
@ -384,7 +391,11 @@ impl Body {
Body::BedBlue => 50.0, Body::BedBlue => 50.0,
Body::Bedroll => 3.0, Body::Bedroll => 3.0,
Body::Bench => 100.0, Body::Bench => 100.0,
Body::BoltFire | Body::BoltFireBig | Body::BoltNature | Body::BoltIcicle => 1.0, Body::BoltFire
| Body::BoltFireBig
| Body::BoltNature
| Body::BoltIcicle
| Body::FireRainDrop => 1.0,
Body::SpitPoison => 100.0, Body::SpitPoison => 100.0,
Body::Bomb | Body::DagonBomb => { Body::Bomb | Body::DagonBomb => {
0.5 * IRON_DENSITY * std::f32::consts::PI / 6.0 * self.dimensions().x.powi(3) 0.5 * IRON_DENSITY * std::f32::consts::PI / 6.0 * self.dimensions().x.powi(3)
@ -445,7 +456,7 @@ impl Body {
Body::FishMeat => 10.0, Body::FishMeat => 10.0,
Body::BirdMeat => 10.0, Body::BirdMeat => 10.0,
Body::SmallMeat => 10.0, Body::SmallMeat => 10.0,
Body::Tornado => 50.0, Body::Tornado | Body::FieryTornado => 50.0,
Body::Apple => 2.0, Body::Apple => 2.0,
Body::Hive => 2.0, Body::Hive => 2.0,
Body::Coconut => 2.0, Body::Coconut => 2.0,
@ -479,7 +490,7 @@ impl Body {
Body::HaniwaSentry => Vec3::new(0.8, 0.8, 1.4), Body::HaniwaSentry => Vec3::new(0.8, 0.8, 1.4),
Body::SeaLantern => Vec3::new(0.8, 0.8, 1.4), Body::SeaLantern => Vec3::new(0.8, 0.8, 1.4),
Body::Snowball => Vec3::broadcast(2.5), Body::Snowball => Vec3::broadcast(2.5),
Body::Tornado => Vec3::new(2.0, 2.0, 3.4), Body::Tornado | Body::FieryTornado => Vec3::new(2.0, 2.0, 3.4),
Body::TrainingDummy => Vec3::new(1.5, 1.5, 3.0), Body::TrainingDummy => Vec3::new(1.5, 1.5, 3.0),
Body::GnarlingTotemRed | Body::GnarlingTotemGreen | Body::GnarlingTotemWhite => { Body::GnarlingTotemRed | Body::GnarlingTotemGreen | Body::GnarlingTotemWhite => {
Vec3::new(0.8, 0.8, 1.4) Vec3::new(0.8, 0.8, 1.4)
@ -489,6 +500,7 @@ impl Body {
Body::LaserBeam => Vec3::new(8.0, 8.0, 8.0), Body::LaserBeam => Vec3::new(8.0, 8.0, 8.0),
Body::Mine => Vec3::new(0.8, 0.8, 0.5), Body::Mine => Vec3::new(0.8, 0.8, 0.5),
Body::LightningBolt | Body::SpearIcicle => Vec3::new(1.0, 1.0, 1.0), Body::LightningBolt | Body::SpearIcicle => Vec3::new(1.0, 1.0, 1.0),
Body::FireRainDrop => Vec3::new(0.01, 0.01, 0.02),
// FIXME: this *must* be exhaustive match // FIXME: this *must* be exhaustive match
_ => Vec3::broadcast(0.5), _ => Vec3::broadcast(0.5),
} }

View File

@ -162,6 +162,12 @@ pub enum BuffKind {
PotionSickness, PotionSickness,
/// Changed into another body. /// Changed into another body.
Polymorphed, Polymorphed,
/// Slows movement speed and reduces energy reward.
/// Both scales non-linearly to strength, 0.5 lead to movespeed reduction
/// by 25% and energy reward reduced by 150%, 1.0 lead to MS reduction by
/// 33.3% and energy reward reduced by 200%. Energy reward can't be
/// reduced by more than 200%, to a minimum value of -100%.
Heatstroke,
} }
impl BuffKind { impl BuffKind {
@ -201,7 +207,8 @@ impl BuffKind {
| BuffKind::Poisoned | BuffKind::Poisoned
| BuffKind::Parried | BuffKind::Parried
| BuffKind::PotionSickness | BuffKind::PotionSickness
| BuffKind::Polymorphed => false, | BuffKind::Polymorphed
| BuffKind::Heatstroke => false,
} }
} }
@ -409,6 +416,10 @@ impl BuffKind {
BuffEffect::AttackSpeed(1.0 + nn_scaling(data.strength) / 2.0), BuffEffect::AttackSpeed(1.0 + nn_scaling(data.strength) / 2.0),
BuffEffect::MovementSpeed(1.0 + nn_scaling(data.strength) / 4.0), BuffEffect::MovementSpeed(1.0 + nn_scaling(data.strength) / 4.0),
], ],
BuffKind::Heatstroke => vec![
BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength) * 0.5),
BuffEffect::EnergyReward((1.0 - nn_scaling(data.strength) * 3.0).max(-1.0)),
],
} }
} }

View File

@ -44,6 +44,7 @@ pub enum Reagent {
Red, Red,
White, White,
Yellow, Yellow,
Phoenix,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]

View File

@ -780,6 +780,9 @@ fn default_main_tool(body: &Body) -> Item {
object::Body::Tornado => Some(Item::new_from_asset_expect( object::Body::Tornado => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.tornado", "common.items.npc_weapons.unique.tornado",
)), )),
object::Body::FieryTornado => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.fiery_tornado",
)),
object::Body::GnarlingTotemRed => Some(Item::new_from_asset_expect( object::Body::GnarlingTotemRed => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.biped_small.gnarling.redtotem", "common.items.npc_weapons.biped_small.gnarling.redtotem",
)), )),
@ -984,6 +987,7 @@ impl LoadoutBuilder {
| bird_large::Species::WealdWyvern => { | bird_large::Species::WealdWyvern => {
Some("common.items.npc_armor.bird_large.wyvern") Some("common.items.npc_armor.bird_large.wyvern")
}, },
bird_large::Species::Phoenix => Some("common.items.npc_armor.bird_large.phoenix"),
_ => None, _ => None,
}, },
Body::Golem(body) => match body.species { Body::Golem(body) => match body.species {

View File

@ -59,6 +59,13 @@ pub enum ProjectileConstructor {
knockback: f32, knockback: f32,
energy_regen: f32, energy_regen: f32,
}, },
FireDroplet {
damage: f32,
radius: f32,
energy_regen: f32,
min_falloff: f32,
reagent: Option<Reagent>,
},
Fireball { Fireball {
damage: f32, damage: f32,
radius: f32, radius: f32,
@ -253,6 +260,56 @@ impl ProjectileConstructor {
is_point: true, is_point: true,
} }
}, },
FireDroplet {
damage,
radius,
energy_regen,
min_falloff,
reagent,
} => {
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
.with_requirement(CombatRequirement::AnyDamage);
let buff = CombatEffect::Buff(CombatBuff {
kind: BuffKind::Burning,
dur_secs: 4.0,
strength: CombatBuffStrength::DamageFraction(1.0),
chance: 1.0,
})
.adjusted_by_stats(tool_stats);
let damage = AttackDamage::new(
Damage {
source: DamageSource::Explosion,
kind: DamageKind::Energy,
value: damage,
},
Some(GroupTarget::OutOfGroup),
instance,
)
.with_effect(buff);
let attack = Attack::default()
.with_damage(damage)
.with_precision(precision_mult)
.with_effect(energy)
.with_combo_increment();
let explosion = Explosion {
effects: vec![
RadiusEffect::Attack(attack),
RadiusEffect::TerrainDestruction(2.0, Rgb::black()),
],
radius,
reagent,
min_falloff,
};
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: true,
is_sticky: true,
is_point: true,
}
},
Fireball { Fireball {
damage, damage,
radius, radius,
@ -915,6 +972,16 @@ impl ProjectileConstructor {
*damage *= power; *damage *= power;
*energy_regen *= regen; *energy_regen *= regen;
}, },
FireDroplet {
ref mut damage,
ref mut energy_regen,
ref mut radius,
..
} => {
*damage *= power;
*energy_regen *= regen;
*radius *= range;
},
Fireball { Fireball {
ref mut damage, ref mut damage,
ref mut energy_regen, ref mut energy_regen,
@ -1034,6 +1101,7 @@ impl ProjectileConstructor {
match self { match self {
Arrow { .. } => false, Arrow { .. } => false,
Knife { .. } => false, Knife { .. } => false,
FireDroplet { .. } => true,
Fireball { .. } => true, Fireball { .. } => true,
Frostball { .. } => true, Frostball { .. } => true,
Poisonball { .. } => true, Poisonball { .. } => true,

View File

@ -146,6 +146,9 @@ pub enum Outcome {
TeleportedByPortal { TeleportedByPortal {
pos: Vec3<f32>, pos: Vec3<f32>,
}, },
FromTheAshes {
pos: Vec3<f32>,
},
} }
impl Outcome { impl Outcome {
@ -177,6 +180,7 @@ impl Outcome {
| Outcome::GroundDig { pos } | Outcome::GroundDig { pos }
| Outcome::PortalActivated { pos } | Outcome::PortalActivated { pos }
| Outcome::TeleportedByPortal { pos} | Outcome::TeleportedByPortal { pos}
| Outcome::FromTheAshes { pos }
| Outcome::Glider { pos, .. } => Some(*pos), | Outcome::Glider { pos, .. } => Some(*pos),
Outcome::BreakBlock { pos, .. } Outcome::BreakBlock { pos, .. }
| Outcome::SpriteUnlocked { pos } | Outcome::SpriteUnlocked { pos }

View File

@ -29,7 +29,7 @@ pub struct StaticData {
/// Has information used to construct the auras /// Has information used to construct the auras
pub auras: Vec<AuraBuffConstructor>, pub auras: Vec<AuraBuffConstructor>,
/// How long aura lasts /// How long aura lasts
pub aura_duration: Secs, pub aura_duration: Option<Secs>,
/// Radius of aura /// Radius of aura
pub range: f32, pub range: f32,
/// What key is used to press ability /// What key is used to press ability
@ -78,7 +78,8 @@ impl CharacterBehavior for Data {
let mut aura = aura_data.to_aura( let mut aura = aura_data.to_aura(
data.uid, data.uid,
self.static_data.range, self.static_data.range,
Some(self.static_data.aura_duration), // check for indefinite aura
self.static_data.aura_duration,
targets, targets,
*data.time, *data.time,
); );

View File

@ -4,8 +4,11 @@ use crate::{
DamageKind, DamageSource, GroupTarget, DamageKind, DamageSource, GroupTarget,
}, },
comp::{ comp::{
beam, body::biped_large, character_state::OutputEvents, object::Body::Flamethrower, Body, beam,
CharacterState, Ori, StateUpdate, body::{biped_large, bird_large},
character_state::OutputEvents,
object::Body::Flamethrower,
Body, CharacterState, Ori, StateUpdate,
}, },
event::LocalEvent, event::LocalEvent,
outcome::Outcome, outcome::Outcome,
@ -242,8 +245,13 @@ impl CharacterBehavior for Data {
fn height_offset(body: &Body, look_dir: Dir, velocity: Vec3<f32>, on_ground: Option<Block>) -> f32 { fn height_offset(body: &Body, look_dir: Dir, velocity: Vec3<f32>, on_ground: Option<Block>) -> f32 {
match body { match body {
// Hack to make the beam offset correspond to the animation // Hack to make the beam offset correspond to the animation
Body::BirdLarge(_) => { Body::BirdLarge(b) => {
body.height() * 0.3 let height_factor = match b.species {
bird_large::Species::Phoenix => 0.5,
bird_large::Species::Cockatrice => 0.4,
_ => 0.3,
};
body.height() * height_factor
+ if on_ground.is_none() { + if on_ground.is_none() {
(2.0 - velocity.xy().magnitude() * 0.25).max(-1.0) (2.0 - velocity.xy().magnitude() * 0.25).max(-1.0)
} else { } else {

View File

@ -3,8 +3,11 @@ use crate::{
self, self,
character_state::OutputEvents, character_state::OutputEvents,
inventory::loadout_builder::{self, LoadoutBuilder}, inventory::loadout_builder::{self, LoadoutBuilder},
object::Body::FieryTornado,
skillset::skills, skillset::skills,
Behavior, BehaviorCapability, CharacterState, Projectile, StateUpdate, Behavior, BehaviorCapability,
Body::Object,
CharacterState, Projectile, StateUpdate,
}, },
event::{LocalEvent, NpcBuilder, ServerEvent}, event::{LocalEvent, NpcBuilder, ServerEvent},
npc::NPC_NAMES, npc::NPC_NAMES,
@ -185,11 +188,19 @@ impl CharacterBehavior for Data {
is_sticky: false, is_sticky: false,
is_point: false, is_point: false,
}); });
let extra_height =
if self.static_data.summon_info.body == Object(FieryTornado) {
5.0
} else {
0.0
};
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
// Send server event to create npc // Send server event to create npc
output_events.emit_server(ServerEvent::CreateNpc { output_events.emit_server(ServerEvent::CreateNpc {
pos: comp::Pos(collision_vector - Vec3::unit_z() * obstacle_z), pos: comp::Pos(
collision_vector - Vec3::unit_z() * obstacle_z + extra_height,
),
ori: comp::Ori::from(Dir::random_2d(&mut rng)), ori: comp::Ori::from(Dir::random_2d(&mut rng)),
npc: NpcBuilder::new(stats, body, comp::Alignment::Owned(*data.uid)) npc: NpcBuilder::new(stats, body, comp::Alignment::Owned(*data.uid))
.with_skill_set(skill_set) .with_skill_set(skill_set)

View File

@ -9,9 +9,11 @@ use crate::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::{StageSection, *}, utils::{StageSection, *},
}, },
util::Dir,
}; };
use rand::{thread_rng, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::{f32::consts::TAU, time::Duration};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
/// Separated out to condense update portions of character state /// Separated out to condense update portions of character state
@ -37,6 +39,19 @@ pub struct StaticData {
pub ability_info: AbilityInfo, pub ability_info: AbilityInfo,
/// Adds an effect onto the main damage of the attack /// Adds an effect onto the main damage of the attack
pub damage_effect: Option<CombatEffect>, pub damage_effect: Option<CombatEffect>,
/// Whether ablity should be casted from above as aoe or shoot projectiles
/// as normal
pub properties_of_aoe: Option<ProjectileOffset>,
/// Used to specify the attack to the frontend
pub specifier: Option<FrontendSpecifier>,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct ProjectileOffset {
/// Radius of AOE
pub radius: f32,
/// Height of shooting point for AOE's projectiles
pub height: f32,
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -94,10 +109,35 @@ impl CharacterBehavior for Data {
let precision_mult = combat::compute_precision_mult(data.inventory, data.msm); let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
let tool_stats = get_tool_stats(data, self.static_data.ability_info); let tool_stats = get_tool_stats(data, self.static_data.ability_info);
// Gets offsets // Gets offsets
let body_offsets = data let pos: Pos = self.static_data.properties_of_aoe.as_ref().map_or_else(
.body || {
.projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0)); // Default position
let pos = Pos(data.pos.0 + body_offsets); let body_offsets = data.body.projectile_offsets(
update.ori.look_vec(),
data.scale.map_or(1.0, |s| s.0),
);
Pos(data.pos.0 + body_offsets)
},
|aoe_data| {
// Position calculated from aoe_data
let rand_pos = {
let mut rng = thread_rng();
let theta = rng.gen::<f32>() * TAU;
let radius = aoe_data.radius * rng.gen::<f32>().sqrt();
let x = radius * theta.sin();
let y = radius * theta.cos();
vek::Vec2::new(x, y)
};
Pos(data.pos.0 + rand_pos.with_z(aoe_data.height))
},
);
let direction: Dir = if self.static_data.properties_of_aoe.is_some() {
Dir::down()
} else {
data.inputs.look_dir
};
let projectile = self.static_data.projectile.create_projectile( let projectile = self.static_data.projectile.create_projectile(
Some(*data.uid), Some(*data.uid),
precision_mult, precision_mult,
@ -107,7 +147,7 @@ impl CharacterBehavior for Data {
output_events.emit_server(ServerEvent::Shoot { output_events.emit_server(ServerEvent::Shoot {
entity: data.entity, entity: data.entity,
pos, pos,
dir: data.inputs.look_dir, dir: direction,
body: self.static_data.projectile_body, body: self.static_data.projectile_body,
projectile, projectile,
light: self.static_data.projectile_light, light: self.static_data.projectile_light,
@ -167,3 +207,8 @@ impl CharacterBehavior for Data {
update update
} }
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum FrontendSpecifier {
FireRain,
}

View File

@ -4,7 +4,8 @@ use crate::{
character_state::OutputEvents, character_state::OutputEvents,
CharacterState, StateUpdate, CharacterState, StateUpdate,
}, },
event::ServerEvent, event::{LocalEvent, ServerEvent},
outcome::Outcome,
resources::Secs, resources::Secs,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -41,6 +42,8 @@ pub struct StaticData {
pub enforced_limit: bool, pub enforced_limit: bool,
/// What key is used to press ability /// What key is used to press ability
pub ability_info: AbilityInfo, pub ability_info: AbilityInfo,
/// Used to specify an outcome for the buff
pub specifier: Option<FrontendSpecifier>,
} }
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -145,6 +148,12 @@ impl CharacterBehavior for Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(data, self.timer, None),
..*self ..*self
}); });
if let Some(FrontendSpecifier::FromTheAshes) = self.static_data.specifier {
// Send local event used for frontend shenanigans
output_events.emit_local(LocalEvent::CreateOutcome(
Outcome::FromTheAshes { pos: data.pos.0 },
));
}
} else { } else {
update.character = CharacterState::SelfBuff(Data { update.character = CharacterState::SelfBuff(Data {
timer: Duration::default(), timer: Duration::default(),
@ -176,3 +185,8 @@ impl CharacterBehavior for Data {
update update
} }
} }
/// Used to specify a particular effect for frontend purposes
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum FrontendSpecifier {
FromTheAshes,
}

View File

@ -211,35 +211,52 @@ impl Data {
} }
} }
for (site_id, site) in this.sites.iter() for (site_id, site) in this.sites.iter() {
// TODO: Stupid
.filter(|(_, site)| site.world_site.map_or(false, |ws|
matches!(&index.sites.get(ws).kind, SiteKind::Dungeon(_))))
{
let rand_wpos = |rng: &mut SmallRng| { let rand_wpos = |rng: &mut SmallRng| {
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); // don't spawn in buildings
let spread_factor = rng.gen_range(-3..3) * 50;
let spread = if spread_factor == 0 {
100
} else {
spread_factor
};
let wpos2d = site.wpos.map(|e| e + spread);
wpos2d wpos2d
.map(|e| e as f32 + 0.5) .map(|e| e as f32 + 0.5)
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
}; };
let site_kind = site.world_site.map(|ws| &index.sites.get(ws).kind);
let species = [ let Some(species) = [
comp::body::bird_large::Species::Phoenix, Some(comp::body::bird_large::Species::Phoenix)
comp::body::bird_large::Species::Cockatrice, .filter(|_| matches!(site_kind, Some(SiteKind::Dungeon(_)))),
comp::body::bird_large::Species::Roc, Some(comp::body::bird_large::Species::Cockatrice)
comp::body::bird_large::Species::FlameWyvern, .filter(|_| matches!(site_kind, Some(SiteKind::Dungeon(_)))),
comp::body::bird_large::Species::CloudWyvern, Some(comp::body::bird_large::Species::Roc)
comp::body::bird_large::Species::FrostWyvern, .filter(|_| matches!(site_kind, Some(SiteKind::Dungeon(_)))),
comp::body::bird_large::Species::SeaWyvern, Some(comp::body::bird_large::Species::FlameWyvern)
comp::body::bird_large::Species::WealdWyvern, .filter(|_| matches!(site_kind, Some(SiteKind::Dungeon(_)))),
Some(comp::body::bird_large::Species::CloudWyvern)
.filter(|_| matches!(site_kind, Some(SiteKind::Dungeon(_)))),
Some(comp::body::bird_large::Species::FrostWyvern)
.filter(|_| matches!(site_kind, Some(SiteKind::Adlet(_)))),
Some(comp::body::bird_large::Species::SeaWyvern)
.filter(|_| matches!(site_kind, Some(SiteKind::ChapelSite(_)))),
Some(comp::body::bird_large::Species::WealdWyvern)
.filter(|_| matches!(site_kind, Some(SiteKind::GiantTree(_)))),
] ]
.choose(&mut rng) .into_iter()
.unwrap(); .flatten()
.choose(&mut rng) else {
continue;
};
this.npcs.create_npc( this.npcs.create_npc(
Npc::new( Npc::new(
rng.gen(), rng.gen(),
rand_wpos(&mut rng), rand_wpos(&mut rng),
Body::BirdLarge(comp::body::bird_large::Body::random_with(&mut rng, species)), Body::BirdLarge(comp::body::bird_large::Body::random_with(
&mut rng, &species,
)),
Role::Wild, Role::Wild,
) )
.with_home(site_id), .with_home(site_id),

View File

@ -16,7 +16,7 @@ use crate::{
use common::{ use common::{
astar::{Astar, PathResult}, astar::{Astar, PathResult},
comp::{ comp::{
self, self, bird_large,
compass::{Direction, Distance}, compass::{Direction, Distance},
dialogue::Subject, dialogue::Subject,
Content, Content,
@ -1210,15 +1210,21 @@ fn bird_large() -> impl Action<DefaultState> {
.map(|e| e + ctx.rng.gen_range(-0.1..0.1)) .map(|e| e + ctx.rng.gen_range(-0.1..0.1))
.try_normalized() .try_normalized()
.unwrap_or_default(); .unwrap_or_default();
let bearing_dist = 24.0; let bearing_dist = 15.0;
let mut pos = ctx.npc.wpos.xy() + *bearing * bearing_dist; let mut pos = ctx.npc.wpos.xy() + *bearing * bearing_dist;
let is_deep_water = ctx let is_deep_water =
matches!(ctx.npc.body, common::comp::Body::BirdLarge(b) if matches!(b.species, bird_large::Species::SeaWyvern))
|| ctx
.world .world
.sim() .sim()
.get(pos.as_().wpos_to_cpos()) .get(pos.as_().wpos_to_cpos())
.map_or(true, |c| { .map_or(true, |c| {
c.alt - c.water_alt < -120.0 && (c.river.is_ocean() || c.river.is_lake()) c.alt - c.water_alt < -120.0 && (c.river.is_ocean() || c.river.is_lake())
}); });
if is_deep_water {
*bearing *= -1.0;
pos = ctx.npc.wpos.xy() + *bearing * bearing_dist;
};
// when high tree_density fly high, otherwise fly low-mid // when high tree_density fly high, otherwise fly low-mid
let npc_pos = ctx.npc.wpos.xy(); let npc_pos = ctx.npc.wpos.xy();
let trees = ctx let trees = ctx
@ -1231,35 +1237,56 @@ fn bird_large() -> impl Action<DefaultState> {
} else { } else {
ctx.rng.gen_range(0.4..0.9) ctx.rng.gen_range(0.4..0.9)
}; };
if !is_deep_water {
goto_2d_flying(
pos,
0.1,
bearing_dist,
8.0,
8.0,
ctx.npc.body.flying_height() * height_factor,
)
} else { let data = ctx.state.data();
*bearing *= -1.0; // without destination site fly to next waypoint
let mut dest_site = pos;
pos = ctx.npc.wpos.xy() + *bearing * 24.0; if let Some(home) = ctx.npc.home {
let is_home = ctx.npc.current_site.map_or(false, |site| home == site);
goto_2d_flying( if is_home {
pos, if let Some((id, _)) = data
0.1, .sites
bearing_dist, .iter()
8.0, .filter(|(id, site)| {
8.0, *id != home
ctx.npc.body.flying_height() * height_factor, && site.world_site.map_or(false, |site| {
) match ctx.npc.body {
common::comp::Body::BirdLarge(b) => match b.species {
bird_large::Species::SeaWyvern => matches!(&ctx.index.sites.get(site).kind, SiteKind::ChapelSite(_)),
bird_large::Species::FrostWyvern => matches!(&ctx.index.sites.get(site).kind, SiteKind::Adlet(_)),
bird_large::Species::WealdWyvern => matches!(&ctx.index.sites.get(site).kind, SiteKind::GiantTree(_)),
_ => matches!(&ctx.index.sites.get(site).kind, SiteKind::Dungeon(_)),
},
_ => matches!(&ctx.index.sites.get(site).kind, SiteKind::Dungeon(_)),
} }
// If we are too far away from our goal position we can stop since we aren't going to a specific place. })
})
/*choose closest destination:
.min_by_key(|(_, site)| site.wpos.as_().distance(npc_pos) as i32)*/
//choose random destination:
.choose(&mut ctx.rng)
{
ctx.controller.set_new_home(id)
}
} else if let Some(site) = data.sites.get(home) {
dest_site = site.wpos.as_::<f32>()
}
}
goto_2d_flying(
pos,
0.2,
bearing_dist,
8.0,
8.0,
ctx.npc.body.flying_height() * height_factor,
)
// If we are too far away from our waypoint position we can stop since we aren't going to a specific place.
// If waypoint position is further away from destination site find a new waypoint
.stop_if(move |ctx: &mut NpcCtx| { .stop_if(move |ctx: &mut NpcCtx| {
ctx.npc.wpos.xy().distance_squared(pos) > (bearing_dist + 5.0).powi(2) ctx.npc.wpos.xy().distance_squared(pos) > (bearing_dist + 5.0).powi(2)
|| dest_site.distance_squared(pos) > dest_site.distance_squared(npc_pos)
}) })
// If goal position wasn't reached within 20 seconds we're probably stuck and need to find a new goal position. // If waypoint position wasn't reached within 10 seconds we're probably stuck and need to find a new waypoint.
.stop_if(timeout(10.0)) .stop_if(timeout(10.0))
.debug({ .debug({
let bearing = *bearing; let bearing = *bearing;

View File

@ -1075,6 +1075,7 @@ impl<'a> AgentData<'a> {
| "Gnarling Totem Red" | "Gnarling Totem Red"
| "Gnarling Totem Green" | "Gnarling Totem Green"
| "Gnarling Totem White" => Tactic::RadialTurret, | "Gnarling Totem White" => Tactic::RadialTurret,
"FieryTornado" => Tactic::FieryTornado,
"Yeti" => Tactic::Yeti, "Yeti" => Tactic::Yeti,
"Harvester" => Tactic::Harvester, "Harvester" => Tactic::Harvester,
"Cardinal" => Tactic::Cardinal, "Cardinal" => Tactic::Cardinal,
@ -1500,6 +1501,7 @@ impl<'a> AgentData<'a> {
read_data, read_data,
), ),
Tactic::RadialTurret => self.handle_radial_turret_attack(controller), Tactic::RadialTurret => self.handle_radial_turret_attack(controller),
Tactic::FieryTornado => self.handle_fiery_tornado_attack(agent, controller),
Tactic::Yeti => { Tactic::Yeti => {
self.handle_yeti_attack(agent, controller, &attack_data, tgt_data, read_data) self.handle_yeti_attack(agent, controller, &attack_data, tgt_data, read_data)
}, },

View File

@ -2678,6 +2678,23 @@ impl<'a> AgentData<'a> {
controller.push_basic_input(InputKind::Primary); controller.push_basic_input(InputKind::Primary);
} }
pub fn handle_fiery_tornado_attack(&self, agent: &mut Agent, controller: &mut Controller) {
enum Conditions {
AuraEmited = 0,
}
if matches!(self.char_state, CharacterState::BasicAura(c) if matches!(c.stage_section, StageSection::Recover))
{
agent.combat_state.conditions[Conditions::AuraEmited as usize] = true;
}
// 1 time use of aura
if !agent.combat_state.conditions[Conditions::AuraEmited as usize] {
controller.push_basic_input(InputKind::Secondary);
} else {
// Spin
controller.push_basic_input(InputKind::Primary);
}
}
pub fn handle_mindflayer_attack( pub fn handle_mindflayer_attack(
&self, &self,
agent: &mut Agent, agent: &mut Agent,
@ -2975,106 +2992,141 @@ impl<'a> AgentData<'a> {
read_data: &ReadData, read_data: &ReadData,
_rng: &mut impl Rng, _rng: &mut impl Rng,
) { ) {
const PHOENIX_HEAL_THRESHOLD: f32 = 0.20;
enum Conditions {
Healed = 0,
}
enum ActionStateTimers { enum ActionStateTimers {
AttackTimer = 0, AttackTimer1,
AttackTimer2,
} }
// Set fly to false
controller.push_cancel_input(InputKind::Fly); let attack_timer_1 =
if attack_data.dist_sqrd > 30.0_f32.powi(2) { if agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] < 2.0 {
if entities_have_line_of_sight( 0
self.pos, } else if agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] < 4.0 {
self.body, 1
self.scale, } else if agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] < 6.0 {
tgt_data.pos, 2
tgt_data.body, } else {
tgt_data.scale, 3
read_data, };
) && attack_data.angle < 15.0 agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] += read_data.dt.0;
{ if agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] > 8.0 {
controller.push_basic_input(InputKind::Primary); // Reset timer
agent.combat_state.timers[ActionStateTimers::AttackTimer1 as usize] = 0.0;
} }
if let Some((bearing, speed)) = agent.chaser.chase( let (attack_timer_2, speed) =
&*read_data.terrain, if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] < 3.0 {
self.pos.0, // fly high
self.vel.0, (0, 2.0)
tgt_data.pos.0, } else if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] < 6.0 {
TraversalConfig { // attack_mid_1
min_tgt_dist: 1.25, (1, 2.0)
..self.traversal_config } else if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] < 9.0 {
}, // fly high
) { (0, 3.0)
controller.inputs.move_dir = } else if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] < 16.0 {
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed; // attack_mid_2
if (self.pos.0.z - tgt_data.pos.0.z) < 20.0 { (2, 1.0)
} else if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] < 20.0 {
// fly low
(5, 20.0)
} else {
// attack_close
(3, 1.0)
};
agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] += read_data.dt.0;
if agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] > 28.0 {
// Reset timer
agent.combat_state.timers[ActionStateTimers::AttackTimer2 as usize] = 0.0;
}
// Fly to target
let dir_to_target = ((tgt_data.pos.0 + Vec3::unit_z() * 1.5) - self.pos.0)
.try_normalized()
.unwrap_or_else(Vec3::zero);
controller.inputs.move_dir = dir_to_target.xy() * speed;
// Always fly! If the floor can't touch you, it can't hurt you...
controller.push_basic_input(InputKind::Fly); controller.push_basic_input(InputKind::Fly);
controller.inputs.move_z = 1.0; // Flee from the ground! The internet told me it was lava!
} // If on the ground, jump with every last ounce of energy, holding onto
} // all that is dear in life and straining for the wide open skies.
} else if !read_data if self.physics_state.on_ground.is_some() {
controller.push_basic_input(InputKind::Jump);
} else {
// Use a proportional controller with a coefficient of 1.0 to
// maintain altidude at the the provided set point
let mut maintain_altitude = |set_point| {
let alt = read_data
.terrain .terrain
.ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * 2.0)) .ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * 7.0))
.until(Block::is_solid) .until(Block::is_solid)
.cast() .cast()
.1 .0;
.map_or(true, |b| b.is_some()) let error = set_point - alt;
controller.inputs.move_z = error;
};
// heal once - from_the_ashes
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
if matches!(self.char_state, CharacterState::SelfBuff(c) if matches!(c.stage_section, StageSection::Recover))
{ {
// Do not increment the timer during this movement agent.combat_state.conditions[Conditions::Healed as usize] = true;
// The next stage shouldn't trigger until the entity
// is on the ground
controller.push_basic_input(InputKind::Fly);
let move_dir = tgt_data.pos.0 - self.pos.0;
controller.inputs.move_dir =
move_dir.xy().try_normalized().unwrap_or_else(Vec2::zero) * 2.0;
controller.inputs.move_z = move_dir.z - 0.5;
if attack_data.dist_sqrd > (4.0 * attack_data.min_attack_dist).powi(2)
&& attack_data.angle < 15.0
{
controller.push_basic_input(InputKind::Primary);
} }
} else if attack_data.dist_sqrd > (3.0 * attack_data.min_attack_dist).powi(2) { if !agent.combat_state.conditions[Conditions::Healed as usize]
self.path_toward_target( && PHOENIX_HEAL_THRESHOLD > health_fraction
agent,
controller,
tgt_data.pos.0,
read_data,
Path::Separate,
None,
);
} else if self.energy.current() > 60.0
&& agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] < 3.0
&& attack_data.angle < 15.0
{ {
// Shockwave controller.push_basic_input(InputKind::Ability(4));
controller.push_basic_input(InputKind::Ability(0)); } else if (tgt_data.pos.0 - self.pos.0).xy().magnitude_squared() > (35.0_f32).powi(2) {
// Move towards the target slowly // heat laser
self.path_toward_target( maintain_altitude(2.0);
agent, controller.push_basic_input(InputKind::Ability(3))
controller,
tgt_data.pos.0,
read_data,
Path::Separate,
Some(0.5),
);
agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] += read_data.dt.0;
} else if agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] < 6.0
&& attack_data.angle < 90.0
&& attack_data.in_min_range()
{
// Triple strike
controller.push_basic_input(InputKind::Secondary);
agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] += read_data.dt.0;
} else { } else {
// Reset timer match attack_timer_2 {
agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] = 0.0; 0 => maintain_altitude(3.0),
// Target is behind us or the timer needs to be reset. Chase target 1 => {
self.path_toward_target( //summontornados
agent, controller.push_basic_input(InputKind::Ability(1));
controller, },
tgt_data.pos.0, 2 => {
read_data, // firerain
Path::Separate, controller.push_basic_input(InputKind::Ability(2));
None, },
); 3 => {
if attack_data.dist_sqrd < 4.0_f32.powi(2) && attack_data.angle < 150.0 {
// close range attack
match attack_timer_1 {
1 => {
// short strike
controller.push_basic_input(InputKind::Primary);
},
3 => {
// long strike
controller.push_basic_input(InputKind::Secondary)
},
_ => {
// leg strike
controller.push_basic_input(InputKind::Ability(0))
},
}
} else {
match attack_timer_1 {
0 | 2 => {
maintain_altitude(2.0);
},
_ => {
// heat laser
controller.push_basic_input(InputKind::Ability(3))
},
}
}
},
_ => {
maintain_altitude(2.0);
},
}
}
} }
} }

View File

@ -176,6 +176,7 @@ pub enum Tactic {
FixedTurret, FixedTurret,
RotatingTurret, RotatingTurret,
RadialTurret, RadialTurret,
FieryTornado,
SimpleDouble, SimpleDouble,
// u8s are weights that each ability gets used, if it can be used // u8s are weights that each ability gets used, if it can be used
RandomAbilities { RandomAbilities {

View File

@ -162,7 +162,7 @@ fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo
} else { } else {
let config_asset = match npc.body { let config_asset = match npc.body {
Body::BirdLarge(body) => match body.species { Body::BirdLarge(body) => match body.species {
comp::bird_large::Species::Phoenix => "common.entity.wild.peaceful.phoenix", comp::bird_large::Species::Phoenix => "common.entity.wild.aggressive.phoenix",
comp::bird_large::Species::Cockatrice => "common.entity.wild.aggressive.cockatrice", comp::bird_large::Species::Cockatrice => "common.entity.wild.aggressive.cockatrice",
comp::bird_large::Species::Roc => "common.entity.wild.aggressive.roc", comp::bird_large::Species::Roc => "common.entity.wild.aggressive.roc",
comp::bird_large::Species::CloudWyvern => { comp::bird_large::Species::CloudWyvern => {

View File

@ -0,0 +1,93 @@
use super::{
super::{vek::*, Animation},
BirdLargeSkeleton, SkeletonAttr,
};
use common::states::utils::StageSection;
pub struct AuraAnimation;
impl Animation for AuraAnimation {
type Dependency<'a> = (Option<StageSection>, bool);
type Skeleton = BirdLargeSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"bird_large_aura\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "bird_large_aura")]
fn update_skeleton_inner(
skeleton: &Self::Skeleton,
(stage_section, on_ground): Self::Dependency<'_>,
anim_time: f32,
_rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let (movement1base, movement2base, movement3, _twitch) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.5), 0.0, 0.0, 0.0),
Some(StageSection::Action) => (1.0, anim_time.min(1.0).powf(0.1), 0.0, anim_time),
Some(StageSection::Recover) => (1.0, 1.0, anim_time, 1.0),
_ => (0.0, 0.0, 0.0, 0.0),
};
let pullback = 1.0 - movement3;
let movement1abs = movement1base * pullback;
let _movement2abs = movement2base * pullback;
let wave_slow_cos = (anim_time * 4.5).cos();
next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + movement1abs * 1.5);
next.chest.orientation = Quaternion::rotation_x(movement1abs * 1.0);
next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1);
next.neck.orientation = Quaternion::rotation_x(-0.2);
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1);
next.head.orientation = Quaternion::rotation_x(wave_slow_cos * 0.01);
next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1);
next.beak.orientation = Quaternion::rotation_x(wave_slow_cos * -0.02 - 0.02);
next.tail_front.position = Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1);
next.tail_front.orientation = Quaternion::rotation_x(0.6);
next.tail_rear.position = Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1);
next.tail_rear.orientation = Quaternion::rotation_x(-0.2);
if on_ground {
next.wing_in_l.position = Vec3::new(-s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2);
next.wing_in_r.position = Vec3::new(s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2);
next.wing_in_l.orientation = Quaternion::rotation_y(-0.8 + movement1abs * 1.5)
* Quaternion::rotation_z(0.2 + movement1abs * -0.8);
next.wing_in_r.orientation = Quaternion::rotation_y(0.8 + movement1abs * -1.5)
* Quaternion::rotation_z(-0.2 + movement1abs * 0.8);
next.wing_mid_l.position = Vec3::new(-s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2);
next.wing_mid_r.position = Vec3::new(s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2);
next.wing_mid_l.orientation =
Quaternion::rotation_y(-0.1) * Quaternion::rotation_z(0.7);
next.wing_mid_r.orientation =
Quaternion::rotation_y(0.1) * Quaternion::rotation_z(-0.7);
next.wing_out_l.position = Vec3::new(-s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2);
next.wing_out_r.position = Vec3::new(s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2);
next.wing_out_l.orientation =
Quaternion::rotation_y(-0.4) * Quaternion::rotation_z(0.2);
next.wing_out_r.orientation =
Quaternion::rotation_y(0.4) * Quaternion::rotation_z(-0.2);
next.leg_l.position = Vec3::new(-s_a.leg.0, s_a.leg.1, s_a.leg.2);
next.leg_l.orientation = Quaternion::rotation_x(0.0);
next.leg_r.position = Vec3::new(s_a.leg.0, s_a.leg.1, s_a.leg.2);
next.leg_r.orientation = Quaternion::rotation_x(0.0);
next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1, s_a.foot.2);
next.foot_l.orientation = Quaternion::rotation_x(0.0);
next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2);
next.foot_r.orientation = Quaternion::rotation_x(0.0);
}
next
}
}

View File

@ -6,7 +6,7 @@ use common::{states::utils::StageSection, util::Dir};
pub struct BreatheAnimation; pub struct BreatheAnimation;
type BreatheAnimationDependency = ( type BreatheAnimationDependency<'a> = (
Vec3<f32>, Vec3<f32>,
f32, f32,
Vec3<f32>, Vec3<f32>,
@ -15,10 +15,11 @@ type BreatheAnimationDependency = (
f32, f32,
Dir, Dir,
bool, bool,
Option<&'a str>,
); );
impl Animation for BreatheAnimation { impl Animation for BreatheAnimation {
type Dependency<'a> = BreatheAnimationDependency; type Dependency<'a> = BreatheAnimationDependency<'a>;
type Skeleton = BirdLargeSkeleton; type Skeleton = BirdLargeSkeleton;
#[cfg(feature = "use-dyn-lib")] #[cfg(feature = "use-dyn-lib")]
@ -27,7 +28,17 @@ impl Animation for BreatheAnimation {
#[cfg_attr(feature = "be-dyn-lib", export_name = "bird_large_breathe")] #[cfg_attr(feature = "be-dyn-lib", export_name = "bird_large_breathe")]
fn update_skeleton_inner( fn update_skeleton_inner(
skeleton: &Self::Skeleton, skeleton: &Self::Skeleton,
(velocity,global_time, _orientation, _last_ori, stage_section, timer, look_dir, on_ground): Self::Dependency<'_>, (
velocity,
global_time,
_orientation,
_last_ori,
stage_section,
timer,
look_dir,
on_ground,
ability_id,
): Self::Dependency<'_>,
anim_time: f32, anim_time: f32,
_rate: &mut f32, _rate: &mut f32,
s_a: &SkeletonAttr, s_a: &SkeletonAttr,
@ -106,6 +117,13 @@ impl Animation for BreatheAnimation {
next.tail_rear.orientation = next.tail_rear.orientation =
Quaternion::rotation_x(-movement1abs * 0.1 + movement2abs * 0.1 + twitch2 * -0.2); Quaternion::rotation_x(-movement1abs * 0.1 + movement2abs * 0.1 + twitch2 * -0.2);
} else { } else {
match ability_id {
Some("common.abilities.custom.birdlargefire.heat_laser") => {
next.chest.orientation = Quaternion::rotation_x(
movement1abs * 0.2 - movement2abs * 0.5 + twitch2 * 0.03,
);
next.chest.position =
Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + movement2abs * -3.0);
next.neck.orientation = Quaternion::rotation_x( next.neck.orientation = Quaternion::rotation_x(
movement1abs * -0.4 movement1abs * -0.4
+ movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0), + movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0),
@ -116,8 +134,21 @@ impl Animation for BreatheAnimation {
+ movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0) + movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0)
+ look_dir.z * 0.4, + look_dir.z * 0.4,
); );
} },
_ => {
next.neck.orientation = Quaternion::rotation_x(
movement1abs * -0.4
+ movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0),
);
next.head.orientation = Quaternion::rotation_x(
movement1abs * 0.5
+ movement2abs * (-0.5 + velocity.xy().magnitude() * 0.2).min(0.0)
+ look_dir.z * 0.4,
);
},
};
}
next next
} }
} }

View File

@ -25,7 +25,7 @@ impl Animation for ComboAnimation {
fn update_skeleton_inner( fn update_skeleton_inner(
skeleton: &Self::Skeleton, skeleton: &Self::Skeleton,
( (
_ability_id, ability_id,
stage_section, stage_section,
current_strike, current_strike,
global_time, global_time,
@ -79,7 +79,21 @@ impl Animation for ComboAnimation {
} * 1.3; } * 1.3;
for strike in 0..=current_strike { for strike in 0..=current_strike {
match strike { match ability_id {
Some("common.abilities.custom.birdlargefire.legstrike") => match strike {
0..=2 => {
next.chest.position = Vec3::new(
0.0,
s_a.chest.0,
s_a.chest.1 + wave_slow_cos * 0.06 + move2 * -6.0,
);
next.chest.orientation = Quaternion::rotation_x(move1 * 0.5 - move2 * 0.8);
next.leg_r.orientation = Quaternion::rotation_x(move1 * 1.5 + move2 * -2.5);
next.leg_l.orientation = Quaternion::rotation_x(move1 * 1.5 + move2 * -2.5);
},
_ => {},
},
_ => match strike {
0..=2 => { 0..=2 => {
next.chest.position = Vec3::new( next.chest.position = Vec3::new(
0.0, 0.0,
@ -98,14 +112,16 @@ impl Animation for ComboAnimation {
* Quaternion::rotation_y(move1mirror * 0.5); * Quaternion::rotation_y(move1mirror * 0.5);
next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1); next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1);
next.beak.orientation = next.beak.orientation = Quaternion::rotation_x(
Quaternion::rotation_x(wave_slow_cos * -0.02 + move1 * -0.5 + move2 * 0.5); wave_slow_cos * -0.02 + move1 * -0.5 + move2 * 0.5,
);
if on_ground { if on_ground {
next.tail_front.position = next.tail_front.position =
Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1); Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1);
next.tail_front.orientation = Quaternion::rotation_x(-move1 * 0.2); next.tail_front.orientation = Quaternion::rotation_x(-move1 * 0.2);
next.tail_rear.position = Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1); next.tail_rear.position =
Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1);
next.tail_rear.orientation = Quaternion::rotation_x(0.0); next.tail_rear.orientation = Quaternion::rotation_x(0.0);
next.wing_in_l.position = next.wing_in_l.position =
@ -124,7 +140,8 @@ impl Animation for ComboAnimation {
Vec3::new(-s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); Vec3::new(-s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2);
next.wing_mid_r.position = next.wing_mid_r.position =
Vec3::new(s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2); Vec3::new(s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2);
next.wing_mid_l.orientation = Quaternion::rotation_y(-0.1 + move1 * -0.5) next.wing_mid_l.orientation =
Quaternion::rotation_y(-0.1 + move1 * -0.5)
* Quaternion::rotation_z(0.7 + move1 * -0.7); * Quaternion::rotation_z(0.7 + move1 * -0.7);
next.wing_mid_r.orientation = Quaternion::rotation_y(0.1 + move1 * 0.5) next.wing_mid_r.orientation = Quaternion::rotation_y(0.1 + move1 * 0.5)
* Quaternion::rotation_z(-0.7 + move1 * 0.7); * Quaternion::rotation_z(-0.7 + move1 * 0.7);
@ -133,13 +150,15 @@ impl Animation for ComboAnimation {
Vec3::new(-s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); Vec3::new(-s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2);
next.wing_out_r.position = next.wing_out_r.position =
Vec3::new(s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2); Vec3::new(s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2);
next.wing_out_l.orientation = Quaternion::rotation_y(-0.2 + move1 * -0.3) next.wing_out_l.orientation =
Quaternion::rotation_y(-0.2 + move1 * -0.3)
* Quaternion::rotation_z(0.2); * Quaternion::rotation_z(0.2);
next.wing_out_r.orientation = Quaternion::rotation_y(0.2 + move1 * 0.3) next.wing_out_r.orientation = Quaternion::rotation_y(0.2 + move1 * 0.3)
* Quaternion::rotation_z(-0.2); * Quaternion::rotation_z(-0.2);
} }
}, },
_ => {}, _ => {},
},
} }
} }

View File

@ -1,4 +1,5 @@
pub mod alpha; pub mod alpha;
pub mod aura;
pub mod breathe; pub mod breathe;
pub mod combomelee; pub mod combomelee;
pub mod dash; pub mod dash;
@ -6,6 +7,7 @@ pub mod feed;
pub mod fly; pub mod fly;
pub mod idle; pub mod idle;
pub mod run; pub mod run;
pub mod selfbuff;
pub mod shockwave; pub mod shockwave;
pub mod shoot; pub mod shoot;
pub mod stunned; pub mod stunned;
@ -14,10 +16,11 @@ pub mod swim;
// Reexports // Reexports
pub use self::{ pub use self::{
alpha::AlphaAnimation, breathe::BreatheAnimation, combomelee::ComboAnimation, alpha::AlphaAnimation, aura::AuraAnimation, breathe::BreatheAnimation,
dash::DashAnimation, feed::FeedAnimation, fly::FlyAnimation, idle::IdleAnimation, combomelee::ComboAnimation, dash::DashAnimation, feed::FeedAnimation, fly::FlyAnimation,
run::RunAnimation, shockwave::ShockwaveAnimation, shoot::ShootAnimation, idle::IdleAnimation, run::RunAnimation, selfbuff::SelfBuffAnimation,
stunned::StunnedAnimation, summon::SummonAnimation, swim::SwimAnimation, shockwave::ShockwaveAnimation, shoot::ShootAnimation, stunned::StunnedAnimation,
summon::SummonAnimation, swim::SwimAnimation,
}; };
use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton}; use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton};

View File

@ -0,0 +1,99 @@
use super::{
super::{vek::*, Animation},
BirdLargeSkeleton, SkeletonAttr,
};
use common::states::utils::StageSection;
pub struct SelfBuffAnimation;
impl Animation for SelfBuffAnimation {
type Dependency<'a> = (Option<StageSection>, bool);
type Skeleton = BirdLargeSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"bird_large_selfbuff\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "bird_large_selfbuff")]
fn update_skeleton_inner(
skeleton: &Self::Skeleton,
(stage_section, on_ground): Self::Dependency<'_>,
anim_time: f32,
_rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let (movement1base, movement2base, movement3, _twitch) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.5), 0.0, 0.0, 0.0),
Some(StageSection::Action) => (1.0, anim_time.min(1.0).powf(0.1), 0.0, anim_time),
Some(StageSection::Recover) => (1.0, 1.0, anim_time, 1.0),
_ => (0.0, 0.0, 0.0, 0.0),
};
let pullback = 1.0 - movement3;
let movement1abs = movement1base * pullback;
let movement2abs = movement2base * pullback;
let wave_slow_cos = (anim_time * 4.5).cos();
next.chest.position = Vec3::new(
0.0,
s_a.chest.0,
s_a.chest.1 + movement1abs * 2.5 + movement2abs * 2.5,
);
next.chest.orientation = Quaternion::rotation_x(movement1abs * 1.0 + movement2abs * 1.0);
next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1);
next.neck.orientation = Quaternion::rotation_x(-0.2);
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1);
next.head.orientation = Quaternion::rotation_x(wave_slow_cos * 0.01);
next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1);
next.beak.orientation = Quaternion::rotation_x(wave_slow_cos * -0.02 - 0.02);
next.tail_front.position = Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1);
next.tail_front.orientation = Quaternion::rotation_x(0.6);
next.tail_rear.position = Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1);
next.tail_rear.orientation = Quaternion::rotation_x(-0.2);
if on_ground {
next.wing_in_l.position = Vec3::new(-s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2);
next.wing_in_r.position = Vec3::new(s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2);
next.wing_in_l.orientation =
Quaternion::rotation_y(-0.8 + movement1abs * 1.6 + movement2abs * 1.6)
* Quaternion::rotation_z(0.2 + movement1abs * -0.8);
next.wing_in_r.orientation =
Quaternion::rotation_y(0.8 + movement1abs * -1.6 + movement2abs * -1.6)
* Quaternion::rotation_z(-0.2 + movement1abs * 0.8);
next.wing_mid_l.position = Vec3::new(-s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2);
next.wing_mid_r.position = Vec3::new(s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2);
next.wing_mid_l.orientation =
Quaternion::rotation_y(-0.1) * Quaternion::rotation_z(0.7);
next.wing_mid_r.orientation =
Quaternion::rotation_y(0.1) * Quaternion::rotation_z(-0.7);
next.wing_out_l.position = Vec3::new(-s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2);
next.wing_out_r.position = Vec3::new(s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2);
next.wing_out_l.orientation =
Quaternion::rotation_y(-0.4) * Quaternion::rotation_z(0.2);
next.wing_out_r.orientation =
Quaternion::rotation_y(0.4) * Quaternion::rotation_z(-0.2);
next.leg_l.position = Vec3::new(-s_a.leg.0, s_a.leg.1, s_a.leg.2);
next.leg_l.orientation = Quaternion::rotation_x(0.0);
next.leg_r.position = Vec3::new(s_a.leg.0, s_a.leg.1, s_a.leg.2);
next.leg_r.orientation = Quaternion::rotation_x(0.0);
next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1, s_a.foot.2);
next.foot_l.orientation = Quaternion::rotation_x(0.0);
next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2);
next.foot_r.orientation = Quaternion::rotation_x(0.0);
}
next
}
}

View File

@ -6,10 +6,18 @@ use common::{states::utils::StageSection, util::Dir};
pub struct ShootAnimation; pub struct ShootAnimation;
type ShootAnimationDependency = (Vec3<f32>, f32, Option<StageSection>, f32, Dir, bool); type ShootAnimationDependency<'a> = (
Vec3<f32>,
f32,
Option<StageSection>,
f32,
Dir,
bool,
Option<&'a str>,
);
impl Animation for ShootAnimation { impl Animation for ShootAnimation {
type Dependency<'a> = ShootAnimationDependency; type Dependency<'a> = ShootAnimationDependency<'a>;
type Skeleton = BirdLargeSkeleton; type Skeleton = BirdLargeSkeleton;
#[cfg(feature = "use-dyn-lib")] #[cfg(feature = "use-dyn-lib")]
@ -18,13 +26,88 @@ impl Animation for ShootAnimation {
#[cfg_attr(feature = "be-dyn-lib", export_name = "bird_large_shoot")] #[cfg_attr(feature = "be-dyn-lib", export_name = "bird_large_shoot")]
fn update_skeleton_inner( fn update_skeleton_inner(
skeleton: &Self::Skeleton, skeleton: &Self::Skeleton,
(velocity, global_time, stage_section, timer, look_dir, on_ground): Self::Dependency<'_>, (velocity, global_time, stage_section, timer, look_dir, on_ground, ability_id,
): Self::Dependency<'_>,
anim_time: f32, anim_time: f32,
_rate: &mut f32, _rate: &mut f32,
s_a: &SkeletonAttr, s_a: &SkeletonAttr,
) -> Self::Skeleton { ) -> Self::Skeleton {
let mut next = (*skeleton).clone(); let mut next = (*skeleton).clone();
match ability_id {
Some("common.abilities.custom.birdlargefire.firerain") => {
let (movement1base, movement2base, movement3, _twitch) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.5), 0.0, 0.0, 0.0),
Some(StageSection::Action) => {
(1.0, anim_time.min(1.0).powf(0.1), 0.0, anim_time)
},
Some(StageSection::Recover) => (1.0, 1.0, anim_time, 1.0),
_ => (0.0, 0.0, 0.0, 0.0),
};
let pullback = 1.0 - movement3;
let movement1abs = movement1base * pullback;
let _movement2abs = movement2base * pullback;
let wave_slow_cos = (anim_time * 4.5).cos();
next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + movement1abs * 2.5);
next.chest.orientation = Quaternion::rotation_x(movement1abs * 1.0);
next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1);
next.neck.orientation = Quaternion::rotation_x(-0.2);
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1);
next.head.orientation = Quaternion::rotation_x(wave_slow_cos * 0.01);
next.beak.position = Vec3::new(0.0, s_a.beak.0, s_a.beak.1);
next.beak.orientation = Quaternion::rotation_x(wave_slow_cos * -0.02 - 0.02);
next.tail_front.position = Vec3::new(0.0, s_a.tail_front.0, s_a.tail_front.1);
next.tail_front.orientation = Quaternion::rotation_x(0.6);
next.tail_rear.position = Vec3::new(0.0, s_a.tail_rear.0, s_a.tail_rear.1);
next.tail_rear.orientation = Quaternion::rotation_x(-0.2);
if on_ground {
next.wing_in_l.position =
Vec3::new(-s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2);
next.wing_in_r.position =
Vec3::new(s_a.wing_in.0, s_a.wing_in.1, s_a.wing_in.2);
next.wing_in_l.orientation = Quaternion::rotation_y(-0.8 + movement1abs * 1.6)
* Quaternion::rotation_z(0.2 + movement1abs * -0.8);
next.wing_in_r.orientation = Quaternion::rotation_y(0.8 + movement1abs * -1.6)
* Quaternion::rotation_z(-0.2 + movement1abs * 0.8);
next.wing_mid_l.position =
Vec3::new(-s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2);
next.wing_mid_r.position =
Vec3::new(s_a.wing_mid.0, s_a.wing_mid.1, s_a.wing_mid.2);
next.wing_mid_l.orientation =
Quaternion::rotation_y(-0.1) * Quaternion::rotation_z(0.7);
next.wing_mid_r.orientation =
Quaternion::rotation_y(0.1) * Quaternion::rotation_z(-0.7);
next.wing_out_l.position =
Vec3::new(-s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2);
next.wing_out_r.position =
Vec3::new(s_a.wing_out.0, s_a.wing_out.1, s_a.wing_out.2);
next.wing_out_l.orientation =
Quaternion::rotation_y(-0.4) * Quaternion::rotation_z(0.2);
next.wing_out_r.orientation =
Quaternion::rotation_y(0.4) * Quaternion::rotation_z(-0.2);
next.leg_l.position = Vec3::new(-s_a.leg.0, s_a.leg.1, s_a.leg.2);
next.leg_l.orientation = Quaternion::rotation_x(0.0);
next.leg_r.position = Vec3::new(s_a.leg.0, s_a.leg.1, s_a.leg.2);
next.leg_r.orientation = Quaternion::rotation_x(0.0);
next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1, s_a.foot.2);
next.foot_l.orientation = Quaternion::rotation_x(0.0);
next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2);
next.foot_r.orientation = Quaternion::rotation_x(0.0);
}
},
_ => {
let (movement1base, movement3, twitch) = match stage_section { let (movement1base, movement3, twitch) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0), Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0),
Some(StageSection::Recover) => (1.0, anim_time.powf(0.25), anim_time), Some(StageSection::Recover) => (1.0, anim_time.powf(0.25), anim_time),
@ -40,7 +123,6 @@ impl Animation for ShootAnimation {
let movement1mirror = movement1abs * mirror; let movement1mirror = movement1abs * mirror;
let wave_slow_cos = (anim_time * 4.5).cos(); let wave_slow_cos = (anim_time * 4.5).cos();
next.chest.position = Vec3::new( next.chest.position = Vec3::new(
0.0, 0.0,
s_a.chest.0, s_a.chest.0,
@ -85,6 +167,8 @@ impl Animation for ShootAnimation {
* Quaternion::rotation_z(0.2 - movement1abs * 0.8); * Quaternion::rotation_z(0.2 - movement1abs * 0.8);
next.wing_in_r.orientation = Quaternion::rotation_y(1.0 - movement1abs * 0.8) next.wing_in_r.orientation = Quaternion::rotation_y(1.0 - movement1abs * 0.8)
* Quaternion::rotation_z(-0.2 + movement1abs * 0.8); * Quaternion::rotation_z(-0.2 + movement1abs * 0.8);
}
},
}; };
next next
} }

View File

@ -137,7 +137,8 @@ pub fn localize_chat_message(
| BuffKind::Poisoned | BuffKind::Poisoned
| BuffKind::Parried | BuffKind::Parried
| BuffKind::PotionSickness | BuffKind::PotionSickness
| BuffKind::Polymorphed => { | BuffKind::Polymorphed
| BuffKind::Heatstroke => {
tracing::error!("Player was killed by a debuff that doesn't do damage!"); tracing::error!("Player was killed by a debuff that doesn't do damage!");
"mysterious" "mysterious"
}, },

View File

@ -186,6 +186,7 @@ pub enum SfxEvent {
GroundDig, GroundDig,
PortalActivated, PortalActivated,
TeleportedByPortal, TeleportedByPortal,
FromTheAshes,
} }
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
@ -219,6 +220,7 @@ pub enum VoiceKind {
Truffler, Truffler,
Wolf, Wolf,
Wyvern, Wyvern,
Phoenix,
} }
fn body_to_voice(body: &Body) -> Option<VoiceKind> { fn body_to_voice(body: &Body) -> Option<VoiceKind> {
@ -274,6 +276,7 @@ fn body_to_voice(body: &Body) -> Option<VoiceKind> {
| bird_large::Species::FrostWyvern | bird_large::Species::FrostWyvern
| bird_large::Species::SeaWyvern | bird_large::Species::SeaWyvern
| bird_large::Species::WealdWyvern => VoiceKind::Wyvern, | bird_large::Species::WealdWyvern => VoiceKind::Wyvern,
bird_large::Species::Phoenix => VoiceKind::Phoenix,
_ => VoiceKind::Bird, _ => VoiceKind::Bird,
}, },
Body::BipedSmall(body) => match body.species { Body::BipedSmall(body) => match body.species {
@ -492,6 +495,11 @@ impl SfxMgr {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::DeepLaugh); let sfx_trigger_item = triggers.get_key_value(&SfxEvent::DeepLaugh);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater); audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
}, },
Body::Object(object::Body::Tornado)
| Body::Object(object::Body::FieryTornado) => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Swoosh);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
_ => { // not mapped to sfx file _ => { // not mapped to sfx file
}, },
} }
@ -524,6 +532,10 @@ impl SfxMgr {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlameThrower); let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlameThrower);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater); audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
}, },
Outcome::FromTheAshes { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FromTheAshes);
audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
},
Outcome::ProjectileShot { pos, body, .. } => { Outcome::ProjectileShot { pos, body, .. } => {
match body { match body {
Body::Object( Body::Object(
@ -633,7 +645,9 @@ impl SfxMgr {
audio.emit_sfx(sfx_trigger_item, *pos, None, underwater); audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
}; };
}, },
beam::FrontendSpecifier::Flamethrower | beam::FrontendSpecifier::Cultist => { beam::FrontendSpecifier::Flamethrower
| beam::FrontendSpecifier::Cultist
| beam::FrontendSpecifier::PhoenixLaser => {
if thread_rng().gen_bool(0.5) { if thread_rng().gen_bool(0.5) {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlameThrower); let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlameThrower);
audio.emit_sfx(sfx_trigger_item, *pos, None, underwater); audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);

View File

@ -795,6 +795,7 @@ image_ids! {
debuff_parried_0: "voxygen.element.de_buffs.debuff_parried_0", debuff_parried_0: "voxygen.element.de_buffs.debuff_parried_0",
debuff_potionsickness_0: "voxygen.element.de_buffs.debuff_potionsickness_0", debuff_potionsickness_0: "voxygen.element.de_buffs.debuff_potionsickness_0",
debuff_polymorphed: "voxygen.element.de_buffs.debuff_polymorphed", debuff_polymorphed: "voxygen.element.de_buffs.debuff_polymorphed",
debuff_heatstroke_0: "voxygen.element.de_buffs.debuff_heatstroke_0",
// Animation Frames // Animation Frames
// Buff Frame // Buff Frame

View File

@ -5193,6 +5193,7 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
BuffKind::Parried => imgs.debuff_parried_0, BuffKind::Parried => imgs.debuff_parried_0,
BuffKind::PotionSickness => imgs.debuff_potionsickness_0, BuffKind::PotionSickness => imgs.debuff_potionsickness_0,
BuffKind::Polymorphed => imgs.debuff_polymorphed, BuffKind::Polymorphed => imgs.debuff_polymorphed,
BuffKind::Heatstroke => imgs.debuff_heatstroke_0,
} }
} }
@ -5234,6 +5235,7 @@ pub fn get_buff_title(buff: BuffKind, localized_strings: &Localization) -> Cow<s
BuffKind::Parried => localized_strings.get_msg("buff-title-parried"), BuffKind::Parried => localized_strings.get_msg("buff-title-parried"),
BuffKind::PotionSickness => localized_strings.get_msg("buff-title-potionsickness"), BuffKind::PotionSickness => localized_strings.get_msg("buff-title-potionsickness"),
BuffKind::Polymorphed => localized_strings.get_msg("buff-title-polymorphed"), BuffKind::Polymorphed => localized_strings.get_msg("buff-title-polymorphed"),
BuffKind::Heatstroke => localized_strings.get_msg("buff-title-heatstroke"),
} }
} }
@ -5279,6 +5281,7 @@ pub fn get_buff_desc(buff: BuffKind, data: BuffData, localized_strings: &Localiz
BuffKind::Parried => localized_strings.get_msg("buff-desc-parried"), BuffKind::Parried => localized_strings.get_msg("buff-desc-parried"),
BuffKind::PotionSickness => localized_strings.get_msg("buff-desc-potionsickness"), BuffKind::PotionSickness => localized_strings.get_msg("buff-desc-potionsickness"),
BuffKind::Polymorphed => localized_strings.get_msg("buff-desc-polymorphed"), BuffKind::Polymorphed => localized_strings.get_msg("buff-desc-polymorphed"),
BuffKind::Heatstroke => localized_strings.get_msg("buff-desc-heatstroke"),
} }
} }

View File

@ -215,7 +215,8 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec<String> {
| BuffKind::Sunderer | BuffKind::Sunderer
| BuffKind::Defiance | BuffKind::Defiance
| BuffKind::Bloodfeast | BuffKind::Bloodfeast
| BuffKind::Berserk => Cow::Borrowed(""), | BuffKind::Berserk
| BuffKind::Heatstroke => Cow::Borrowed(""),
}; };
write!(&mut description, "{}", buff_desc).unwrap(); write!(&mut description, "{}", buff_desc).unwrap();
@ -263,7 +264,8 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec<String> {
| BuffKind::Sunderer | BuffKind::Sunderer
| BuffKind::Defiance | BuffKind::Defiance
| BuffKind::Bloodfeast | BuffKind::Bloodfeast
| BuffKind::Berserk => Cow::Borrowed(""), | BuffKind::Berserk
| BuffKind::Heatstroke => Cow::Borrowed(""),
} }
} else if let BuffKind::Saturation } else if let BuffKind::Saturation
| BuffKind::Regeneration | BuffKind::Regeneration

View File

@ -99,6 +99,16 @@ pub enum ParticleMode {
PortalFizz = 45, PortalFizz = 45,
Ink = 46, Ink = 46,
Whirlwind = 47, Whirlwind = 47,
FieryBurst = 48,
FieryBurstVortex = 49,
FieryBurstSparks = 50,
FieryBurstAsh = 51,
FieryTornado = 52,
PhoenixCloud = 53,
FieryDropletTrace = 54,
EnergyPhoenix = 55,
PhoenixBeam = 56,
PhoenixBuildUpAim = 57,
} }
impl ParticleMode { impl ParticleMode {

View File

@ -4931,6 +4931,7 @@ impl FigureMgr {
state.state_time, state.state_time,
look_dir, look_dir,
physics.on_ground.is_some(), physics.on_ground.is_some(),
ability_id,
), ),
stage_progress, stage_progress,
&mut state_animation_rate, &mut state_animation_rate,
@ -4993,6 +4994,36 @@ impl FigureMgr {
state.state_time, state.state_time,
look_dir, look_dir,
physics.on_ground.is_some(), physics.on_ground.is_some(),
ability_id,
),
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::RepeaterRanged(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::Recover => {
stage_time / s.static_data.recover_duration.as_secs_f32()
},
_ => 0.0,
};
anim::bird_large::ShootAnimation::update_skeleton(
&target_base,
(
rel_vel,
time,
Some(s.stage_section),
state.state_time,
look_dir,
physics.on_ground.is_some(),
ability_id,
), ),
stage_progress, stage_progress,
&mut state_animation_rate, &mut state_animation_rate,
@ -5021,6 +5052,50 @@ impl FigureMgr {
skeleton_attr, 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::Action => {
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::bird_large::AuraAnimation::update_skeleton(
&target_base,
(Some(s.stage_section), physics.on_ground.is_some()),
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::SelfBuff(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::Action => {
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::bird_large::SelfBuffAnimation::update_skeleton(
&target_base,
(Some(s.stage_section), physics.on_ground.is_some()),
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::BasicSummon(s) => { CharacterState::BasicSummon(s) => {
let stage_time = s.timer.as_secs_f32(); let stage_time = s.timer.as_secs_f32();
let stage_progress = match s.stage_section { let stage_progress = match s.stage_section {

View File

@ -492,6 +492,7 @@ impl Scene {
Rgb::new(1.0, 0.0, 0.0) Rgb::new(1.0, 0.0, 0.0)
} }
}, },
Some(Reagent::Phoenix) => Rgb::new(1.0, 0.8, 0.3),
Some(Reagent::White) => Rgb::new(1.0, 1.0, 1.0), Some(Reagent::White) => Rgb::new(1.0, 1.0, 1.0),
Some(Reagent::Yellow) => Rgb::new(1.0, 1.0, 0.0), Some(Reagent::Yellow) => Rgb::new(1.0, 1.0, 0.0),
None => Rgb::new(1.0, 0.5, 0.0), None => Rgb::new(1.0, 0.5, 0.0),

View File

@ -15,7 +15,7 @@ use common::{
item::Reagent, item::Reagent,
object, object,
shockwave::{self, ShockwaveDodgeable}, shockwave::{self, ShockwaveDodgeable},
Beam, Body, CharacterState, Ori, Pos, Scale, Shockwave, Vel, Beam, Body, CharacterActivity, CharacterState, Ori, Pos, Scale, Shockwave, Vel,
}, },
figure::Segment, figure::Segment,
outcome::Outcome, outcome::Outcome,
@ -170,6 +170,23 @@ impl ParticleMgr {
}, },
); );
}, },
Some(Reagent::Phoenix) => {
self.particles.resize_with(
self.particles.len() + (5.0 * power.abs()) as usize,
|| {
Particle::new_directed(
Duration::from_millis(300),
time,
ParticleMode::Explosion,
*pos,
*pos + Vec3::<f32>::zero()
.map(|_| rng.gen_range(-1.0..1.0))
.normalized()
* *radius,
)
},
);
},
_ => {}, _ => {},
} }
} else { } else {
@ -186,6 +203,7 @@ impl ParticleMgr {
Some(Reagent::Red) => ParticleMode::FireworkRed, Some(Reagent::Red) => ParticleMode::FireworkRed,
Some(Reagent::White) => ParticleMode::FireworkWhite, Some(Reagent::White) => ParticleMode::FireworkWhite,
Some(Reagent::Yellow) => ParticleMode::FireworkYellow, Some(Reagent::Yellow) => ParticleMode::FireworkYellow,
Some(Reagent::Phoenix) => ParticleMode::FireworkYellow,
None => ParticleMode::Shrapnel, None => ParticleMode::Shrapnel,
}, },
*pos, *pos,
@ -431,6 +449,7 @@ impl ParticleMgr {
| Outcome::Steam { .. } | Outcome::Steam { .. }
| Outcome::FireShockwave { .. } | Outcome::FireShockwave { .. }
| Outcome::PortalActivated { .. } | Outcome::PortalActivated { .. }
| Outcome::FromTheAshes { .. }
| Outcome::LaserBeam { .. } => {}, | Outcome::LaserBeam { .. } => {},
} }
} }
@ -501,12 +520,18 @@ impl ParticleMgr {
Body::Object(object::Body::BoltFireBig) => { Body::Object(object::Body::BoltFireBig) => {
self.maintain_boltfirebig_particles(scene_data, interpolated.pos, vel) self.maintain_boltfirebig_particles(scene_data, interpolated.pos, vel)
}, },
Body::Object(object::Body::FireRainDrop) => {
self.maintain_fireraindrop_particles(scene_data, interpolated.pos, vel)
},
Body::Object(object::Body::BoltNature) => { Body::Object(object::Body::BoltNature) => {
self.maintain_boltnature_particles(scene_data, interpolated.pos, vel) self.maintain_boltnature_particles(scene_data, interpolated.pos, vel)
}, },
Body::Object(object::Body::Tornado) => { Body::Object(object::Body::Tornado) => {
self.maintain_tornado_particles(scene_data, interpolated.pos) self.maintain_tornado_particles(scene_data, interpolated.pos)
}, },
Body::Object(object::Body::FieryTornado) => {
self.maintain_fiery_tornado_particles(scene_data, interpolated.pos)
},
Body::Object(object::Body::Mine) => { Body::Object(object::Body::Mine) => {
self.maintain_mine_particles(scene_data, interpolated.pos) self.maintain_mine_particles(scene_data, interpolated.pos)
}, },
@ -672,6 +697,38 @@ impl ParticleMgr {
); );
} }
fn maintain_fireraindrop_particles(
&mut self,
scene_data: &SceneData,
pos: Vec3<f32>,
vel: Option<&Vel>,
) {
span!(
_guard,
"fireraindrop_particles",
"ParticleMgr::maintain_fireraindrop_particles"
);
let time = scene_data.state.get_time();
let dt = scene_data.state.get_delta_time();
let mut rng = thread_rng();
// trace
self.particles.resize_with(
self.particles.len()
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(100))),
|| {
Particle::new(
Duration::from_millis(300),
time,
ParticleMode::FieryDropletTrace,
pos.map(|e| e + rng.gen_range(-0.25..0.25))
+ Vec3::new(0.0, 0.0, 0.5)
+ vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
)
},
);
}
fn maintain_boltnature_particles( fn maintain_boltnature_particles(
&mut self, &mut self,
scene_data: &SceneData, scene_data: &SceneData,
@ -715,6 +772,24 @@ impl ParticleMgr {
); );
} }
fn maintain_fiery_tornado_particles(&mut self, scene_data: &SceneData, pos: Vec3<f32>) {
let time = scene_data.state.get_time();
let mut rng = thread_rng();
// air particles
self.particles.resize_with(
self.particles.len() + usize::from(self.scheduler.heartbeats(Duration::from_millis(5))),
|| {
Particle::new(
Duration::from_millis(1000),
time,
ParticleMode::FieryTornado,
pos.map(|e| e + rng.gen_range(-0.25..0.25)),
)
},
);
}
fn maintain_bomb_particles( fn maintain_bomb_particles(
&mut self, &mut self,
scene_data: &SceneData, scene_data: &SceneData,
@ -830,12 +905,14 @@ impl ParticleMgr {
let dt = scene_data.state.get_delta_time(); let dt = scene_data.state.get_delta_time();
let mut rng = thread_rng(); let mut rng = thread_rng();
for (entity, interpolated, vel, character_state, body) in ( for (entity, interpolated, vel, character_state, body, ori, character_activity) in (
&ecs.entities(), &ecs.entities(),
&ecs.read_storage::<Interpolated>(), &ecs.read_storage::<Interpolated>(),
ecs.read_storage::<Vel>().maybe(), ecs.read_storage::<Vel>().maybe(),
&ecs.read_storage::<CharacterState>(), &ecs.read_storage::<CharacterState>(),
&ecs.read_storage::<Body>(), &ecs.read_storage::<Body>(),
&ecs.read_storage::<Ori>(),
&ecs.read_storage::<CharacterActivity>(),
) )
.join() .join()
{ {
@ -957,6 +1034,87 @@ impl ParticleMgr {
} }
} }
}, },
CharacterState::RepeaterRanged(repeater) => {
if let Some(specifier) = repeater.static_data.specifier {
match specifier {
states::repeater_ranged::FrontendSpecifier::FireRain => {
// base, dark clouds
self.particles.resize_with(
self.particles.len()
+ 2 * usize::from(
self.scheduler.heartbeats(Duration::from_millis(25)),
),
|| {
let rand_pos = {
let theta = rng.gen::<f32>() * TAU;
let radius = repeater
.static_data
.properties_of_aoe
.map(|aoe| aoe.radius)
.unwrap_or_default()
* rng.gen::<f32>().sqrt();
let x = radius * theta.sin();
let y = radius * theta.cos();
Vec2::new(x, y) + interpolated.pos.xy()
};
let pos1 = rand_pos.with_z(
repeater
.static_data
.properties_of_aoe
.map(|aoe| aoe.height)
.unwrap_or_default()
+ interpolated.pos.z
+ 2.0 * rng.gen::<f32>(),
);
Particle::new_directed(
Duration::from_secs_f32(3.0),
time,
ParticleMode::PhoenixCloud,
pos1,
pos1 + Vec3::new(7.09, 4.09, 18.09),
)
},
);
self.particles.resize_with(
self.particles.len()
+ 2 * usize::from(
self.scheduler.heartbeats(Duration::from_millis(25)),
),
|| {
let rand_pos = {
let theta = rng.gen::<f32>() * TAU;
let radius = repeater
.static_data
.properties_of_aoe
.map(|aoe| aoe.radius)
.unwrap_or_default()
* rng.gen::<f32>().sqrt();
let x = radius * theta.sin();
let y = radius * theta.cos();
Vec2::new(x, y) + interpolated.pos.xy()
};
let pos1 = rand_pos.with_z(
repeater
.static_data
.properties_of_aoe
.map(|aoe| aoe.height)
.unwrap_or_default()
+ interpolated.pos.z
+ 1.5 * rng.gen::<f32>(),
);
Particle::new_directed(
Duration::from_secs_f32(2.5),
time,
ParticleMode::PhoenixCloud,
pos1,
pos1 + Vec3::new(10.025, 4.025, 17.025),
)
},
);
},
}
}
},
CharacterState::Blink(c) => { CharacterState::Blink(c) => {
self.particles.resize_with( self.particles.resize_with(
self.particles.len() self.particles.len()
@ -992,6 +1150,94 @@ impl ParticleMgr {
); );
}, },
CharacterState::SelfBuff(c) => { CharacterState::SelfBuff(c) => {
if let Some(specifier) = c.static_data.specifier {
match specifier {
states::self_buff::FrontendSpecifier::FromTheAshes => {
if matches!(c.stage_section, StageSection::Action) {
let pos = interpolated.pos;
self.particles.resize_with(
self.particles.len()
+ 2 * usize::from(
self.scheduler.heartbeats(Duration::from_millis(1)),
),
|| {
let start_pos = pos + Vec3::unit_z() - 1.0;
let end_pos = pos
+ Vec3::new(
4.0 * rng.gen::<f32>() - 1.0,
4.0 * rng.gen::<f32>() - 1.0,
0.0,
)
.normalized()
* 1.5
+ Vec3::unit_z()
+ 5.0 * rng.gen::<f32>();
Particle::new_directed(
Duration::from_secs_f32(0.5),
time,
ParticleMode::FieryBurst,
start_pos,
end_pos,
)
},
);
self.particles.resize_with(
self.particles.len()
+ usize::from(
self.scheduler
.heartbeats(Duration::from_millis(10)),
),
|| {
Particle::new(
Duration::from_millis(650),
time,
ParticleMode::FieryBurstVortex,
pos.map(|e| e + rng.gen_range(-0.25..0.25))
+ Vec3::new(0.0, 0.0, 1.0),
)
},
);
self.particles.resize_with(
self.particles.len()
+ usize::from(
self.scheduler
.heartbeats(Duration::from_millis(40)),
),
|| {
Particle::new(
Duration::from_millis(1000),
time,
ParticleMode::FieryBurstSparks,
pos.map(|e| e + rng.gen_range(-0.25..0.25)),
)
},
);
self.particles.resize_with(
self.particles.len()
+ usize::from(
self.scheduler
.heartbeats(Duration::from_millis(14)),
),
|| {
let pos1 = pos.map(|e| e + rng.gen_range(-0.25..0.25));
Particle::new_directed(
Duration::from_millis(1000),
time,
ParticleMode::FieryBurstAsh,
pos1,
Vec3::new(
4.5, // radius of rand spawn
20.4, // integer part - radius of the curve part, fractional part - relative time of setting particle on fire
8.58) // height of the flight
+ pos1,
)
},
);
}
},
}
}
use buff::BuffKind; use buff::BuffKind;
if let BuffKind::Frenzied = c.static_data.buff_kind { if let BuffKind::Frenzied = c.static_data.buff_kind {
if matches!(c.stage_section, StageSection::Action) { if matches!(c.stage_section, StageSection::Action) {
@ -1022,6 +1268,44 @@ impl ParticleMgr {
} }
} }
}, },
CharacterState::BasicBeam(beam) => {
let ori = *ori;
let _look_dir = *character_activity.look_dir.unwrap_or(ori.look_dir());
let dir = ori.look_dir(); //.with_z(look_dir.z);
let specifier = beam.static_data.specifier;
if specifier == beam::FrontendSpecifier::PhoenixLaser
&& matches!(beam.stage_section, StageSection::Buildup)
{
self.particles.resize_with(
self.particles.len()
+ 2 * usize::from(
self.scheduler.heartbeats(Duration::from_millis(2)),
),
|| {
let mut left_right_alignment =
dir.cross(Vec3::new(0.0, 0.0, 1.0)).normalized();
if rng.gen_bool(0.5) {
left_right_alignment *= -1.0;
}
let start = interpolated.pos
+ left_right_alignment * 4.0
+ dir.normalized() * 6.0;
let lifespan = Duration::from_secs_f32(0.5);
Particle::new_directed(
lifespan,
time,
ParticleMode::PhoenixBuildUpAim,
start,
interpolated.pos
+ dir.normalized() * 3.0
+ left_right_alignment * 0.4
+ vel
.map_or(Vec3::zero(), |v| v.0 * lifespan.as_secs_f32()),
)
},
);
}
},
_ => {}, _ => {},
} }
} }
@ -1285,6 +1569,20 @@ impl ParticleMgr {
}, },
); );
}, },
beam::FrontendSpecifier::PhoenixLaser => {
self.particles.resize_with(
self.particles.len() + usize::from(beam_tick_count) / 2,
|| {
Particle::new_directed(
Duration::from_secs_f64(beam.duration.0),
time,
ParticleMode::PhoenixBeam,
beam.bezier.start,
beam.bezier.start + beam_dir * beam.range,
)
},
);
},
} }
} }
} }
@ -1458,6 +1756,62 @@ impl ParticleMgr {
); );
} }
}, },
aura::AuraKind::Buff {
kind: buff::BuffKind::Heatstroke,
..
} => {
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) / 900,
|| {
let rand_dist = aura.radius * (1.0 - rng.gen::<f32>().powi(100));
let init_pos = Vec3::new(rand_dist, 0_f32, 0_f32);
let duration = Duration::from_secs_f64(
aura.end_time
.map_or(1.0, |end| end.0 - time)
.clamp(0.0, 1.0),
);
Particle::new_directed(
duration,
time,
ParticleMode::EnergyPhoenix,
pos,
pos + init_pos,
)
},
);
let num_particles = aura.radius.powi(2) * dt / 50.0;
let num_particles = num_particles.floor() as usize
+ usize::from(rng.gen_bool(f64::from(num_particles % 1.0)));
self.particles
.resize_with(self.particles.len() + num_particles, || {
let rand_pos = {
let theta = rng.gen::<f32>() * TAU;
let radius = aura.radius * rng.gen::<f32>().sqrt();
let x = radius * theta.sin();
let y = radius * theta.cos();
Vec2::new(x, y) + pos.xy()
};
let duration = Duration::from_secs_f64(
aura.end_time
.map_or(1.0, |end| end.0 - time)
.clamp(0.0, 1.0),
);
Particle::new_directed(
duration,
time,
ParticleMode::FieryBurstAsh,
pos,
Vec3::new(
0.0, // radius of rand spawn
20.0, // integer part - radius of the curve part, fractional part - relative time of setting particle on fire
5.5) // height of the flight
+ rand_pos.with_z(pos.z),
)
});
},
_ => {}, _ => {},
} }
} }