Training dummy leans with knockback, blast pushes, Vacuum suction and gusts

Hits tip it by their actual knockback (shared with fighters through knock_get), blasts push it, a Vacuum pulls it
toward its wielder and an airblast shoves it away. Blocks stay still. Protocol 7.
This commit is contained in:
shweet committed 2026-11-10 18:39:56 -05:00
1 parent b96defa1e6
commit f4e7784f3c
4 files changed
+52 -13

No files matched your search

+11 -5
View File
@@ -127,6 +127,15 @@ void Fighter::state_reset(Sim& sim, float maxHealth)
isAlive = true;
}
// The knock a hit gives whatever it lands on, centred at center: away from where it came from (and a little upward), at
// a speed that grows with the damage, times the hit's own knock.
Vec2 knock_get(Damage& damage, Vec2 center)
{
Vec2 away = center - damage.source;
Vec2 direction = vec2_normalize(vec2_normalize(vec2_length(away) > 1 ? away : Vec2{0, -1}) + Vec2{0, -KNOCK_LIFT});
return direction * std::min(KNOCK_MAX, KNOCK_BASE + damage.amount * KNOCK_PER_DAMAGE) * damage.knock;
}
// Loses health with a flash and hitstop, and gets knocked away from where the hit came from (and a little upward),
// up to a speed that grows with the damage (see knock_take). Shock Absorber softens it. Running out of health kills it
// in a burst, and its attacks vanish with it.
@@ -138,11 +147,8 @@ void Fighter::damage_take(Sim& sim, Damage damage)
flash = 1;
hitstop_add(damage.hitstop);
Vec2 away = center_get() - damage.source;
Vec2 direction = vec2_normalize(vec2_normalize(vec2_length(away) > 1 ? away : Vec2{0, -1}) + Vec2{0, -KNOCK_LIFT});
float knock = std::min(KNOCK_MAX, KNOCK_BASE + damage.amount * KNOCK_PER_DAMAGE) * damage.knock *
(isRacing ? RACE_KNOCK : 1) * stat_get(STAT_KNOCKBACK_TAKEN);
knock_take(direction * knock, damage.owner);
float taken = (isRacing ? RACE_KNOCK : 1) * stat_get(STAT_KNOCKBACK_TAKEN);
knock_take(knock_get(damage, center_get()) * taken, damage.owner);
if (health > 0)
return;
die(sim);
+3
View File
@@ -98,6 +98,9 @@ constexpr float CHARGE_VISIBLE = 0.15f;
// knock it about (half as hard), and touching a pit puts it back on the last ground it stood squarely on. Slopes slow
// it climbing and speed it up descending. Crouching on one slides it down, rolling, and the slide carries on over flat
// ground until it slows.
struct Damage;
Vec2 knock_get(Damage& damage, Vec2 center);
class Fighter
{
friend class Scene;
+1 -1
View File
@@ -27,7 +27,7 @@
#include <string_view>
#include <type_traits>
constexpr int PROTOCOL_VERSION = 6;
constexpr int PROTOCOL_VERSION = 7;
constexpr int LOBBY_MEMBERS_MAX = 16;
constexpr int LOBBY_CODE_LENGTH = 4;
// Letters only, without I and O, so codes are easy to read out and type.
+37 -7
View File
@@ -85,7 +85,10 @@ constexpr float WHIP_WOBBLES = 1;
constexpr float WHIP_DAMPING = 0.02f;
constexpr float WHIP_SAG = 0.6f;
constexpr float WHIP_PULL = 220;
constexpr float WOBBLE_IMPULSE = 0.15f;
// How far a push tips a dummy: its sideways speed into lean speed for a sudden push (a hit or a blast), and its
// sideways acceleration into lean acceleration for a steady one (a Vacuum's pull).
constexpr float WOBBLE_PER_SPEED = 0.005f;
constexpr float WOBBLE_PER_PULL = 0.05f;
// Vacuum: how fast its pull speeds fighters and shots up toward it, how much of an airblast's knock is left at the edge
// of its reach, and how much of it shoves shots.
constexpr float VACUUM_PULL_RATE = 900;
@@ -1607,6 +1610,14 @@ void Weapons::attack_hit(Sim& sim, Attack& attack, int target, Vec2 point)
});
}
// Tips a dummy the way a sudden push goes (a block, which never leans, ignores it), by its sideways part: a dummy
// stands on its stand, so only its lean feels the push.
static void dummy_push(Dummy& dummy, Vec2 push)
{
if (dummy.tile < 0)
dummy.wobbleSpeed += push.x * WOBBLE_PER_SPEED;
}
// Hurts a target with all the feedback: sparks, a number, shake that grows with the damage and a sound. The attacker
// freezes in hitstop, its Boxing Glove adds to the knock, and its Embiggener to the damage.
// Dummies flash and wobble, blocks crack and break, and fighters take the damage themselves. In a race, fighters only
@@ -1627,7 +1638,7 @@ void Weapons::damage_deal(Sim& sim, Damage damage)
{
Dummy& dummy = sim.world.dummies[damage.target - MAX_FIGHTERS];
dummy.flash = 1;
dummy.wobbleSpeed += side * WOBBLE_IMPULSE * damage.amount;
dummy_push(dummy, knock_get(damage, {box.x + box.w / 2, box.y + box.h / 2}));
dummy.health -= damage.amount;
if (dummy.tile >= 0 && dummy.health <= 0)
sim.world.block_break(sim, dummy, damage.owner);
@@ -2052,6 +2063,13 @@ void Weapons::blast_spawn(Sim& sim, Blast blast)
vec2_normalize(distance > 0 ? away : Vec2{0, -1}) * KNOCKBACK_SPEED * (1 - distance / reach) * absorb;
fighter.impulse_add(push);
}
for (Dummy& dummy : sim.world.dummies)
{
Vec2 away = Vec2{dummy.box.x + dummy.box.w / 2, dummy.box.y + dummy.box.h / 2} - blast.position;
float distance = vec2_length(away);
if (blast.isPushing && distance < reach)
dummy_push(dummy, vec2_normalize(away) * KNOCKBACK_SPEED * (1 - distance / reach) * boxing);
}
Attack attack = {
.stats = {.damage = blast.damage, .range = blast.area},
@@ -2283,6 +2301,15 @@ void Weapons::vacuum_move(Sim& sim, Attack& attack)
float pull = std::min(room, VACUUM_PULL_RATE * boxing * TICK) * target.stat_get(STAT_KNOCKBACK_TAKEN);
target.body.velocity = target.body.velocity + toward * pull;
}
for (int i = 0; i < (int)sim.world.dummies.size(); i++)
{
Dummy& dummy = sim.world.dummies[i];
Vec2 center = {dummy.box.x + dummy.box.w / 2, dummy.box.y + dummy.box.h / 2};
if (dummy.tile >= 0 || !is_target(sim, MAX_FIGHTERS + i, attack.owner) || !is_in_cone(attack, center))
continue;
Vec2 toward = vec2_normalize(attack.position - center);
dummy.wobbleSpeed += toward.x * VACUUM_PULL_RATE * fighter.stat_get(STAT_KNOCKBACK) * WOBBLE_PER_PULL * TICK;
}
// Each interval (the hit cooldown counting it down), everyone in the cone can be nibbled again.
float interval = fighter.level_get(attack.key).cooldown / fighter.stat_get(STAT_ATTACK_RATE);
bool isPulse = attack.hitCooldown <= 0;
@@ -2323,13 +2350,16 @@ void Weapons::gust_move(Sim& sim, Attack& attack)
if (!is_target(sim, target, attack.owner) || !is_in_cone(attack, center))
continue;
attack_hit(sim, attack, target, center);
if (target >= MAX_FIGHTERS)
continue;
// Boxing Glove adds to it as to any knock.
Vec2 push = vec2_normalize(center - attack.position) * attack.stats.speed * cone_strength_get(attack, center) *
sim.fighters[attack.owner].stat_get(STAT_KNOCKBACK);
if (target >= MAX_FIGHTERS)
{
dummy_push(sim.world.dummies[target - MAX_FIGHTERS], push);
continue;
}
Fighter& fighter = sim.fighters[target];
float strength = attack.stats.speed * cone_strength_get(attack, center) *
fighter.stat_get(STAT_KNOCKBACK_TAKEN) * sim.fighters[attack.owner].stat_get(STAT_KNOCKBACK);
fighter.knock_take(vec2_normalize(center - attack.position) * strength, attack.owner);
fighter.knock_take(push * fighter.stat_get(STAT_KNOCKBACK_TAKEN), attack.owner);
}
shots_drift(sim, attack, attack.stats.speed * GUST_SHOT_SHARE, 0, attack.isCopy ? 0 : GUST_RESTORE);
}