Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions native/src/sql/sim/attacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1143,8 +1143,12 @@ mod tests {
m_target: "m_target",
m_threshold: "m_threshold",
m_player: "m_player",
m_subsector: "m_subsector",
prndindex: "prndindex",
readyweapon: "readyweapon",
p_cheats: "p_cheats",
p_powers: "p_powers",
sec_special: "sec_special",
}
}

Expand Down
351 changes: 308 additions & 43 deletions native/src/sql/sim/inter.rs

Large diffs are not rendered by default.

53 changes: 42 additions & 11 deletions native/src/sql/sim/missile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -842,28 +842,49 @@ fn clamp(mom: &str) -> String {
}

/// The ClickHouse type of a [`thought`] tuple, for a caller that carries a
/// list of them through a fold.
pub const THOUGHT_TYPE: &str = "Tuple(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, \
Int32, Int32, Int32, Int32, UInt32, Tuple(Int32, Int32, Int32, \
Int32, Int32, Int32, Int32, Int32, Int32, UInt32, Int32, UInt8, \
UInt8, Int32, UInt32, UInt8), UInt32, UInt8)";
/// list of them through a fold. Its own [`thought::HURT`] member is
/// [`inter::HURT_TYPE`], named once rather than spelled twice so the two
/// cannot drift apart.
pub fn thought_type() -> String {
format!(
"Tuple(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, \
Int32, UInt32, {}, UInt32, UInt8)",
inter::HURT_TYPE
)
}

/// The mobj arrays a missile already on the list reads for its own move,
/// its own fall and its own state cycle, plus what a thing it damages
/// reads. `slot` names which mobj in these `flying` and `hurting` reads
/// the missile itself, over the same arrays a caller driving anything
/// else's move already holds.
///
/// The player's own health, armour, damagecount and attacker thread
/// through every missile the list carries, in order: each one's own
/// `P_DamageMobj` reads what the missile before it left, the way
/// [`inter::damage_fold`] threads them for a caller with only one ask.
/// `start` is the tic's own player fields for the first list this runs
/// over, or the previous list's own final fields for one chained after
/// it; the fold's own final tuple carries them on for a caller to chain
/// further still.
pub fn thinks_fold(
asks: &str,
start: &str,
map: &World<'_>,
flying: &Flying<'_>,
hurting: &inter::Hurting<'_>,
) -> String {
let (values, body) = thought_of(map, flying, hurting);
let step = bind::chain_in("tka", &values, &body);
let folded = bind::chain_in(
"tkb",
&[("tk_result".to_owned(), step)],
"(arrayPushBack(tk_held.1, tk_result.1), tk_result.2)",
);
format!(
"arrayFold((tk_held, tk_ask) -> arrayPushBack(tk_held, {}), {asks}, \
CAST([] AS Array({THOUGHT_TYPE})))",
bind::chain_in("tka", &values, &body)
"arrayFold((tk_held, tk_ask) -> {folded}, {asks}, \
(CAST([] AS Array({})), {start}))",
thought_type()
)
}

Expand Down Expand Up @@ -1199,7 +1220,13 @@ fn thought_of(
damage = struck::DAMAGE,
),
);
value("mn_hurt", inter::damage_fold("mn_damage_asks", hurting));
// `tk_held.2` is what the missile before this one in the same list
// left the player's own fields at, or the tic's own row where this is
// the first.
value(
"mn_hurt",
inter::damage_fold("mn_damage_asks", "tk_held.2", hurting),
);
value(
"mn_hurt_target",
format!(
Expand Down Expand Up @@ -1546,7 +1573,7 @@ fn thought_of(
"mn_draws".to_owned(),
"mn_stuck".to_owned(),
];
(values, format!("({})", members.join(", ")))
(values, format!("(({}), mn_hurt)", members.join(", ")))
}

#[cfg(test)]
Expand Down Expand Up @@ -1828,13 +1855,17 @@ mod tests {
m_target: "m_target",
m_threshold: "m_threshold",
m_player: "m_player",
m_subsector: "m_subsector",
prndindex: "prndindex",
readyweapon: "readyweapon",
p_cheats: "p_cheats",
p_powers: "p_powers",
sec_special: "sec_special",
}
}

fn thinks_sql() -> String {
thinks_fold("asks", &map(), &flying(), &hurting())
thinks_fold("asks", &inter::no_hurt(), &map(), &flying(), &hurting())
}

#[test]
Expand Down
150 changes: 138 additions & 12 deletions native/src/sql/sim/mobj.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! What a thing does with its momentum and its states, from `p_mobj.c`.

use super::map::{self, World, answer};
use super::{State, attacks, enemy, inter, maputl, mask, missile, sight, specials, unresolved};
use super::{
State, attacks, enemy, inter, maputl, mask, missile, player, sight, specials, unresolved,
};
use crate::sql::Statement;
use crate::sql::bind;
use crate::sql::fixed;
Expand Down Expand Up @@ -550,8 +552,12 @@ pub fn thinkers(state: &State) -> Vec<(String, String)> {
m_target: &s("m_target"),
m_threshold: &s("m_threshold"),
m_player: &s("m_player"),
m_subsector: &s("m_subsector"),
prndindex: &s("prndindex"),
readyweapon: &s("p_readyweapon"),
p_cheats: &s("p_cheats"),
p_powers: &s("p_powers"),
sec_special: &s("sec_special"),
};
bind(
"mt_attacker_draws_asks",
Expand Down Expand Up @@ -916,18 +922,28 @@ pub fn thinkers(state: &State) -> Vec<(String, String)> {
m_target: "mk_m_target",
m_threshold: "mk_m_threshold",
m_player: &s("m_player"),
m_subsector: "mk_m_subsector",
prndindex: &s("prndindex"),
readyweapon: &s("p_readyweapon"),
p_cheats: &s("p_cheats"),
p_powers: &s("p_powers"),
sec_special: &s("sec_special"),
};
// The player's own health, armour, damagecount and attacker pick up
// from `mt_hurt`'s own final fields: a claw runs before this in
// `strikes`, the first of the tic's three damage folds.
bind(
"mt_missile_thoughts",
"mt_missile_folded",
missile::thinks_fold(
"mt_missile_asks",
"mt_hurt",
&struck_map,
&struck_flying,
&struck_hurting,
),
);
bind("mt_missile_thoughts", "mt_missile_folded.1".to_owned());
bind("mt_missile_players", "mt_missile_folded.2".to_owned());
bind(
"mt_missile_hurt_targets",
format!(
Expand Down Expand Up @@ -988,6 +1004,33 @@ pub fn thinkers(state: &State) -> Vec<(String, String)> {
missile::thought::STUCK
),
),
(
unresolved::PLAYER_DIES,
&format!(
"arrayExists(t -> t.{hurt}.{dies} = 1, mt_missile_thoughts) \
OR mt_hurt.{dies} = 1",
hurt = missile::thought::HURT,
dies = inter::hurt::PL_DIES,
),
),
(
unresolved::SECTOR11_STUCK,
&format!(
"arrayExists(t -> t.{hurt}.{sec} = 1, mt_missile_thoughts) \
OR mt_hurt.{sec} = 1",
hurt = missile::thought::HURT,
sec = inter::hurt::PL_SECTOR11,
),
),
(
unresolved::DM_SAME_TARGET,
&format!(
"arrayExists(t -> t.{hurt}.{same} = 1, mt_missile_thoughts) \
OR mt_hurt.{same} = 1",
hurt = missile::thought::HURT,
same = inter::hurt::SAME_TARGET,
),
),
],
)
),
Expand Down Expand Up @@ -1291,10 +1334,30 @@ fn strikes(state: &State, map: &World<'_>) -> Vec<(String, String)> {
m_target: "mk_m_target",
m_threshold: "mk_m_threshold",
m_player: &s("m_player"),
m_subsector: "mk_m_subsector",
prndindex: &s("prndindex"),
readyweapon: &s("p_readyweapon"),
p_cheats: &s("p_cheats"),
p_powers: &s("p_powers"),
sec_special: &s("sec_special"),
};
bind("mt_hurt", inter::damage_fold("mt_hurt_asks", &hurting));
// The player's own health, armour, damagecount and attacker start
// from the tic's own row: `strikes` runs before this stage's own
// missile impacts, the first of the tic's three damage folds.
bind(
"mt_hurt",
inter::damage_fold(
"mt_hurt_asks",
&inter::player_start(
&s("p_health"),
&s("p_armorpoints"),
&s("p_armortype"),
&s("p_damagecount"),
&s("p_attacker"),
),
&hurting,
),
);

// `P_SpawnMissile` for an imp whose claw did not reach. The fireball
// is the only missile a routine throws here, so its type is the one
Expand Down Expand Up @@ -1428,8 +1491,12 @@ pub fn thrown_thinks(state: &State) -> Vec<(String, String)> {
m_target: &s("m_target"),
m_threshold: &s("m_threshold"),
m_player: &s("m_player"),
m_subsector: &s("m_subsector"),
prndindex: &s("prndindex"),
readyweapon: &s("p_readyweapon"),
p_cheats: &s("p_cheats"),
p_powers: &s("p_powers"),
sec_special: &s("sec_special"),
};

// The compaction appends one slot per thing `mt_thrown` carries, in
Expand All @@ -1442,10 +1509,19 @@ pub fn thrown_thinks(state: &State) -> Vec<(String, String)> {
draws = missile::thrown::DRAWS,
),
);
// The player's own health, armour, damagecount and attacker pick up
// from `mt_missile_players`, the thinker stage's own final fields:
// this is the last of the tic's three damage folds, so its own final
// fields are what `player.rs`'s writeback reads.
bind(
"tk_thoughts",
missile::thinks_fold("tk_asks", &map, &flying, &hurting),
"tk_folded",
missile::thinks_fold("tk_asks", "mt_missile_players", &map, &flying, &hurting),
);
bind("tk_thoughts", "tk_folded.1".to_owned());
bind("tk_players", "tk_folded.2".to_owned());
for (name, expr) in player::hurt_writeback("tk_players") {
bind(&name, expr);
}
bind("tk_slots", "arrayMap(a -> a.1, tk_asks)".to_owned());
bind(
"tk_at",
Expand Down Expand Up @@ -1575,13 +1651,39 @@ pub fn thrown_thinks(state: &State) -> Vec<(String, String)> {
"now_unresolved",
mask(
&s("unresolved"),
&[(
unresolved::TK_STUCK,
&format!(
"arrayExists(t -> t.{} = 1, tk_thoughts)",
missile::thought::STUCK
&[
(
unresolved::TK_STUCK,
&format!(
"arrayExists(t -> t.{} = 1, tk_thoughts)",
missile::thought::STUCK
),
),
)],
(
unresolved::PLAYER_DIES,
&format!(
"arrayExists(t -> t.{hurt}.{dies} = 1, tk_thoughts)",
hurt = missile::thought::HURT,
dies = inter::hurt::PL_DIES,
),
),
(
unresolved::SECTOR11_STUCK,
&format!(
"arrayExists(t -> t.{hurt}.{sec} = 1, tk_thoughts)",
hurt = missile::thought::HURT,
sec = inter::hurt::PL_SECTOR11,
),
),
(
unresolved::DM_SAME_TARGET,
&format!(
"arrayExists(t -> t.{hurt}.{same} = 1, tk_thoughts)",
hurt = missile::thought::HURT,
same = inter::hurt::SAME_TARGET,
),
),
],
),
);
bindings
Expand Down Expand Up @@ -3268,7 +3370,7 @@ mod tests {
/// the damage tint and the weapon it drops are the player's own
/// columns. The claw's own damage fold has to be read the same way.
#[test]
fn a_claw_that_lands_on_the_player_is_unresolved() {
fn a_claw_s_own_stuck_call_leaves_the_thinker_stage_unresolved() {
let bindings = thinkers(&State::default());
let at_unrun = bindings
.iter()
Expand All @@ -3281,6 +3383,30 @@ mod tests {
);
}

/// A hit that would kill the player, from a claw or a missile already
/// in flight, is its own bit rather than folded into `DM_STUCK`.
#[test]
fn a_hit_that_would_kill_the_player_is_its_own_bit() {
let bindings = thinkers(&State::default());
let now_unresolved = bindings
.iter()
.find(|(binding, _)| binding == "now_unresolved")
.map(|(_, expr)| expr.clone())
.unwrap_or_else(|| panic!("now_unresolved is bound"));
assert!(
now_unresolved.contains(&format!("mt_hurt.{} = 1", inter::hurt::PL_DIES)),
"{now_unresolved}"
);
assert!(
now_unresolved.contains(&format!(
"t.{}.{} = 1, mt_missile_thoughts",
missile::thought::HURT,
inter::hurt::PL_DIES,
)),
"{now_unresolved}"
);
}

#[test]
fn the_loop_runs_as_many_steps_as_the_momentum_needs() {
let text = steps("mx", "my", "u");
Expand Down
18 changes: 15 additions & 3 deletions native/src/sql/sim/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ pub mod unresolved {
/// `P_PlayerInSpecialSector`: the player stands in a sector that
/// damages it.
pub const PL_HURTS: u64 = 1 << 2;
/// `P_DamageMobj`: the hit lands on a player, or the frame it enters
/// carries an action other than none, `A_Pain` or `A_Scream`.
/// `P_DamageMobj`: the frame the hit enters carries an action other
/// than none, `A_Pain` or `A_Scream`.
pub const DM_STUCK: u64 = 1 << 3;
/// `P_GunShot`'s own fold: a shot's damage call hits a player or an
/// unimplemented post-hit routine, or a shot crosses a special line
Expand Down Expand Up @@ -163,10 +163,19 @@ pub mod unresolved {
/// The same, for a monster's own move. A monster's own crossing never
/// reaches a door special, so only `PLAT_TRIGGER_SPECIALS` applies.
pub const TX_MULTI_CROSSED: u64 = 1 << 33;
/// `P_DamageMobj`: a hit on the player leaves its health at 0 or below.
pub const PLAYER_DIES: u64 = 1 << 34;
/// `P_DamageMobj`: the target's sector special is 11, and the damage
/// reaches the clamp that keeps a hit there from killing outright.
pub const SECTOR11_STUCK: u64 = 1 << 35;
/// `P_DamageMobj`: a hit lands on a target this tic has already hit
/// once, whether in the same `damage_fold` call or an earlier one
/// chained into it.
pub const DM_SAME_TARGET: u64 = 1 << 36;
}

/// Every bit `unresolved` names, in ascending order.
const UNRESOLVED_BITS: [(u64, &str); 34] = [
const UNRESOLVED_BITS: [(u64, &str); 37] = [
(unresolved::PK_STUCK, "PK_STUCK"),
(unresolved::PX_CROSSED, "PX_CROSSED"),
(unresolved::PL_HURTS, "PL_HURTS"),
Expand Down Expand Up @@ -201,6 +210,9 @@ const UNRESOLVED_BITS: [(u64, &str); 34] = [
(unresolved::MISSILE_STUCK, "MISSILE_STUCK"),
(unresolved::PX_MULTI_CROSSED, "PX_MULTI_CROSSED"),
(unresolved::TX_MULTI_CROSSED, "TX_MULTI_CROSSED"),
(unresolved::PLAYER_DIES, "PLAYER_DIES"),
(unresolved::SECTOR11_STUCK, "SECTOR11_STUCK"),
(unresolved::DM_SAME_TARGET, "DM_SAME_TARGET"),
];

/// The names of the bits `bits` sets, most significant last, for a message
Expand Down
Loading