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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ User-facing changes only. The release workflow publishes the matching

### Fixed

- Setups page: Crosshair now names the crosshair the setup uses, with your own designs under
the names you saved them with, instead of reading "Execs crosshairs" for every setup.
- Viewmodels: hiding a weapon no longer hides another weapon you left shown when the two share
animations. Keep melee visible no longer hid Soldier's Shovel (through the Concheror) or melee
swings for Engineer, Demoman and Medic. A weapon that stays partly visible because of this is
Expand Down
75 changes: 63 additions & 12 deletions apps/desktop/src-tauri/core/src/setup_overview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use std::path::Path;
use serde::Serialize;

use crate::apply::profile_file_bytes_from;
use crate::profile::{load_library_from, load_manifest, ProfileError, ProfileManifest};
use crate::profile::{
load_library_from, load_manifest, CrosshairRecord, ProfileError, ProfileManifest,
};
use crate::profile_compare::{casual_names, managed_text, managed_values, sound_names};

const CONFIG_CFG: &str = "tf/cfg/config.cfg";
Expand All @@ -17,8 +19,12 @@ const CONFIG_CFG: &str = "tf/cfg/config.cfg";
pub struct SetupOverview {
pub id: String,
pub hud: Option<String>,
/// The custom crosshair pack; `None` when it is off.
/// The crosshair every weapon gets from the custom pack (its `shape`;
/// per-weapon assignments aside); `None` when the pack is off.
pub crosshair: Option<String>,
/// Saved designer parameters when `crosshair` is one of the player's
/// designs, so it can be shown under its saved name.
pub crosshair_design: Option<String>,
pub fov: Option<String>,
pub sensitivity: Option<String>,
pub hit_sound: Option<String>,
Expand Down Expand Up @@ -51,6 +57,18 @@ pub fn setup_overviews(
.collect())
}

/// The stored design behind `record.shape`. `design` holds either a single
/// design (for the `designed` entry) or a map of library name to design.
fn shape_design(record: &CrosshairRecord) -> Option<String> {
let raw = record.design.as_deref()?;
let value: serde_json::Value = serde_json::from_str(raw).ok()?;
let entries = value.as_object()?;
if entries.contains_key("style") {
return (record.shape == "designed").then(|| raw.to_string());
}
entries.get(&record.shape)?.as_str().map(str::to_string)
}

/// Values in the profile's saved `config.cfg`; empty when it has none.
fn config_values(profiles_dir: &Path, manifest: &ProfileManifest) -> BTreeMap<String, String> {
if !manifest.files.iter().any(|file| file.path == CONFIG_CFG) {
Expand Down Expand Up @@ -80,14 +98,17 @@ fn overview(profiles_dir: &Path, manifest: &ProfileManifest) -> SetupOverview {
let fov = value("fov_desired");
let sensitivity = value("sensitivity");
let (hit_sound, kill_sound) = sound_names(manifest.hitsound.as_ref());
// `id` names the pack itself; `shape` is the crosshair it gives every
// weapon. Very old records may have no shape to name.
let pack = manifest
.crosshair
.as_ref()
.filter(|record| !record.inactive && !record.shape.is_empty());
SetupOverview {
id: manifest.id.clone(),
hud: manifest.hud.as_ref().map(|hud| hud.id.clone()),
crosshair: manifest
.crosshair
.as_ref()
.filter(|record| !record.inactive)
.map(|record| record.id.clone()),
crosshair: pack.map(|record| record.shape.clone()),
crosshair_design: pack.and_then(shape_design),
fov,
sensitivity,
hit_sound,
Expand Down Expand Up @@ -209,8 +230,7 @@ mod tests {
fn crosshair_hud_sounds_and_mods_come_from_the_manifest() {
let dir = crate::test_temp_dir();
let (profiles, root, id) = saved(&dir, &[], false);
let edit = |active: bool| {
let crosshair = serde_json::json!({ "id": "dot", "inactive": !active });
let edit = |crosshair: serde_json::Value| {
let hitsound = serde_json::json!({
"hit": { "name": "ding", "source": "file" },
"kill": { "name": "bell", "source": "community" },
Expand Down Expand Up @@ -253,19 +273,50 @@ mod tests {
.unwrap();
};

edit(true);
let pack = |shape: &str, design: Option<&str>, active: bool| {
serde_json::json!({
"id": "execs-crosshairs",
"inactive": !active,
"shape": shape,
"assignments": { "tf_weapon_sniperrifle": "circle" },
"design": design,
})
};

edit(pack("dot", None, true));
let on = only(&profiles, &root);
assert_eq!(on.hud.as_deref(), Some("flawhud"));
// The shape every weapon gets, not the pack's id or an assignment.
assert_eq!(on.crosshair.as_deref(), Some("dot"));
assert_eq!(on.crosshair_design, None);
assert_eq!(on.hit_sound.as_deref(), Some("ding"));
assert_eq!(on.kill_sound.as_deref(), Some("bell"));
// Two mods, plus two Casual addons and one particle mod (named "skin"
// after the mod it points at).
assert_eq!(on.mods, 2 + 3);
assert_eq!(on.performance, None);

edit(false);
assert_eq!(only(&profiles, &root).crosshair, None);
// A design is passed along with its saved parameters, whether the
// record holds one design or a library of them.
let single = r#"{"style":"dot","label":"Mine"}"#;
edit(pack("designed", Some(single), true));
let designed = only(&profiles, &root);
assert_eq!(designed.crosshair.as_deref(), Some("designed"));
assert_eq!(designed.crosshair_design.as_deref(), Some(single));
let ring = r#"{"style":"ring","label":"Ring"}"#;
let library = serde_json::json!({ "design-ring": ring, "design-x": "{}" }).to_string();
edit(pack("design-ring", Some(&library), true));
assert_eq!(
only(&profiles, &root).crosshair_design.as_deref(),
Some(ring)
);
edit(pack("dot", Some(single), true));
assert_eq!(only(&profiles, &root).crosshair_design, None);

edit(pack("dot", None, false));
let off = only(&profiles, &root);
assert_eq!(off.crosshair, None);
assert_eq!(off.crosshair_design, None);
std::fs::remove_dir_all(dir).unwrap();
}

Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/src/SetupsHome.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const OVERVIEWS: SetupOverview[] = [
id: "main",
hud: "rayshud",
crosshair: "dot",
crosshairDesign: null,
fov: "90",
sensitivity: "2.5",
hitSound: "Quake hit",
Expand All @@ -53,6 +54,7 @@ const OVERVIEWS: SetupOverview[] = [
id: "casual",
hud: "flawhud",
crosshair: "dot",
crosshairDesign: null,
fov: "90",
sensitivity: "1.8",
hitSound: "Quake hit",
Expand All @@ -64,6 +66,7 @@ const OVERVIEWS: SetupOverview[] = [
id: "plain",
hud: null,
crosshair: null,
crosshairDesign: null,
fov: "110",
sensitivity: "2.5",
hitSound: null,
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src/lib/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,8 +422,13 @@ export type SetupOverview = {
id: string;
/** HUD catalog id or folder name. */
hud: string | null;
/** Custom crosshair id; `null` when the crosshair pack is off. */
/**
* The crosshair the pack gives every weapon (per-weapon picks aside);
* `null` when the crosshair pack is off.
*/
crosshair: string | null;
/** Saved designer parameters when `crosshair` is one of the player's designs. */
crosshairDesign: string | null;
/** `fov_desired`, from the Gameplay cfg or else the profile's config.cfg. */
fov: string | null;
sensitivity: string | null;
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/lib/preview-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ const PREVIEW_SETUP_FACTS: Omit<SetupOverview, "id">[] = [
{
hud: "rayshud",
crosshair: "dot",
crosshairDesign: null,
fov: "90",
sensitivity: "2.5",
hitSound: "Quake hit",
Expand All @@ -177,6 +178,7 @@ const PREVIEW_SETUP_FACTS: Omit<SetupOverview, "id">[] = [
{
hud: "budhud",
crosshair: "dot",
crosshairDesign: null,
fov: "90",
sensitivity: "1.8",
hitSound: "Quake hit",
Expand All @@ -187,6 +189,7 @@ const PREVIEW_SETUP_FACTS: Omit<SetupOverview, "id">[] = [
{
hud: null,
crosshair: null,
crosshairDesign: null,
fov: "110",
sensitivity: "2.5",
hitSound: null,
Expand All @@ -197,6 +200,7 @@ const PREVIEW_SETUP_FACTS: Omit<SetupOverview, "id">[] = [
{
hud: "m0rehud",
crosshair: "ring",
crosshairDesign: null,
fov: "100",
sensitivity: "2.5",
hitSound: "Bonk",
Expand Down
17 changes: 17 additions & 0 deletions apps/desktop/src/lib/setups-ui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function overview(patch: Partial<SetupOverview> = {}): SetupOverview {
id: "main",
hud: "rayshud",
crosshair: "dot",
crosshairDesign: null,
fov: "90",
sensitivity: "2.5",
hitSound: "Quake hit",
Expand Down Expand Up @@ -126,6 +127,14 @@ describe("fact values", () => {
expect(value("hud")).toBe("Ray's HUD");
expect(value("crosshair")).toBe("Small dot");
expect(value("crosshair", { crosshair: "design-my-ring" })).toBe("My ring");
const named = JSON.stringify({ style: "ring", label: "Tournament ring" });
expect(value("crosshair", { crosshair: "design-my-ring", crosshairDesign: named })).toBe(
"Tournament ring",
);
expect(value("crosshair", { crosshair: "designed", crosshairDesign: named })).toBe(
"Tournament ring",
);
expect(value("crosshair", { crosshair: "designed" })).toBe("My crosshair");
expect(value("fov")).toBe("90°");
expect(value("fov", { fov: "90.0" })).toBe("90°");
expect(value("sensitivity", { sensitivity: "1.800000" })).toBe("1.8");
Expand All @@ -138,6 +147,7 @@ describe("fact values", () => {
const bare = {
hud: null,
crosshair: null,
crosshairDesign: null,
fov: null,
sensitivity: null,
hitSound: null,
Expand Down Expand Up @@ -189,6 +199,13 @@ describe("differences from the setup in the game", () => {
expect(differingFacts(overview({ fov: "wide" }), overview({ fov: "90" }))).toEqual(["fov"]);
});

it("tells two different designs saved under the same name apart", () => {
const design = (label: string) =>
overview({ crosshair: "designed", crosshairDesign: JSON.stringify({ style: "dot", label }) });
expect(differingFacts(design("A"), design("B"))).toEqual(["crosshair"]);
expect(differingFacts(design("A"), design("A"))).toEqual([]);
});

it("summarises the count against the active setup's name", () => {
expect(changeSummary(3, "Main")).toBe("3 changes from Main");
expect(changeSummary(1, "Main")).toBe("1 change from Main");
Expand Down
13 changes: 11 additions & 2 deletions apps/desktop/src/lib/setups-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ function numberLabel(value: string | null, unit = ""): string {
return parsed === null ? value : `${parsed}${unit}`;
}

/** The pack's crosshair, under its saved name when it is one of the player's designs. */
function crosshairFact({ crosshair, crosshairDesign }: SetupOverview): string {
if (crosshair === null) return TF2_OWN;
return crosshairLabel(
crosshair,
crosshairDesign === null ? {} : { [crosshair]: crosshairDesign },
);
}

export function factValue(
key: SetupFactKey,
overview: SetupOverview,
Expand All @@ -101,7 +110,7 @@ export function factValue(
case "hud":
return hudLabel(overview.hud, catalog);
case "crosshair":
return overview.crosshair === null ? TF2_OWN : crosshairLabel(overview.crosshair);
return crosshairFact(overview);
case "fov":
return numberLabel(overview.fov, "°");
case "sensitivity":
Expand Down Expand Up @@ -135,7 +144,7 @@ export function sameFact(key: SetupFactKey, a: SetupOverview, b: SetupOverview):
case "hud":
return sameText(a.hud, b.hud, true);
case "crosshair":
return sameText(a.crosshair, b.crosshair);
return sameText(a.crosshair, b.crosshair) && a.crosshairDesign === b.crosshairDesign;
case "fov":
return sameNumber(a.fov, b.fov);
case "sensitivity":
Expand Down
Loading