This commit is contained in:
Purp1e
2025-03-27 21:14:36 +08:00
14 changed files with 1037 additions and 209 deletions

View File

@@ -45,7 +45,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
addToast({ title: `电源计划已切换 → ${PowerPlans[current].title}` })
})
})
}, [])
// 检测steam路径和游戏路径是否有效
const debounceSteamDir = useDebounce(steam.state.steamDir, {

View File

@@ -1,178 +1,452 @@
import { CloseSmall, Down, Edit, Plus, SettingConfig, Up } from "@icon-park/react"
import { useState } from "react"
import { useEffect, useState } from "react"
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "../window/Card"
import { ToolButton } from "../window/ToolButton"
import { addToast, NumberInput, Tab, Tabs, Tooltip } from "@heroui/react"
import { motion } from "framer-motion"
import { useToolStore } from "@/store/tool"
import { useToolStore, VideoSetting as VideoConfig, VideoSettingTemplate } from "@/store/tool"
import { useSteamStore } from "@/store/steam"
const VideoSetting = () => {
const [hide, setHide] = useState(false)
const [edit, setEdit] = useState(false)
const tool = useToolStore()
// const [launchOpt, setLaunchOpt] = useState(tool.state.VideoSettings[tool.state.launchIndex] || "")
const steam = useSteamStore()
const videoSettings = (video: VideoConfig) => {
return [
{
type: "fullscreen",
title: "全屏",
value: video.fullscreen === "1" ? "全屏" : "窗口",
options: ["窗口", "全屏"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
}[value] || "0"
)
},
},
{
type: "mat_vsync",
title: "垂直同步",
value: video.mat_vsync === "1" ? "开启" : "关闭",
options: ["关闭", "开启"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
}[value] || "0"
)
},
},
{
type: "r_low_latency",
title: "低延迟模式",
value: video.r_low_latency === "1" ? "开启" : "关闭",
options: ["关闭", "开启"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
}[value] || "0"
)
},
},
// TODO: 改选项不在 cs2_video.txt 中
// {
// type: "r_player_visible_mode",
// title: "增强角色对比度",
// value: video.r_csgo_cmaa_enable === "1" ? "启用" : "禁用",
// options: ["禁用", "启用"],
// },
{
type: "r_csgo_cmaa_enable",
title: "CMAA2抗锯齿",
value: video.r_csgo_cmaa_enable === "1" ? "开启" : "关闭",
options: ["关闭", "开启"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
}[value] || "0"
)
},
},
{
type: "msaa_samples",
title: "多重采样抗锯齿",
value:
{
0: "无",
2: "2X MSAA",
4: "4X MSAA",
8: "8X MSAA",
}[parseInt(video.msaa_samples, 10)] || "无",
options: ["无", "2X MSAA", "4X MSAA", "8X MSAA"],
mapping: (value: string) => {
return (
{
: "0",
"2X MSAA": "2",
"4X MSAA": "4",
"8X MSAA": "8",
}[value] || "0"
)
},
},
{
type: "videocfg_shadow_quality",
title: "全局阴影效果",
value: ["低", "中", "高", "非常高"][parseInt(video.videocfg_shadow_quality, 10)] || "低",
options: ["低", "中", "高", "非常高"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
: "2",
: "3",
}[value] || "0"
)
},
},
{
type: "videocfg_dynamic_shadows",
title: "动态阴影",
value: ["仅限日光", "全部"][parseInt(video.videocfg_dynamic_shadows, 10)] || "仅限日光",
options: ["仅限日光", "全部"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
}[value] || "0"
)
},
},
{
type: "videocfg_texture_detail",
title: "模型/贴图细节",
value: ["低", "中", "高"][parseInt(video.videocfg_texture_detail, 10)] || "低",
options: ["低", "中", "高"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
: "2",
}[value] || "0"
)
},
},
{
type: "r_texturefilteringquality",
title: "贴图过滤模式",
value:
["双线性", "三线性", "异向 2X", "异向 4X", "异向 8X", "异向 16X"][
parseInt(video.r_texturefilteringquality, 10)
] || "双线性",
options: ["双线性", "三线性", "异向 2X", "异向 4X", "异向 8X", "异向 16X"],
mapping: (value: string) => {
return (
{
线: "0",
线: "1",
"异向 2X": "2",
"异向 4X": "3",
"异向 8X": "4",
"异向 16X": "5",
}[value] || "0"
)
},
},
{
type: "shaderquality",
title: "光影细节",
value: video.shaderquality === "1" ? "高" : "低",
options: ["低", "高"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
}[value] || "0"
)
},
},
{
type: "videocfg_particle_detail",
title: "粒子细节",
value: ["低", "中", "高", "非常高"][parseInt(video.videocfg_particle_detail, 10)] || "低",
options: ["低", "中", "高", "非常高"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
: "2",
: "3",
}[value] || "低"
)
},
},
{
type: "videocfg_ao_detail",
title: "环境光遮蔽",
value: ["已禁用", "中", "高"][parseInt(video.videocfg_ao_detail, 10)] || "已禁用",
options: ["已禁用", "中", "高"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
: "2",
}[value] || "已禁用"
)
},
},
{
type: "videocfg_hdr_detail",
title: "高动态范围",
value: video.videocfg_hdr_detail === "-1" ? "品质" : "性能",
options: ["性能", "品质"],
mapping: (value: string) => {
return (
{
: "3",
: "-1",
}[value] || "3"
)
},
},
{
type: "videocfg_fsr_detail",
title: "Fidelity FX 超级分辨率",
value:
["已禁用", "超高品质", "品质", "均衡", "性能"][parseInt(video.videocfg_fsr_detail, 10)] ||
"性能",
options: ["性能", "均衡", "品质", "超高品质", "已禁用"],
mapping: (value: string) => {
return (
{
: "0",
: "1",
: "2",
: "3",
: "4",
}[value] || "已禁用"
)
},
},
]
}
// useEffect(() => {
// setLaunchOpt(tool.state.VideoSettings[tool.state.launchIndex] || "")
// }, [tool.state.launchIndex, tool.state.VideoSettings])
const [vconfig, setVconfig] = useState<VideoConfig>(tool.state.videoSetting)
// 设置对应关系
// TODO Value通过实际数值映射
const videoSettings = [
{ type: "", title: "全屏", value: "全屏", options: ["窗口", "全屏"] },
{ type: "", title: "垂直同步", value: "关闭", options: ["关闭", "开启"] },
{ type: "", title: "低延迟模式", value: "关闭", options: ["关闭", "开启"] },
{ type: "", title: "增强角色对比度", value: "禁用", options: ["禁用", "启用"] },
{ type: "", title: "CMAA2抗锯齿", value: "关闭", options: ["关闭", "开启"] },
{
type: "",
title: "多重采样抗锯齿",
value: "2X MSAA",
options: ["无", "2X MSAA", "4X MSAA", "8X MSAA"],
},
{ type: "", title: "全局阴影效果", value: "低", options: ["低", "中", "高", "非常高"] },
{ type: "", title: "动态阴影", value: "全部", options: ["仅限日光", "全部"] },
{ type: "", title: "模型/贴图细节", value: "中", options: ["低", "中", "高"] },
{
type: "",
title: "贴图过滤模式",
value: "异向 4X",
options: ["双线性", "三线性", "异向 2X", "异向 4X", "异向 8X", "异向 16X"],
},
{ type: "", title: "光影细节", value: "低", options: ["低", "高"] },
{ type: "", title: "粒子细节", value: "低", options: ["低", "中", "高", "非常高"] },
{ type: "", title: "环境光遮蔽", value: "已禁用", options: ["已禁用", "中", "高"] },
{ type: "", title: "高动态范围", value: "性能", options: ["性能", "品质"] },
{
type: "",
title: "Fidelity FX 超级分辨率",
value: "已禁用",
options: ["性能", "均衡", "品质", "超高品质", "已禁用"],
},
]
useEffect(() => {
if (steam.state.steamDirValid && steam.currentUser())
void tool.getVideoConfig(steam.state.steamDir, steam.currentUser()?.steam_id32 || 0)
}, [])
return (
<Tooltip content="功能测试中,尚未实装" showArrow={true} delay={300}>
<Card>
<CardHeader>
<CardIcon>
<SettingConfig />
</CardIcon>
<CardTool>
{/* {tool.state.VideoSettings.map((option, index) => (
<Card>
<CardHeader>
<CardIcon>
<SettingConfig />
</CardIcon>
<CardTool>
{/* {tool.state.VideoSettings.map((option, index) => (
<ToolButton key={index} onClick={() => tool.setLaunchIndex(index)}>
{index + 1}
</ToolButton>
))} */}
{edit && (
{edit && (
<>
<ToolButton onClick={() => setVconfig({ ...vconfig, ...VideoSettingTemplate.low })}>
</ToolButton>
<ToolButton
onClick={() => setVconfig({ ...vconfig, ...VideoSettingTemplate.middle })}
>
</ToolButton>
<ToolButton onClick={() => setVconfig({ ...vconfig, ...VideoSettingTemplate.high })}>
</ToolButton>
<ToolButton
onClick={() => setVconfig({ ...vconfig, ...VideoSettingTemplate.veryhigh })}
>
</ToolButton>
<ToolButton
onClick={() => setVconfig({ ...vconfig, ...VideoSettingTemplate.recommend })}
>
</ToolButton>
<ToolButton
onClick={async () => {
await tool.setVideoConfig(
steam.state.steamDir,
steam.currentUser()?.steam_id32 || 0,
vconfig
)
await tool.getVideoConfig(
steam.state.steamDir,
steam.currentUser()?.steam_id32 || 0
)
setEdit(false)
addToast({ title: "应用设置成功" })
}}
>
<Plus />
</ToolButton>
</>
)}
<ToolButton
onClick={async () => {
if (steam.state.steamDirValid && steam.currentUser())
await tool.getVideoConfig(
steam.state.steamDir,
steam.currentUser()?.steam_id32 || 0
)
setVconfig(tool.state.videoSetting)
setEdit(!edit)
}}
>
{edit ? (
<>
<ToolButton></ToolButton>
<ToolButton></ToolButton>
<ToolButton></ToolButton>
<ToolButton></ToolButton>
<ToolButton></ToolButton>
<ToolButton
onClick={() => {
addToast({ title: "测试中 功能完成后可应用设置到游戏" })
}}
>
<Plus />
</ToolButton>
<CloseSmall />
</>
) : (
<>
<Edit />
</>
)}
<ToolButton onClick={() => setEdit(!edit)}>
{edit ? (
<>
<CloseSmall />
</>
) : (
<>
<Edit />
</>
)}
</ToolButton>
<ToolButton onClick={() => setHide(!hide)}>
{hide ? (
<>
<Up />
</>
) : (
<>
<Down />
</>
)}
</ToolButton>
</CardTool>
</CardHeader>
{!hide && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
</ToolButton>
<ToolButton
onClick={async () => {
if (steam.state.steamDirValid && steam.currentUser()) {
await tool.getVideoConfig(
steam.state.steamDir,
steam.currentUser()?.steam_id32 || 0
)
addToast({ title: "读取成功" })
} else addToast({ title: "请先选择用户", color: "danger" })
}}
>
<CardBody>
<ul className="flex flex-wrap gap-3 mt-1">
<li className="flex flex-col gap-1.5">
<span className="ml-2"></span>
<span className="flex gap-3">
<NumberInput
aria-label="width"
value={tool.state.videoSetting.width}
onValueChange={(value) => {
tool.setVideoSetting({
...tool.state.videoSetting,
width: value,
})
}}
radius="full"
step={10}
className="max-w-28"
classNames={{ inputWrapper: "h-10" }}
/>
<NumberInput
aria-label="height"
value={tool.state.videoSetting.height}
onValueChange={(value) => {
tool.setVideoSetting({
...tool.state.videoSetting,
height: value,
})
}}
radius="full"
step={10}
className="max-w-28"
classNames={{ inputWrapper: "h-10" }}
/>
</span>
</ToolButton>
<ToolButton onClick={() => setHide(!hide)}>
{hide ? (
<>
<Up />
</>
) : (
<>
<Down />
</>
)}
</ToolButton>
</CardTool>
</CardHeader>
{!hide && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
<CardBody>
<ul className="flex flex-wrap gap-3 mt-1">
<li className="flex flex-col gap-1.5">
<span className="ml-2"></span>
<span className="flex gap-3">
<NumberInput
aria-label="width"
value={parseInt(
edit ? vconfig.defaultres : tool.state.videoSetting.defaultres,
10
)}
onValueChange={(value) => {
const _ = edit
? setVconfig({
...vconfig,
defaultres: value.toString(),
})
: tool.setVideoSetting({
...tool.state.videoSetting,
defaultres: value.toString(),
})
}}
radius="full"
step={10}
className="max-w-28"
classNames={{ inputWrapper: "h-10" }}
/>
<NumberInput
aria-label="height"
value={parseInt(edit ? vconfig.defaultresheight : tool.state.videoSetting.defaultresheight, 10)}
onValueChange={(value) => {
const _ = edit
? setVconfig({
...vconfig,
defaultresheight: value.toString(),
})
: tool.setVideoSetting({
...tool.state.videoSetting,
defaultresheight: value.toString(),
})
}}
radius="full"
step={10}
className="max-w-28"
classNames={{ inputWrapper: "h-10" }}
/>
</span>
</li>
{videoSettings(edit ? vconfig : tool.state.videoSetting).map((vid, index) => (
<li className="flex flex-col gap-1.5" key={index}>
<span className="ml-2">{vid.title}</span>
<Tabs
size="md"
radius="full"
className="min-w-36"
fullWidth
selectedKey={vid.value}
onSelectionChange={(key) => {
// console.log(vid.type, key)
// 修改 vconfig 名为 vid.type 的 value为 key
const _ =
edit && key
? setVconfig({
...vconfig,
[vid.type]: vid.mapping(key.toString()),
})
: null
}}
>
{vid.options.map((opt, _) => (
<Tab key={opt} title={opt} titleValue={opt} />
))}
</Tabs>
</li>
{videoSettings.map((vid, index) => (
<li className="flex flex-col gap-1.5" key={index}>
<span className="ml-2">{vid.title}</span>
<Tabs
selectedKey={vid.value}
size="md"
radius="full"
className="min-w-36"
fullWidth
>
{vid.options.map((opt, _) => (
<Tab key={opt} title={opt} />
))}
</Tabs>
</li>
))}
</ul>
</CardBody>
</motion.div>
)}
</Card>
</Tooltip>
))}
</ul>
</CardBody>
</motion.div>
)}
</Card>
)
}

View File

@@ -2,7 +2,8 @@ import { store } from "@tauri-store/valtio"
import { useSnapshot } from "valtio"
import { DEFAULT_STORE_CONFIG } from "./config"
import { emit } from "@tauri-apps/api/event"
import { send } from "process"
import { invoke } from "@tauri-apps/api/core"
import VideoSetting from "@/components/cstb/VideoSetting"
interface LaunchOption {
option: string
@@ -10,24 +11,109 @@ interface LaunchOption {
}
export interface VideoSetting {
width: number; // 分辨率宽度
height: number; // 分辨率高度
version: string; // 版本
vendor_id: string; // 供应商ID
device_id: string; // 设备ID
cpu_level: string; // CPU等级
gpu_mem_level: string; // GPU内存等级
gpu_level: string; // GPU等级
knowndevice: string; // 已知设备
defaultres: string; // 默认分辨率宽度
defaultresheight: string; // 默认分辨率高度
refreshrate_numerator: string; // 刷新率分子
refreshrate_denominator: string; // 刷新率分母
fullscreen: string; // 全屏
vsync: string; // 垂直同步
enhanceCharacterContrast: string; // 增强角色对比度
cmaa2AntiAliasing: string; // CMAA2抗锯齿
msaaAntiAliasing: string; // 多重采样抗锯齿
globalShadowQuality: string; // 全局阴影效果
dynamicShadows: string; // 动态阴影
modelTextureDetail: string; // 模型/贴图细节
textureFilteringMode: string; // 贴图过滤模式
lightShadowDetail: string; // 光影细节
particleDetail: string; // 粒子细节
ambientOcclusion: string; // 环境光遮蔽
hdr: string; // 动态范围
fidelityFxSuperResolution: string; // Fidelity FX 超级分辨率
coop_fullscreen: string; // 合作模式全屏
nowindowborder: string; // 无窗口边框
mat_vsync: string; // 垂直同步
fullscreen_min_on_focus_loss: string; // 失去焦点时最小化全屏
high_dpi: string; // 高DPI
auto_config: string; // 自动配置
shaderquality: string; // 光影质量
r_texturefilteringquality: string; // 纹理过滤质量
msaa_samples: string; // 多重采样抗锯齿样本数
r_csgo_cmaa_enable: string; // CMAA抗锯齿启用
videocfg_shadow_quality: string; // 阴影质量
videocfg_dynamic_shadows: string; // 动态阴影
videocfg_texture_detail: string; // 纹理细节
videocfg_particle_detail: string; // 粒子细节
videocfg_ao_detail: string; // 环境光遮蔽细节
videocfg_hdr_detail: string; // 高动态范围细节
videocfg_fsr_detail: string; // FSR细节
monitor_index: string; // 显示器索引
r_low_latency: string; // 低延迟
aspectratiomode: string; // 宽高比模式
}
// 视频设置预设模版
export const VideoSettingTemplate = {
veryhigh: {
shaderquality: "1",
r_texturefilteringquality: "3",
msaa_samples: "8",
r_csgo_cmaa_enable: "0",
videocfg_shadow_quality: "3",
videocfg_dynamic_shadows: "1",
videocfg_texture_detail: "2",
videocfg_particle_detail: "3",
videocfg_ao_detail: "3",
videocfg_hdr_detail: "-1",
videocfg_fsr_detail: "0",
},
high: {
shaderquality: "1",
r_texturefilteringquality: "3",
msaa_samples: "4",
r_csgo_cmaa_enable: "0",
videocfg_shadow_quality: "2",
videocfg_dynamic_shadows: "1",
videocfg_texture_detail: "2",
videocfg_particle_detail: "2",
videocfg_ao_detail: "2",
videocfg_hdr_detail: "-1",
videocfg_fsr_detail: "0",
},
middle: {
shaderquality: "0",
r_texturefilteringquality: "1",
msaa_samples: "2",
r_csgo_cmaa_enable: "0",
videocfg_shadow_quality: "1",
videocfg_dynamic_shadows: "1",
videocfg_texture_detail: "1",
videocfg_particle_detail: "1",
videocfg_ao_detail: "0",
videocfg_fsr_detail: "2",
},
low: {
shaderquality: "0",
r_texturefilteringquality: "0",
msaa_samples: "0",
r_csgo_cmaa_enable: "0",
videocfg_shadow_quality: "0",
videocfg_dynamic_shadows: "0",
videocfg_texture_detail: "0",
videocfg_particle_detail: "0",
videocfg_ao_detail: "0",
videocfg_hdr_detail: "3",
videocfg_fsr_detail: "3",
},
recommend: {
shaderquality: "0",
r_texturefilteringquality: "3",
msaa_samples: "2",
r_csgo_cmaa_enable: "0",
videocfg_shadow_quality: "0",
videocfg_dynamic_shadows: "1",
videocfg_texture_detail: "1",
videocfg_particle_detail: "0",
videocfg_ao_detail: "0",
videocfg_hdr_detail: "3",
videocfg_fsr_detail: "0",
},
}
const defaultValue = {
launchOptions: [
{ option: "-novid -high -freq 144 -fullscreen", name: "" },
@@ -37,8 +123,38 @@ const defaultValue = {
launchIndex: 0,
powerPlan: 0,
videoSetting: {
width: 1920,
height: 1080
version: "15",
vendor_id: "0",
device_id: "0",
cpu_level: "3",
gpu_mem_level: "3",
gpu_level: "3",
knowndevice: "0",
defaultres: "1920",
defaultresheight: "1080",
refreshrate_numerator: "144",
refreshrate_denominator: "1",
fullscreen: "1",
coop_fullscreen: "0",
nowindowborder: "1",
mat_vsync: "0",
fullscreen_min_on_focus_loss: "1",
high_dpi: "0",
auto_config: "2",
shaderquality: "0",
r_texturefilteringquality: "3",
msaa_samples: "2",
r_csgo_cmaa_enable: "0",
videocfg_shadow_quality: "0",
videocfg_dynamic_shadows: "1",
videocfg_texture_detail: "1",
videocfg_particle_detail: "0",
videocfg_ao_detail: "0",
videocfg_hdr_detail: "3",
videocfg_fsr_detail: "0",
monitor_index: "0",
r_low_latency: "1",
aspectratiomode: "0",
} as VideoSetting,
}
@@ -67,6 +183,8 @@ export const useToolStore = () => {
setLaunchIndex,
setPowerPlan,
setVideoSetting,
getVideoConfig,
setVideoConfig,
addLaunchOption,
resetToolStore,
}
@@ -104,6 +222,17 @@ const setVideoSetting = (setting: VideoSetting) => {
toolStore.state.videoSetting = setting
}
const getVideoConfig = async (steam_dir: string, steam_id32: number) => {
const video = await invoke<VideoSetting>("get_cs2_video_config", { steamDir: steam_dir, steamId32: steam_id32 })
// console.log(video)
setVideoSetting(video)
}
const setVideoConfig = async (steam_dir: string, steam_id32: number, video_config: VideoSetting) => {
console.log(video_config.videocfg_hdr_detail)
await invoke("set_cs2_video_config", { steamDir: steam_dir, steamId32: steam_id32, videoConfig: video_config })
}
const addLaunchOption = (option: LaunchOption) => {
// 限制最高10个
if (toolStore.state.launchOptions.length >= 10) {