Merge branch 'sam/clay-golem' into 'master'

Clay Golem Rework

See merge request veloren/veloren!2264
This commit is contained in:
Joshua Yanovski 2021-05-11 01:36:13 +00:00
commit 81bba1393a
39 changed files with 1068 additions and 363 deletions

View File

@ -101,6 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Better active/inactive master sound slider logic
- Cultist Husk no longer drops weapons and armor
- Animal Trainers now spawn in tier-5 dungeon and not in tier-3
- Reworked clay golem to have unique attacks.
### Removed

View File

@ -199,6 +199,11 @@
secondary: "common.abilities.custom.turret.arrows",
abilities: [],
),
Custom("Haniwa Sentry"): (
primary: "common.abilities.custom.turret.flamethrower",
secondary: "common.abilities.custom.turret.flamethrower",
abilities: [],
),
Custom("Mindflayer"): (
primary: "common.abilities.custom.mindflayer.cursedflames",
secondary: "common.abilities.custom.mindflayer.necroticvortex",
@ -216,6 +221,14 @@
(None, "common.abilities.custom.minotaur.frenzy"),
],
),
Custom("Clay Golem"): (
primary: "common.abilities.custom.claygolem.strike",
secondary: "common.abilities.custom.claygolem.laser",
abilities: [
(None, "common.abilities.custom.claygolem.shockwave"),
(None, "common.abilities.custom.claygolem.rocket"),
],
),
Custom("Bird Large Breathe"): (
primary: "common.abilities.custom.birdlargebreathe.firebomb",
secondary: "common.abilities.custom.birdlargebreathe.triplestrike",

View File

@ -0,0 +1,14 @@
BasicBeam(
buildup_duration: 0.5,
recover_duration: 0.4,
beam_duration: 0.25,
damage: 100,
tick_rate: 2.0,
range: 40.0,
max_angle: 1.0,
damage_effect: None,
energy_regen: 50,
energy_drain: 0,
orientation_behavior: FromOri,
specifier: ClayGolem,
)

View File

@ -0,0 +1,13 @@
BasicRanged(
energy_cost: 0,
buildup_duration: 0.5,
recover_duration: 0.8,
projectile: ClayRocket(
damage: 500.0,
knockback: 25.0,
radius: 10.0,
),
projectile_body: Object(ClayRocket),
projectile_light: None,
projectile_speed: 30.0,
)

View File

@ -0,0 +1,16 @@
Shockwave(
energy_cost: 0,
buildup_duration: 0.6,
swing_duration: 0.12,
recover_duration: 1.2,
damage: 500,
poise_damage: 50,
knockback: (strength: 40.0, direction: TowardsUp),
shockwave_angle: 180.0,
shockwave_vertical_angle: 90.0,
shockwave_speed: 15.0,
shockwave_duration: 2.5,
requires_ground: true,
move_efficiency: 0.0,
damage_kind: Crushing,
)

View File

@ -0,0 +1,13 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 0.8,
swing_duration: 0.2,
recover_duration: 0.5,
base_damage: 200,
base_poise_damage: 50,
knockback: 10.0,
range: 4.0,
max_angle: 45.0,
damage_effect: None,
damage_kind: Crushing,
)

View File

@ -9,5 +9,5 @@ BasicRanged(
),
projectile_body: Object(ArrowTurret),
projectile_light: None,
projectile_speed: 90.0,
projectile_speed: 130.0,
)

View File

@ -1,14 +1,19 @@
BasicBeam(
buildup_duration: 0.25,
recover_duration: 0.25,
beam_duration: 0.5,
damage: 3000,
beam_duration: 1.0,
damage: 35,
tick_rate: 3.0,
range: 30.0,
range: 20.0,
max_angle: 15.0,
damage_effect: None,
damage_effect: Some(Buff((
kind: Burning,
dur_secs: 10.0,
strength: DamageFraction(0.5),
chance: 0.25,
))),
energy_regen: 0,
energy_drain: 0,
orientation_behavior: Turret,
orientation_behavior: Normal,
specifier: Flamethrower,
)

View File

@ -0,0 +1,13 @@
ItemDef(
name: "Clay Golem Armor",
description: "Worn by clay golem.",
kind: Armor((
kind: Chest("Clay Golem"),
stats: (
protection: Normal(180.0),
poise_resilience: Normal(1.0),
),
)),
quality: Legendary,
tags: [],
)

View File

@ -0,0 +1,19 @@
ItemDef(
name: "Clay Golem Fists",
description: "Yeet.",
kind: Tool((
kind: Natural,
hands: Two,
stats: Direct((
equip_time_secs: 0.001,
power: 1.0,
poise_strength: 1.0,
speed: 1.0,
crit_chance: 0.1,
crit_mult: 1.5,
)),
)),
quality: Low,
tags: [],
ability_spec: Some(Custom("Clay Golem")),
)

View File

@ -0,0 +1,19 @@
ItemDef(
name: "Haniwa Sentry",
description: "Rotating turret weapon",
kind: Tool((
kind: Natural,
hands: Two,
stats: Direct((
equip_time_secs: 0.01,
power: 1.0,
poise_strength: 1.0,
speed: 1.0,
crit_chance: 0.0625,
crit_mult: 1.9142857,
)),
)),
quality: Low,
tags: [],
ability_spec: Some(Custom("Haniwa Sentry")),
)

View File

@ -1,5 +1,5 @@
ItemDef(
name: "Emerald Staff",
name: "Emerald Sceptre",
description: "Its stone is the closest thing from perfection",
kind: Tool((
kind: Sceptre,

View File

@ -55,4 +55,19 @@
("common.items.weapons.sceptre.fork0", 1),
("common.items.consumable.potion_med", 100),
],
"tier-3": [
("common.items.armor.plate.belt", 1),
("common.items.armor.plate.chest", 1),
("common.items.armor.plate.foot", 1),
("common.items.armor.plate.hand", 1),
("common.items.armor.plate.pants", 1),
("common.items.armor.plate.shoulder", 1),
("common.items.weapons.sword.steel-0", 1),
("common.items.weapons.axe.steel_axe-0", 1),
("common.items.weapons.hammer.steel_hammer-0", 1),
("common.items.weapons.bow.metal-0", 1),
("common.items.weapons.staff.flamethrower_0", 1),
("common.items.weapons.sceptre.coralline_cane", 1),
("common.items.consumable.potion_med", 100),
],
})

View File

@ -66,6 +66,7 @@ const int STATIC_SMOKE = 24;
const int BLOOD = 25;
const int ENRAGED = 26;
const int BIG_SHRAPNEL = 27;
const int LASER = 28;
// meters per second squared (acceleration)
const float earth_gravity = 9.807;
@ -168,114 +169,125 @@ void main() {
Attr attr;
f_reflect = 1.0;
if (inst_mode == SMOKE) {
attr = Attr(
linear_motion(
vec3(0),
vec3(rand2 * 0.02, rand3 * 0.02, 1.0 + rand4 * 0.1)
),
vec3(linear_scale(0.5)),
vec4(vec3(0.8, 0.8, 1) * 0.5, start_end(1.0, 0.0)),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 0.5)
);
} else if (inst_mode == FIRE) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0.0),
vec3(rand2 * 0.1, rand3 * 0.1, 2.0 + rand4 * 1.0)
),
vec3(1.0),
vec4(2, 1.5 + rand5 * 0.5, 0, start_end(1.0, 0.0)),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3)
);
} else if (inst_mode == FIRE_BOWL) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(normalize(vec2(rand0, rand1)) * 0.1, 0.6),
vec3(rand2 * 0.2, rand3 * 0.5, 0.8 + rand4 * 0.5)
),
vec3(0.2), // Size
vec4(2, 1.5 + rand5 * 0.5, 0, start_end(1.0, 0.0)), // Colour
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3)
);
} else if (inst_mode == GUN_POWDER_SPARK) {
attr = Attr(
linear_motion(
normalize(vec3(rand0, rand1, rand3)) * 0.3,
normalize(vec3(rand4, rand5, rand6)) * 4.0 + grav_vel(earth_gravity)
),
vec3(1.0),
vec4(3.5, 3 + rand7, 0, 1),
spin_in_axis(vec3(1,0,0),0)
);
} else if (inst_mode == SHRAPNEL) {
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand4, rand5, rand6)) * 20.0 + grav_vel(earth_gravity)
),
vec3(1),
vec4(vec3(0.25), 1),
spin_in_axis(vec3(1,0,0),0)
);
} else if (inst_mode == BIG_SHRAPNEL) {
float brown_color = 0.05 + 0.1 * rand1;
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand4, rand5, rand6)) * 15.0 + grav_vel(earth_gravity)
),
vec3(5 * (1 - percent())),
vec4(vec3(brown_color, brown_color / 2, 0), 1),
spin_in_axis(vec3(1,0,0),0)
);
} else if (inst_mode == FIREWORK_BLUE) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand1, rand2, rand3)) * 40.0 + grav_vel(earth_gravity)
),
vec3(3.0 + rand0),
vec4(vec3(0, 0, 2), 1),
identity()
);
} else if (inst_mode == FIREWORK_GREEN) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand1, rand2, rand3)) * 40.0 + grav_vel(earth_gravity)
),
vec3(3.0 + rand0),
vec4(vec3(0, 2, 0), 1),
identity()
);
} else if (inst_mode == FIREWORK_PURPLE) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand1, rand2, rand3)) * 40.0 + grav_vel(earth_gravity)
),
vec3(3.0 + rand0),
vec4(vec3(2, 0, 2), 1),
identity()
);
} else if (inst_mode == FIREWORK_RED) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand1, rand2, rand3)) * 40.0 + grav_vel(earth_gravity)
),
vec3(3.0 + rand0),
vec4(vec3(2, 0, 0), 1),
identity()
);
} else if (inst_mode == FIREWORK_WHITE) {
switch(inst_mode) {
case SMOKE:
attr = Attr(
linear_motion(
vec3(0),
vec3(rand2 * 0.02, rand3 * 0.02, 1.0 + rand4 * 0.1)
),
vec3(linear_scale(0.5)),
vec4(vec3(0.8, 0.8, 1) * 0.5, start_end(1.0, 0.0)),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 0.5)
);
break;
case FIRE:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0.0),
vec3(rand2 * 0.1, rand3 * 0.1, 2.0 + rand4 * 1.0)
),
vec3(1.0),
vec4(2, 1.5 + rand5 * 0.5, 0, start_end(1.0, 0.0)),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3)
);
break;
case FIRE_BOWL:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(normalize(vec2(rand0, rand1)) * 0.1, 0.6),
vec3(rand2 * 0.2, rand3 * 0.5, 0.8 + rand4 * 0.5)
),
vec3(0.2), // Size
vec4(2, 1.5 + rand5 * 0.5, 0, start_end(1.0, 0.0)), // Colour
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3)
);
break;
case GUN_POWDER_SPARK:
attr = Attr(
linear_motion(
normalize(vec3(rand0, rand1, rand3)) * 0.3,
normalize(vec3(rand4, rand5, rand6)) * 4.0 + grav_vel(earth_gravity)
),
vec3(1.0),
vec4(3.5, 3 + rand7, 0, 1),
spin_in_axis(vec3(1,0,0),0)
);
break;
case SHRAPNEL:
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand4, rand5, rand6)) * 20.0 + grav_vel(earth_gravity)
),
vec3(1),
vec4(vec3(0.25), 1),
spin_in_axis(vec3(1,0,0),0)
);
break;
case BIG_SHRAPNEL:
float brown_color = 0.05 + 0.1 * rand1;
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand4, rand5, rand6)) * 15.0 + grav_vel(earth_gravity)
),
vec3(5 * (1 - percent())),
vec4(vec3(brown_color, brown_color / 2, 0), 1),
spin_in_axis(vec3(1,0,0),0)
);
break;
case FIREWORK_BLUE:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand1, rand2, rand3)) * 40.0 + grav_vel(earth_gravity)
),
vec3(3.0 + rand0),
vec4(vec3(0, 0, 2), 1),
identity()
);
break;
case FIREWORK_GREEN:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand1, rand2, rand3)) * 40.0 + grav_vel(earth_gravity)
),
vec3(3.0 + rand0),
vec4(vec3(0, 2, 0), 1),
identity()
);
break;
case FIREWORK_PURPLE:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand1, rand2, rand3)) * 40.0 + grav_vel(earth_gravity)
),
vec3(3.0 + rand0),
vec4(vec3(2, 0, 2), 1),
identity()
);
break;
case FIREWORK_RED:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand1, rand2, rand3)) * 40.0 + grav_vel(earth_gravity)
),
vec3(3.0 + rand0),
vec4(vec3(2, 0, 0), 1),
identity()
);
break;
case FIREWORK_WHITE:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
@ -286,173 +298,208 @@ void main() {
vec4(vec3(2, 2, 2), 1),
identity()
);
} else if (inst_mode == FIREWORK_YELLOW) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
break;
case FIREWORK_YELLOW:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand1, rand2, rand3)) * 40.0 + grav_vel(earth_gravity)
),
vec3(3.0 + rand0),
vec4(vec3(2, 2, 0), 1),
identity()
);
break;
case LEAF:
attr = Attr(
linear_motion(
vec3(0),
vec3(0, 0, -2)
) + vec3(sin(lifetime), sin(lifetime + 0.7), sin(lifetime * 0.5)) * 2.0,
vec3(4),
vec4(vec3(0.2 + rand7 * 0.2, 0.2 + (0.25 + rand6 * 0.5) * 0.3, 0) * (0.75 + rand1 * 0.5), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
break;
case SNOW:
float height = mix(-4, 60, pow(start_end(1, 0), 3));
float wind_speed = (inst_pos.z - 2000) * 0.025;
vec3 offset = linear_motion(vec3(0), vec3(1, 1, 0) * wind_speed);
float end_alt = alt_at(start_pos.xy + offset.xy);
attr = Attr(
offset + vec3(0, 0, end_alt - start_pos.z + height) + vec3(sin(lifetime), sin(lifetime + 0.7), sin(lifetime * 0.5)) * 3,
vec3(mix(4, 0, pow(start_end(1, 0), 4))),
vec4(1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
break;
case FIREFLY:
float raise = pow(sin(3.1416 * lifetime / inst_lifespan), 0.2);
attr = Attr(
vec3(0, 0, raise * 5.0) + vec3(
sin(lifetime * 1.0 + rand0) + sin(lifetime * 7.0 + rand3) * 0.3,
sin(lifetime * 3.0 + rand1) + sin(lifetime * 8.0 + rand4) * 0.3,
sin(lifetime * 2.0 + rand2) + sin(lifetime * 9.0 + rand5) * 0.3
),
vec3(raise),
vec4(vec3(5, 5, 1.1), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
break;
case BEE:
float lower = pow(sin(3.1416 * lifetime / inst_lifespan), 0.2);
attr = Attr(
vec3(0, 0, lower * -0.5) + vec3(
sin(lifetime * 2.0 + rand0) + sin(lifetime * 9.0 + rand3) * 0.3,
sin(lifetime * 3.0 + rand1) + sin(lifetime * 10.0 + rand4) * 0.3,
sin(lifetime * 4.0 + rand2) + sin(lifetime * 11.0 + rand5) * 0.3
) * 0.5,
vec3(lower),
vec4(vec3(1, 0.7, 0), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
break;
case GROUND_SHOCKWAVE:
attr = Attr(
vec3(0.0),
vec3(11.0, 11.0, (33.0 * rand0 * sin(2.0 * lifetime * 3.14 * 2.0))) / 3,
vec4(vec3(0.32 + (rand0 * 0.04), 0.22 + (rand1 * 0.03), 0.05 + (rand2 * 0.01)), 1),
spin_in_axis(vec3(1,0,0),0)
);
break;
case HEALING_BEAM:
f_reflect = 0.0;
attr = Attr(
spiral_motion(inst_dir, 0.3 * (floor(2 * rand0 + 0.5) - 0.5) * min(linear_scale(10), 1), lifetime / inst_lifespan, 10.0, inst_time),
vec3((1.7 - 0.7 * abs(floor(2 * rand0 - 0.5) + 0.5)) * (1.5 + 0.5 * sin(tick.x * 10 - lifetime * 4))),
vec4(vec3(0.4, 1.6 + 0.3 * sin(tick.x * 10 - lifetime * 3 + 4), 1.0 + 0.15 * sin(tick.x * 5 - lifetime * 5)), 1 /*0.3*/),
spin_in_axis(inst_dir, tick.z)
);
break;
case LIFESTEAL_BEAM:
f_reflect = 0.0;
float green_col = 0.2 + 1.4 * sin(tick.x * 5 + lifetime * 5);
float purple_col = 1.2 + 0.1 * sin(tick.x * 3 - lifetime * 3) - max(green_col, 1) + 1;
attr = Attr(
spiral_motion(inst_dir, 0.3 * (floor(2 * rand0 + 0.5) - 0.5) * min(linear_scale(10), 1), lifetime / inst_lifespan, 10.0, inst_time),
vec3((1.7 - 0.7 * abs(floor(2 * rand0 - 0.5) + 0.5)) * (1.5 + 0.5 * sin(tick.x * 10 - lifetime * 4))),
vec4(vec3(purple_col, green_col, 0.75 * purple_col), 1),
spin_in_axis(inst_dir, tick.z)
);
break;
case ENERGY_NATURE:
f_reflect = 0.0;
float spiral_radius = start_end(1 - pow(abs(rand5), 5), 1) * length(inst_dir);
attr = Attr(
spiral_motion(vec3(0, 0, rand3 + 1), spiral_radius, lifetime, abs(rand0), rand1 * 2 * PI) + vec3(0, 0, rand2),
vec3(6 * abs(rand4) * (1 - slow_start(2)) * pow(spiral_radius / length(inst_dir), 0.5)),
vec4(vec3(0, 1.7, 1.3), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3)
);
break;
case FLAMETHROWER:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
vec3((2.5 * (1 - slow_start(0.2)))),
vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case EXPLOSION:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
inst_dir * ((rand0+1.0)/2 + 0.4) * slow_end(2.0) + 0.3 * grav_vel(earth_gravity),
vec3((3 * (1 - slow_start(0.1)))),
vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case ICE:
f_reflect = 0.0; // Ice doesn't reflect to look like magic
attr = Attr(
inst_dir * ((rand0+1.0)/2 + 0.4) * slow_end(2.0) + 0.3 * grav_vel(earth_gravity),
vec3((3 * (1 - slow_start(0.1)))),
vec4(0.2, 1.6 + rand5 * 0.3 - 0.4 * percent(), 3, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case FIRE_SHOCKWAVE:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
vec3(rand0, rand1, lifetime * 10 + rand2),
vec3((5 * (1 - slow_start(0.5)))),
vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
spin_in_axis(vec3(rand3, rand4, rand5), rand6)
);
break;
case CULTIST_FLAME:
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
float purp_color = 0.9 + 0.3 * rand3;
attr = Attr(
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
vec3((3.5 * (1 - slow_start(0.2)))),
vec4(purp_color, 0.0, purp_color, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case STATIC_SMOKE:
attr = Attr(
vec3(0),
normalize(vec3(rand1, rand2, rand3)) * 40.0 + grav_vel(earth_gravity)
),
vec3(3.0 + rand0),
vec4(vec3(2, 2, 0), 1),
identity()
);
} else if (inst_mode == LEAF) {
attr = Attr(
linear_motion(
vec3(0),
vec3(0, 0, -2)
) + vec3(sin(lifetime), sin(lifetime + 0.7), sin(lifetime * 0.5)) * 2.0,
vec3(4),
vec4(vec3(0.2 + rand7 * 0.2, 0.2 + (0.25 + rand6 * 0.5) * 0.3, 0) * (0.75 + rand1 * 0.5), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
} else if (inst_mode == SNOW) {
float height = mix(-4, 60, pow(start_end(1, 0), 3));
float wind_speed = (inst_pos.z - 2000) * 0.025;
vec3 offset = linear_motion(vec3(0), vec3(1, 1, 0) * wind_speed);
float end_alt = alt_at(start_pos.xy + offset.xy);
attr = Attr(
offset + vec3(0, 0, end_alt - start_pos.z + height) + vec3(sin(lifetime), sin(lifetime + 0.7), sin(lifetime * 0.5)) * 3,
vec3(mix(4, 0, pow(start_end(1, 0), 4))),
vec4(1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
} else if (inst_mode == FIREFLY) {
float raise = pow(sin(3.1416 * lifetime / inst_lifespan), 0.2);
attr = Attr(
vec3(0, 0, raise * 5.0) + vec3(
sin(lifetime * 1.0 + rand0) + sin(lifetime * 7.0 + rand3) * 0.3,
sin(lifetime * 3.0 + rand1) + sin(lifetime * 8.0 + rand4) * 0.3,
sin(lifetime * 2.0 + rand2) + sin(lifetime * 9.0 + rand5) * 0.3
),
vec3(raise),
vec4(vec3(5, 5, 1.1), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
} else if (inst_mode == BEE) {
float lower = pow(sin(3.1416 * lifetime / inst_lifespan), 0.2);
attr = Attr(
vec3(0, 0, lower * -0.5) + vec3(
sin(lifetime * 2.0 + rand0) + sin(lifetime * 9.0 + rand3) * 0.3,
sin(lifetime * 3.0 + rand1) + sin(lifetime * 10.0 + rand4) * 0.3,
sin(lifetime * 4.0 + rand2) + sin(lifetime * 11.0 + rand5) * 0.3
) * 0.5,
vec3(lower),
vec4(vec3(1, 0.7, 0), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
} else if (inst_mode == GROUND_SHOCKWAVE) {
attr = Attr(
vec3(0.0),
vec3(11.0, 11.0, (33.0 * rand0 * sin(2.0 * lifetime * 3.14 * 2.0))) / 3,
vec4(vec3(0.32 + (rand0 * 0.04), 0.22 + (rand1 * 0.03), 0.05 + (rand2 * 0.01)), 1),
spin_in_axis(vec3(1,0,0),0)
);
} else if (inst_mode == HEALING_BEAM) {
f_reflect = 0.0;
attr = Attr(
spiral_motion(inst_dir, 0.3 * (floor(2 * rand0 + 0.5) - 0.5) * min(linear_scale(10), 1), lifetime / inst_lifespan, 10.0, inst_time),
vec3((1.7 - 0.7 * abs(floor(2 * rand0 - 0.5) + 0.5)) * (1.5 + 0.5 * sin(tick.x * 10 - lifetime * 4))),
vec4(vec3(0.4, 1.6 + 0.3 * sin(tick.x * 10 - lifetime * 3 + 4), 1.0 + 0.15 * sin(tick.x * 5 - lifetime * 5)), 1 /*0.3*/),
spin_in_axis(inst_dir, tick.z)
);
} else if (inst_mode == LIFESTEAL_BEAM) {
f_reflect = 0.0;
float green_col = 0.2 + 1.4 * sin(tick.x * 5 + lifetime * 5);
float purple_col = 1.2 + 0.1 * sin(tick.x * 3 - lifetime * 3) - max(green_col, 1) + 1;
attr = Attr(
spiral_motion(inst_dir, 0.3 * (floor(2 * rand0 + 0.5) - 0.5) * min(linear_scale(10), 1), lifetime / inst_lifespan, 10.0, inst_time),
vec3((1.7 - 0.7 * abs(floor(2 * rand0 - 0.5) + 0.5)) * (1.5 + 0.5 * sin(tick.x * 10 - lifetime * 4))),
vec4(vec3(purple_col, green_col, 0.75 * purple_col), 1),
spin_in_axis(inst_dir, tick.z)
);
} else if (inst_mode == ENERGY_NATURE) {
f_reflect = 0.0;
float spiral_radius = start_end(1 - pow(abs(rand5), 5), 1) * length(inst_dir);
attr = Attr(
spiral_motion(vec3(0, 0, rand3 + 1), spiral_radius, lifetime, abs(rand0), rand1 * 2 * PI) + vec3(0, 0, rand2),
vec3(6 * abs(rand4) * (1 - slow_start(2)) * pow(spiral_radius / length(inst_dir), 0.5)),
vec4(vec3(0, 1.7, 1.3), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3)
);
} else if (inst_mode == FLAMETHROWER) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
vec3((2.5 * (1 - slow_start(0.2)))),
vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
} else if (inst_mode == EXPLOSION) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
inst_dir * ((rand0+1.0)/2 + 0.4) * slow_end(2.0) + 0.3 * grav_vel(earth_gravity),
vec3((3 * (1 - slow_start(0.1)))),
vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
} else if (inst_mode == ICE) {
f_reflect = 0.0; // Ice doesn't reflect to look like magic
attr = Attr(
inst_dir * ((rand0+1.0)/2 + 0.4) * slow_end(2.0) + 0.3 * grav_vel(earth_gravity),
vec3((3 * (1 - slow_start(0.1)))),
vec4(0.2, 1.6 + rand5 * 0.3 - 0.4 * percent(), 3, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
} else if (inst_mode == FIRE_SHOCKWAVE) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
vec3(rand0, rand1, lifetime * 10 + rand2),
vec3((5 * (1 - slow_start(0.5)))),
vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
spin_in_axis(vec3(rand3, rand4, rand5), rand6)
);
} else if (inst_mode == CULTIST_FLAME) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
float purp_color = 0.9 + 0.3 * rand3;
attr = Attr(
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
vec3((3.5 * (1 - slow_start(0.2)))),
vec4(purp_color, 0.0, purp_color, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
} else if (inst_mode == STATIC_SMOKE) {
attr = Attr(
vec3(0),
vec3((0.5 * (1 - slow_start(0.8)))),
vec4(1.0),
spin_in_axis(vec3(rand6, rand7, rand8), rand9)
);
} else if (inst_mode == BLOOD) {
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand4, rand5, rand6)) * 5.0 + grav_vel(earth_gravity)
),
vec3((2.0 * (1 - slow_start(0.8)))),
vec4(1, 0, 0, 1),
spin_in_axis(vec3(1,0,0),0)
);
} else if (inst_mode == ENRAGED) {
f_reflect = 0.0;
float red_color = 1.2 + 0.3 * rand3;
attr = Attr(
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
vec3((3.5 * (1 - slow_start(0.2)))),
vec4(red_color, 0.0, 0.0, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
} else {
attr = Attr(
linear_motion(
vec3(rand0 * 0.25, rand1 * 0.25, 1.7 + rand5),
vec3(rand2 * 0.1, rand3 * 0.1, 1.0 + rand4 * 0.5)
),
vec3(exp_scale(-0.2)),
vec4(1),
spin_in_axis(vec3(1,0,0),0)
);
vec3((0.5 * (1 - slow_start(0.8)))),
vec4(1.0),
spin_in_axis(vec3(rand6, rand7, rand8), rand9)
);
break;
case BLOOD:
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand4, rand5, rand6)) * 5.0 + grav_vel(earth_gravity)
),
vec3((2.0 * (1 - slow_start(0.8)))),
vec4(1, 0, 0, 1),
spin_in_axis(vec3(1,0,0),0)
);
break;
case ENRAGED:
f_reflect = 0.0;
float red_color = 1.2 + 0.3 * rand3;
attr = Attr(
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
vec3((3.5 * (1 - slow_start(0.2)))),
vec4(red_color, 0.0, 0.0, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
break;
case LASER:
f_reflect = 0.0;
vec3 perp_axis = normalize(cross(inst_dir, vec3(0.0, 0.0, 1.0)));
offset = vec3(0.0);
if (rand0 > 0.0) {
offset = perp_axis * 0.5;
} else {
offset = perp_axis * -0.5;
}
attr = Attr(
inst_dir * percent() + offset,
vec3(1.0, 1.0, 50.0),
vec4(vec3(2.0, 0.0, 0.0), 1),
spin_in_axis(perp_axis, asin(inst_dir.z / length(inst_dir)) + PI / 2.0)
);
break;
default:
attr = Attr(
linear_motion(
vec3(rand0 * 0.25, rand1 * 0.25, 1.7 + rand5),
vec3(rand2 * 0.1, rand3 * 0.1, 1.0 + rand4 * 0.5)
),
vec3(exp_scale(-0.2)),
vec4(1),
spin_in_axis(vec3(1,0,0),0)
);
break;
}
// Temporary: use shrinking particles as a substitute for fading ones

View File

@ -73,7 +73,7 @@
),
(ClayGolem, Male): (
head: (
offset: (-10.5, -6.0, -5.0),
offset: (-10.5, -3.0, -3.0),
central: ("npc.claygolem.male.head"),
),
jaw: (
@ -91,7 +91,7 @@
),
(ClayGolem, Female): (
head: (
offset: (-10.5, -6.0, -5.0),
offset: (-10.5, -3.0, -3.0),
central: ("npc.claygolem.male.head"),
),
jaw: (

BIN
assets/voxygen/voxel/object/haniwa_sentry/bone0.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/object/haniwa_sentry/bone1.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -655,7 +655,7 @@
central: ("object.crossbow.bone0"),
),
bone1: (
offset: (-9.0, -7.0, -5.0 ),
offset: (-9.0, -7.0, -5.0),
central: ("object.crossbow.bone1"),
)
),
@ -689,4 +689,24 @@
central: ("armor.empty"),
)
),
ClayRocket: (
bone0: (
offset: (-0.5, -6.0, -1.5),
central: ("weapon.projectile.clay-missile"),
),
bone1: (
offset: (0.0, 0.0, 0.0),
central: ("armor.empty"),
)
),
HaniwaSentry: (
bone0: (
offset: (-5.5, -4.5, -5.5),
central: ("object.haniwa_sentry.bone0"),
),
bone1: (
offset: (-5.5, -5.5, -3.0),
central: ("object.haniwa_sentry.bone1"),
)
),
})

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

Binary file not shown.

View File

@ -243,7 +243,7 @@ pub enum CharacterAbility {
damage_effect: Option<CombatEffect>,
energy_regen: f32,
energy_drain: f32,
orientation_behavior: basic_beam::MovementBehavior,
orientation_behavior: basic_beam::OrientationBehavior,
specifier: beam::FrontendSpecifier,
},
BasicAura {

View File

@ -52,4 +52,5 @@ pub enum FrontendSpecifier {
LifestealBeam,
HealingBeam,
Cultist,
ClayGolem,
}

View File

@ -288,7 +288,7 @@ impl Body {
Body::Dragon(_) => Vec3::new(16.0, 10.0, 16.0),
Body::FishMedium(_) => Vec3::new(0.5, 2.0, 0.8),
Body::FishSmall(_) => Vec3::new(0.3, 1.2, 0.6),
Body::Golem(_) => Vec3::new(5.0, 5.0, 5.0),
Body::Golem(_) => Vec3::new(5.0, 5.0, 7.5),
Body::Humanoid(humanoid) => {
let height = match (humanoid.species, humanoid.body_type) {
(humanoid::Species::Orc, humanoid::BodyType::Male) => 2.3,
@ -464,9 +464,13 @@ impl Body {
Body::Object(object) => match object {
object::Body::TrainingDummy => 10000,
object::Body::Crossbow => 800,
object::Body::HaniwaSentry => 600,
_ => 10000,
},
Body::Golem(golem) => match golem.species {
golem::Species::ClayGolem => 7500,
_ => 10000,
},
Body::Golem(_) => 2740,
Body::Theropod(theropod) => match theropod.species {
theropod::Species::Archaeos => 3500,
theropod::Species::Odonto => 3000,
@ -565,7 +569,7 @@ impl Body {
},
Body::BipedSmall(_) => 10,
Body::Object(_) => 10,
Body::Golem(_) => 260,
Body::Golem(_) => 0,
Body::Theropod(_) => 20,
Body::QuadrupedLow(quadruped_low) => match quadruped_low.species {
quadruped_low::Species::Crocodile => 20,
@ -599,6 +603,12 @@ impl Body {
pub fn immune_to(&self, buff: BuffKind) -> bool {
match buff {
BuffKind::Bleeding => matches!(self, Body::Object(_) | Body::Golem(_) | Body::Ship(_)),
BuffKind::Burning => match self {
Body::Golem(g) => matches!(g.species, golem::Species::ClayGolem),
Body::BipedSmall(b) => matches!(b.species, biped_small::Species::Haniwa),
Body::Object(object::Body::HaniwaSentry) => true,
_ => false,
},
_ => false,
}
}
@ -614,6 +624,10 @@ impl Body {
biped_large::Species::Minotaur => 3.2,
_ => 1.0,
},
Body::Golem(g) => match g.species {
golem::Species::ClayGolem => 1.2,
_ => 1.0,
},
_ => 1.0,
}
}

View File

@ -81,6 +81,8 @@ make_case_elim!(
Coins = 66,
GoldOre = 67,
SilverOre = 68,
ClayRocket = 69,
HaniwaSentry = 70,
}
);
@ -91,7 +93,7 @@ impl Body {
}
}
pub const ALL_OBJECTS: [Body; 69] = [
pub const ALL_OBJECTS: [Body; 71] = [
Body::Arrow,
Body::Bomb,
Body::Scarecrow,
@ -161,6 +163,8 @@ pub const ALL_OBJECTS: [Body; 69] = [
Body::Coins,
Body::SilverOre,
Body::GoldOre,
Body::ClayRocket,
Body::HaniwaSentry,
];
impl From<Body> for super::Body {
@ -239,6 +243,8 @@ impl Body {
Body::Coins => "coins",
Body::SilverOre => "silver_ore",
Body::GoldOre => "gold_ore",
Body::ClayRocket => "clay_rocket",
Body::HaniwaSentry => "haniwa_sentry",
}
}
@ -328,6 +334,8 @@ impl Body {
Body::WindowSpooky => 10.0,
Body::SilverOre => 1000.0,
Body::GoldOre => 1000.0,
Body::ClayRocket => 50.0,
Body::HaniwaSentry => 300.0,
};
Mass(m)
@ -335,8 +343,12 @@ impl Body {
pub fn dimensions(&self) -> Vec3<f32> {
match self {
Body::Arrow | Body::ArrowSnake | Body::MultiArrow => Vec3::new(0.01, 0.8, 0.01),
Body::Arrow | Body::ArrowSnake | Body::MultiArrow | Body::ArrowTurret => {
Vec3::new(0.01, 0.8, 0.01)
},
Body::BoltFire => Vec3::new(0.1, 0.1, 0.1),
Body::Crossbow => Vec3::new(3.0, 3.0, 1.5),
Body::HaniwaSentry => Vec3::new(0.8, 0.8, 1.4),
_ => Vec3::broadcast(0.2),
}
}

View File

@ -111,7 +111,7 @@ impl LoadoutBuilder {
},
golem::Species::ClayGolem => {
main_tool = Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.stone_golems_fist",
"common.items.npc_weapons.unique.clay_golem_fist",
));
},
_ => {},
@ -310,10 +310,18 @@ impl LoadoutBuilder {
));
},
},
Body::Object(object::Body::Crossbow) => {
main_tool = Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.turret",
));
Body::Object(body) => match body {
object::Body::Crossbow => {
main_tool = Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.turret",
));
},
object::Body::HaniwaSentry => {
main_tool = Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.haniwa_sentry",
));
},
_ => {},
},
Body::BipedSmall(biped_small) => match (biped_small.species, biped_small.body_type)
{
@ -980,6 +988,15 @@ impl LoadoutBuilder {
.build(),
_ => LoadoutBuilder::new().active_item(active_item).build(),
},
Body::Golem(g) => match g.species {
golem::Species::ClayGolem => LoadoutBuilder::new()
.active_item(active_item)
.chest(Some(Item::new_from_asset_expect(
"common.items.npc_armor.golem.claygolem",
)))
.build(),
_ => LoadoutBuilder::new().active_item(active_item).build(),
},
_ => LoadoutBuilder::new().active_item(active_item).build(),
}
};

View File

@ -239,6 +239,16 @@ impl From<Dir> for Ori {
}
}
impl From<Vec3<f32>> for Ori {
fn from(dir: Vec3<f32>) -> Self {
let dir = Dir::from_unnormalized(dir).unwrap_or_default();
let from = Dir::default();
let q = Quaternion::<f32>::rotation_from_to_3d(*from, *dir).normalized();
Self(q).uprighted()
}
}
impl From<Quaternion<f32>> for Ori {
fn from(quat: Quaternion<f32>) -> Self { Self::new(quat) }
}

View File

@ -59,6 +59,11 @@ pub enum ProjectileConstructor {
radius: f32,
},
Possess,
ClayRocket {
damage: f32,
radius: f32,
knockback: f32,
},
}
impl ProjectileConstructor {
@ -205,6 +210,47 @@ impl ProjectileConstructor {
owner,
ignore_group: false,
},
ClayRocket {
damage,
radius,
knockback,
} => {
let knockback = AttackEffect::new(
Some(GroupTarget::OutOfGroup),
CombatEffect::Knockback(Knockback {
strength: knockback,
direction: KnockbackDir::Away,
}),
)
.with_requirement(CombatRequirement::AnyDamage);
let damage = AttackDamage::new(
Damage {
source: DamageSource::Explosion,
kind: DamageKind::Energy,
value: damage,
},
Some(GroupTarget::OutOfGroup),
);
let attack = Attack::default()
.with_damage(damage)
.with_crit(crit_chance, crit_mult)
.with_effect(knockback);
let explosion = Explosion {
effects: vec![
RadiusEffect::Attack(attack),
RadiusEffect::TerrainDestruction(5.0),
],
radius,
reagent: Some(Reagent::Red),
};
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,
}
},
}
}
@ -246,6 +292,14 @@ impl ProjectileConstructor {
*radius *= range;
},
Possess => {},
ClayRocket {
ref mut damage,
ref mut radius,
..
} => {
*damage *= power;
*radius *= range;
},
}
self
}

View File

@ -39,7 +39,7 @@ pub struct StaticData {
/// Energy drained per second
pub energy_drain: f32,
/// Used to dictate how orientation functions in this state
pub orientation_behavior: MovementBehavior,
pub orientation_behavior: OrientationBehavior,
/// What key is used to press ability
pub ability_info: AbilityInfo,
/// Used to specify the beam to the frontend
@ -61,14 +61,16 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data);
match self.static_data.orientation_behavior {
MovementBehavior::Normal => {},
MovementBehavior::Turret => {
let ori_rate = match self.static_data.orientation_behavior {
OrientationBehavior::Normal => 0.6,
OrientationBehavior::Turret => {
update.ori = Ori::from(data.inputs.look_dir);
0.6
},
}
OrientationBehavior::FromOri => 0.1,
};
handle_orientation(data, &mut update, 0.6);
handle_orientation(data, &mut update, ori_rate);
handle_move(data, &mut update, 0.4);
handle_jump(data, &mut update, 1.0);
@ -138,22 +140,41 @@ impl CharacterBehavior for Data {
owner: Some(*data.uid),
specifier: self.static_data.specifier,
};
// Gets offsets
let body_offsets_r = data.body.radius() + 1.0;
let body_offsets_z = match data.body {
Body::BirdLarge(_) => data.body.height() * 0.9,
Body::BirdLarge(_) => data.body.height() * 0.8,
Body::Golem(_) => data.body.height() * 0.9 + data.inputs.look_dir.z * 3.0,
_ => data.body.height() * 0.5,
};
// Gets offsets
let body_offsets = Vec3::new(
(data.body.radius() + 1.0) * data.inputs.look_dir.x,
(data.body.radius() + 1.0) * data.inputs.look_dir.y,
body_offsets_z,
);
let (body_offsets, ori) = match self.static_data.orientation_behavior {
OrientationBehavior::Normal | OrientationBehavior::Turret => (
Vec3::new(
body_offsets_r * data.inputs.look_dir.x,
body_offsets_r * data.inputs.look_dir.y,
body_offsets_z,
),
Ori::from(data.inputs.look_dir),
),
OrientationBehavior::FromOri => (
Vec3::new(
body_offsets_r * update.ori.look_vec().x,
body_offsets_r * update.ori.look_vec().y,
body_offsets_z,
),
Ori::from(Vec3::new(
update.ori.look_vec().x,
update.ori.look_vec().y,
data.inputs.look_dir.z,
)),
),
};
let pos = Pos(data.pos.0 + body_offsets);
// Create beam segment
update.server_events.push_front(ServerEvent::BeamSegment {
properties,
pos,
ori: Ori::from(data.inputs.look_dir),
ori,
});
update.character = CharacterState::BasicBeam(Data {
timer: self
@ -210,7 +231,13 @@ impl CharacterBehavior for Data {
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum MovementBehavior {
pub enum OrientationBehavior {
/// Uses look_dir as direction of beam
Normal,
/// Uses look_dir as direction of beam, sets orientation to same direction
/// as look_dir
Turret,
/// Uses orientation x and y and look_dir z as direction of beam (z from
/// look_dir as orientation will only go through 2d rotations naturally)
FromOri,
}

View File

@ -187,11 +187,15 @@ pub fn handle_shoot(
.write_resource::<Vec<Outcome>>()
.push(Outcome::ProjectileShot { pos, body, vel });
let eye_height = state
.ecs()
.read_storage::<comp::Body>()
.get(entity)
.map_or(0.0, |b| b.eye_height());
let eye_height =
state
.ecs()
.read_storage::<comp::Body>()
.get(entity)
.map_or(0.0, |b| match b {
comp::Body::Golem(_) => b.height() * 0.45,
_ => b.eye_height(),
});
pos.z += eye_height;

View File

@ -108,6 +108,7 @@ pub enum Tactic {
BirdLargeBreathe,
BirdLargeFire,
Minotaur,
ClayGolem,
}
#[derive(SystemData)]
@ -1577,10 +1578,12 @@ impl<'a> AgentData<'a> {
circle_time: 1,
},
"Turret" => Tactic::Turret,
"Haniwa Sentry" => Tactic::RotatingTurret,
"Bird Large Breathe" => Tactic::BirdLargeBreathe,
"Bird Large Fire" => Tactic::BirdLargeFire,
"Mindflayer" => Tactic::Mindflayer,
"Minotaur" => Tactic::Minotaur,
"Clay Golem" => Tactic::ClayGolem,
_ => Tactic::Melee,
},
AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind),
@ -1645,6 +1648,18 @@ impl<'a> AgentData<'a> {
),
)
}
Tactic::ClayGolem if matches!(self.char_state, CharacterState::BasicRanged(_)) => {
const ROCKET_SPEED: f32 = 30.0;
aim_projectile(
ROCKET_SPEED,
Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
Vec3::new(
tgt_data.pos.0.x,
tgt_data.pos.0.y,
tgt_data.pos.0.z + tgt_eye_offset,
),
)
},
_ => Dir::from_unnormalized(
Vec3::new(
tgt_data.pos.0.x,
@ -1792,6 +1807,13 @@ impl<'a> AgentData<'a> {
Tactic::Minotaur => {
self.handle_minotaur_attack(agent, controller, &attack_data, &tgt_data, &read_data)
},
Tactic::ClayGolem => self.handle_clay_golem_attack(
agent,
controller,
&attack_data,
&tgt_data,
&read_data,
),
}
}
@ -2976,8 +2998,7 @@ impl<'a> AgentData<'a> {
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
) && attack_data.angle < 15.0
{
) {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
@ -3376,7 +3397,7 @@ impl<'a> AgentData<'a> {
let minotaur_attack_distance =
self.body.map_or(0.0, |b| b.radius()) + MINOTAUR_ATTACK_RANGE;
let health_fraction = self.health.map_or(1.0, |h| h.fraction());
// Sets action float at start of combat
// Sets action counter at start of combat
if agent.action_state.counter < MINOTAUR_FRENZY_THRESHOLD
&& health_fraction > MINOTAUR_FRENZY_THRESHOLD
{
@ -3441,6 +3462,101 @@ impl<'a> AgentData<'a> {
}
}
fn handle_clay_golem_attack(
&self,
agent: &mut Agent,
controller: &mut Controller,
attack_data: &AttackData,
tgt_data: &TargetData,
read_data: &ReadData,
) {
const GOLEM_MELEE_RANGE: f32 = 4.0;
const GOLEM_LASER_RANGE: f32 = 30.0;
const GOLEM_LONG_RANGE: f32 = 50.0;
const GOLEM_TARGET_SPEED: f32 = 8.0;
let golem_melee_range = self.body.map_or(0.0, |b| b.radius()) + GOLEM_MELEE_RANGE;
// Magnitude squared of cross product of target velocity with golem orientation
let target_speed_cross_sqd = agent
.target
.as_ref()
.map(|t| t.target)
.and_then(|e| read_data.velocities.get(e))
.map_or(0.0, |v| v.0.cross(self.ori.look_vec()).magnitude_squared());
if attack_data.dist_sqrd < golem_melee_range.powi(2) {
if agent.action_state.counter < 7.5 {
// If target is close, whack them
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.counter += read_data.dt.0;
} else {
// If whacked for too long, nuke them
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(1)));
if matches!(self.char_state, CharacterState::BasicRanged(c) if matches!(c.stage_section, StageSection::Recover))
{
agent.action_state.counter = 0.0;
}
}
} else if attack_data.dist_sqrd < GOLEM_LASER_RANGE.powi(2) {
if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs(10))
|| target_speed_cross_sqd < GOLEM_TARGET_SPEED.powi(2)
&& can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
)
&& attack_data.angle < 45.0
{
// If target in range threshold and haven't been lasering for more than 10
// seconds already or if target is moving slow-ish, laser them
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
} else {
// Else target moving too fast for laser, shockwave time
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
}
} else if attack_data.dist_sqrd < GOLEM_LONG_RANGE.powi(2) {
if target_speed_cross_sqd < GOLEM_TARGET_SPEED.powi(2)
&& can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
)
{
// If target is far-ish and moving slow-ish, rocket them
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(1)));
} else {
// Else target moving too fast for laser, shockwave time
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
}
}
// Make clay golem move towards target
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
}
}
fn follow(
&self,
agent: &mut Agent,

View File

@ -0,0 +1,74 @@
use super::{
super::{vek::*, Animation},
GolemSkeleton, SkeletonAttr,
};
use common::{states::utils::StageSection, util::Dir};
pub struct BeamAnimation;
impl Animation for BeamAnimation {
type Dependency<'a> = (Option<StageSection>, f32, f32, Dir);
type Skeleton = GolemSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"golem_beam\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "golem_beam")]
fn update_skeleton_inner<'a>(
skeleton: &Self::Skeleton,
(stage_section, _global_time, _timer, look_dir): Self::Dependency<'a>,
anim_time: f32,
_rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let (move1base, move1iso, move2base, move3) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.25), anim_time.powf(0.25), 0.0, 0.0),
Some(StageSection::Cast) => (1.0, 0.0, 1.0, 0.0),
Some(StageSection::Recover) => (1.0, 0.0, 1.0, anim_time.powf(4.0)),
_ => (0.0, 0.0, 0.0, 0.0),
};
let pullback = 1.0 - move3;
let move1 = move1base * pullback;
let move2 = move2base * pullback;
next.head.orientation = Quaternion::rotation_x(move1iso * 0.5 + move2 * (look_dir.z * 1.0));
next.head.position = Vec3::new(
0.0,
s_a.head.0,
s_a.head.1 - move2 * 5.0 * (look_dir.z * 1.0).min(0.0),
);
next.upper_torso.orientation =
Quaternion::rotation_x(move1iso * 0.3) * Quaternion::rotation_z(0.0);
next.lower_torso.orientation =
Quaternion::rotation_z(0.0) * Quaternion::rotation_x(move1iso * -0.3);
next.shoulder_l.orientation =
Quaternion::rotation_x(move1 * 0.8) * Quaternion::rotation_y(move1 * -0.5);
next.shoulder_r.orientation =
Quaternion::rotation_x(move1 * 0.8) * Quaternion::rotation_y(move1 * 0.5);
next.shoulder_l.position = Vec3::new(
-s_a.shoulder.0,
s_a.shoulder.1 + move1 * 2.0,
s_a.shoulder.2 + move1 * -2.0,
);
next.shoulder_r.position = Vec3::new(
s_a.shoulder.0,
s_a.shoulder.1 + move1 * 2.0,
s_a.shoulder.2 + move1 * -2.0,
);
next.hand_l.orientation =
Quaternion::rotation_z(0.0) * Quaternion::rotation_y(move1 * -1.1);
next.hand_r.orientation = Quaternion::rotation_y(0.0) * Quaternion::rotation_y(move1 * 1.1);
next.torso.position = Vec3::new(0.0, 0.0, 0.0);
next
}
}

View File

@ -1,13 +1,15 @@
pub mod alpha;
pub mod beam;
pub mod idle;
pub mod run;
pub mod shockwave;
pub mod shoot;
pub mod spinmelee;
// Reexports
pub use self::{
alpha::AlphaAnimation, idle::IdleAnimation, run::RunAnimation, shockwave::ShockwaveAnimation,
spinmelee::SpinMeleeAnimation,
alpha::AlphaAnimation, beam::BeamAnimation, idle::IdleAnimation, run::RunAnimation,
shockwave::ShockwaveAnimation, shoot::ShootAnimation, spinmelee::SpinMeleeAnimation,
};
use super::{make_bone, vek::*, FigureBoneData, Skeleton};
@ -120,7 +122,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
head: match (body.species, body.body_type) {
(StoneGolem, _) => (0.0, 2.0),
(Treant, _) => (18.0, -8.0),
(ClayGolem, _) => (2.0, 9.0),
(ClayGolem, _) => (-2.0, 7.0),
},
jaw: match (body.species, body.body_type) {
(StoneGolem, _) => (0.0, 0.0),

View File

@ -0,0 +1,61 @@
use super::{
super::{vek::*, Animation},
GolemSkeleton, SkeletonAttr,
};
use common::{states::utils::StageSection, util::Dir};
pub struct ShootAnimation;
impl Animation for ShootAnimation {
type Dependency<'a> = (Option<StageSection>, f32, f32, Dir);
type Skeleton = GolemSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"golem_shoot\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "golem_shoot")]
fn update_skeleton_inner<'a>(
skeleton: &Self::Skeleton,
(stage_section, _global_time, _timer, look_dir): Self::Dependency<'a>,
anim_time: f32,
_rate: &mut f32,
_s_a: &SkeletonAttr,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let (move1base, move2base, move3) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.4), 0.0, 0.0),
Some(StageSection::Shoot) => (1.0, anim_time, 0.0),
Some(StageSection::Recover) => (1.0, 1.0, anim_time.powf(4.0)),
_ => (0.0, 0.0, 0.0),
};
let pullback = 1.0 - move3;
let move1 = move1base * pullback;
let move2 = move2base * pullback;
next.head.orientation = Quaternion::rotation_x(-0.2) * Quaternion::rotation_z(move1 * -0.5);
next.upper_torso.orientation =
Quaternion::rotation_x(0.0) * Quaternion::rotation_z(move1 * 0.5);
next.lower_torso.orientation =
Quaternion::rotation_z(move1 * -0.5) * Quaternion::rotation_x(0.0);
next.shoulder_l.orientation =
Quaternion::rotation_y(0.0) * Quaternion::rotation_z(move1 * 0.7);
next.shoulder_r.orientation = Quaternion::rotation_x(move1 * (look_dir.z * 1.2 + 1.57))
* Quaternion::rotation_y(move1 * 0.0);
next.hand_l.orientation =
Quaternion::rotation_z(move1 * -0.3) * Quaternion::rotation_x(move1 * 1.3);
next.hand_r.orientation = Quaternion::rotation_y(move1 * -0.3)
* Quaternion::rotation_z(move1 * -0.9 + move2 * -1.6);
next.torso.position = Vec3::new(0.0, 0.0, 0.0);
next
}
}

View File

@ -70,11 +70,13 @@ impl<'a> From<&'a Body> for SkeletonAttr {
use comp::object::Body::*;
Self {
bone0: match body {
Crossbow => (0.0, 0.0, 14.0),
Crossbow => (0.0, 0.0, 11.0),
HaniwaSentry => (0.0, 0.0, 10.5),
_ => (0.0, 0.0, 0.0),
},
bone1: match body {
Crossbow => (0.0, 0.0, 8.0),
HaniwaSentry => (0.0, 0.0, 3.0),
_ => (0.0, 0.0, 0.0),
},
}

View File

@ -402,6 +402,7 @@ impl SfxMgr {
audio.emit_sfx(sfx_trigger_item, *pos, None, false);
}
},
beam::FrontendSpecifier::ClayGolem => {},
},
Outcome::BreakBlock { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::BreakBlock);

View File

@ -91,7 +91,7 @@ impl<'a> System<'a> for Sys {
fn base_ori_interp(body: &Body) -> f32 {
match body {
Body::Object(object) => match object {
object::Body::Crossbow => 100.0,
object::Body::Crossbow | object::Body::HaniwaSentry => 100.0,
_ => 10.0,
},
_ => 10.0,

View File

@ -124,6 +124,7 @@ pub enum ParticleMode {
Blood = 25,
Enraged = 26,
BigShrapnel = 27,
Laser = 28,
}
impl ParticleMode {

View File

@ -4327,6 +4327,74 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::BasicRanged(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::golem::ShootAnimation::update_skeleton(
&target_base,
(Some(s.stage_section), time, state.state_time, look_dir),
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::BasicBeam(s) => {
let stage_time = s.timer.as_secs_f32();
let stage_progress = match s.stage_section {
StageSection::Buildup => {
stage_time / s.static_data.buildup_duration.as_secs_f32()
},
StageSection::Cast => s.timer.as_secs_f32(),
StageSection::Recover => {
stage_time / s.static_data.recover_duration.as_secs_f32()
},
_ => 0.0,
};
anim::golem::BeamAnimation::update_skeleton(
&target_base,
(Some(s.stage_section), time, state.state_time, look_dir),
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::BasicMelee(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::Swing => {
stage_time / s.static_data.swing_duration.as_secs_f32()
},
StageSection::Recover => {
stage_time / s.static_data.recover_duration.as_secs_f32()
},
_ => 0.0,
};
anim::golem::AlphaAnimation::update_skeleton(
&target_base,
(Some(s.stage_section), time, state.state_time),
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::Shockwave(s) => {
let stage_time = s.timer.as_secs_f32();
let stage_progress = match s.stage_section {

View File

@ -791,6 +791,7 @@ impl ParticleMgr {
beam::FrontendSpecifier::HealingBeam => {
// Emit a light when using healing
lights.push(Light::new(pos.0, Rgb::new(0.1, 1.0, 0.15), 1.0));
self.particles.reserve(beam_tick_count as usize);
for i in 0..beam_tick_count {
self.particles.push(Particle::new_directed(
beam.properties.duration,
@ -804,6 +805,7 @@ impl ParticleMgr {
beam::FrontendSpecifier::LifestealBeam => {
// Emit a light when using lifesteal beam
lights.push(Light::new(pos.0, Rgb::new(0.8, 1.0, 0.5), 1.0));
self.particles.reserve(beam_tick_count as usize);
for i in 0..beam_tick_count {
self.particles.push(Particle::new_directed(
beam.properties.duration,
@ -814,6 +816,17 @@ impl ParticleMgr {
));
}
},
beam::FrontendSpecifier::ClayGolem => {
self.particles.resize_with(self.particles.len() + 2, || {
Particle::new_directed(
beam.properties.duration,
time,
ParticleMode::Laser,
pos.0,
pos.0 + *ori.look_dir() * range,
)
})
},
}
}
}
@ -1110,11 +1123,13 @@ impl ParticleMgr {
let distance =
shockwave.properties.speed * (elapsed as f32 - sub_tick_interpolation);
let new_particle_count = distance / scale as f32;
let particle_count_factor = radians / (3.0 * scale);
let new_particle_count = distance * particle_count_factor;
self.particles.reserve(new_particle_count as usize);
for d in 0..((distance / scale) as i32) {
let arc_position = theta - radians / 2.0 + dtheta * d as f32 * scale;
for d in 0..(new_particle_count as i32) {
let arc_position =
theta - radians / 2.0 + dtheta * d as f32 / particle_count_factor;
let position = pos.0
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);

View File

@ -671,34 +671,43 @@ impl Floor {
},
},
)),
3 => entity
.with_body(comp::Body::BipedSmall(
comp::biped_small::Body::random_with(
dynamic_rng,
&comp::biped_small::Species::Haniwa,
),
))
.with_name("Haniwa")
.with_loadout_config(loadout_builder::LoadoutConfig::Haniwa)
.with_skillset_config(
common::skillset_builder::SkillSetConfig::Haniwa,
)
.with_loot_drop(chosen.read().choose().to_item())
.with_main_tool(comp::Item::new_from_asset_expect(
match dynamic_rng.gen_range(0..5) {
0 => {
"common.items.npc_weapons.biped_small.haniwa.adlet_bow"
3 => match dynamic_rng.gen_range(0..4) {
0 => entity
.with_body(comp::Body::Object(comp::object::Body::HaniwaSentry))
.with_name("Haniwa Sentry".to_string())
.with_loot_drop(comp::Item::new_from_asset_expect(
"common.items.crafting_ing.stones",
)),
_ => entity
.with_body(comp::Body::BipedSmall(
comp::biped_small::Body::random_with(
dynamic_rng,
&comp::biped_small::Species::Haniwa,
),
))
.with_name("Haniwa")
.with_loadout_config(loadout_builder::LoadoutConfig::Haniwa)
.with_skillset_config(
common::skillset_builder::SkillSetConfig::Haniwa,
)
.with_loot_drop(chosen.read().choose().to_item())
.with_main_tool(comp::Item::new_from_asset_expect(
match dynamic_rng.gen_range(0..5) {
0 => {
"common.items.npc_weapons.biped_small.haniwa.\
adlet_bow"
},
1 => {
"common.items.npc_weapons.biped_small.haniwa.\
gnoll_staff"
},
_ => {
"common.items.npc_weapons.biped_small.haniwa.\
wooden_spear"
},
},
1 => {
"common.items.npc_weapons.biped_small.haniwa.\
gnoll_staff"
},
_ => {
"common.items.npc_weapons.biped_small.haniwa.\
wooden_spear"
},
},
)),
)),
},
4 => entity
.with_body(comp::Body::BipedSmall(
comp::biped_small::Body::random_with(
@ -810,8 +819,6 @@ impl Floor {
Lottery::<LootSpec>::load_expect("common.loot_tables.fallback")
},
};
let chosen = chosen.read();
let chosen = chosen.choose();
let entity = match room.difficulty {
0 => {
vec![
@ -823,7 +830,7 @@ impl Floor {
),
))
.with_name("Harvester".to_string())
.with_loot_drop(chosen.to_item()),
.with_loot_drop(chosen.read().choose().to_item()),
]
},
1 => {
@ -836,7 +843,7 @@ impl Floor {
),
))
.with_name("Yeti".to_string())
.with_loot_drop(chosen.to_item()),
.with_loot_drop(chosen.read().choose().to_item()),
]
},
2 => {
@ -849,11 +856,12 @@ impl Floor {
),
))
.with_name("Tidal Warrior".to_string())
.with_loot_drop(chosen.to_item()),
.with_loot_drop(chosen.read().choose().to_item()),
]
},
3 => {
vec![
let mut entities = Vec::new();
entities.resize_with(2, || {
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(comp::Body::Golem(
comp::golem::Body::random_with(
@ -862,8 +870,9 @@ impl Floor {
),
))
.with_name("Clay Golem".to_string())
.with_loot_drop(chosen.to_item()),
]
.with_loot_drop(chosen.read().choose().to_item())
});
entities
},
4 => {
vec![
@ -875,7 +884,7 @@ impl Floor {
),
))
.with_name("Minotaur".to_string())
.with_loot_drop(chosen.to_item()),
.with_loot_drop(chosen.read().choose().to_item()),
]
},
5 => {
@ -888,7 +897,7 @@ impl Floor {
),
))
.with_name("Mindflayer".to_string())
.with_loot_drop(chosen.to_item())
.with_loot_drop(chosen.read().choose().to_item())
.with_skillset_config(
common::skillset_builder::SkillSetConfig::Mindflayer,
),