feat(HUD): 添加HUD界面资产与组件
- 新增HUD图标、面板和状态指示器资产 - 实现HUD图标组件和错误边界组件 - 重构顶部状态栏和底部导航栏 - 更新路线面板样式和交互 - 添加HUD资产清单和切片脚本 - 移除未使用的资产文件 - 调整API安全配置和生产环境设置
This commit is contained in:
@@ -10,6 +10,55 @@ export interface EquipmentPreviewEntry {
|
||||
isEquipped: boolean;
|
||||
}
|
||||
|
||||
/** Readiness weights for each survival stat (must sum to 100) */
|
||||
const READINESS_WEIGHTS = {
|
||||
life: 34,
|
||||
energy: 28,
|
||||
thirst: 20,
|
||||
hunger: 18,
|
||||
} as const;
|
||||
|
||||
/** Readiness thresholds for tone classification */
|
||||
const READINESS_TONE_THRESHOLD_GOOD = 74;
|
||||
const READINESS_TONE_THRESHOLD_WARN = 48;
|
||||
|
||||
/** Risk tier boundaries */
|
||||
const RISK_LOW_MAX = 0.35;
|
||||
const RISK_MID_MAX = 0.65;
|
||||
|
||||
/** Survivor metric formula constants */
|
||||
const PROTECTION_BASE_ARMOR = 34;
|
||||
const PROTECTION_BASE_UNARMORED = 12;
|
||||
const PROTECTION_STR_FACTOR = 5;
|
||||
const PROTECTION_LIFE_FACTOR = 30;
|
||||
|
||||
const STEALTH_AGI_FACTOR = 8;
|
||||
const STEALTH_PER_FACTOR = 5;
|
||||
const STEALTH_SANITY_FACTOR = 20;
|
||||
|
||||
const MOBILITY_AGI_FACTOR = 7;
|
||||
const MOBILITY_ENERGY_FACTOR = 42;
|
||||
const MOBILITY_LOAD_PENALTY = 18;
|
||||
|
||||
const PLACE_ATMOSPHERE: Record<
|
||||
string,
|
||||
{
|
||||
weather: string;
|
||||
temperature: string;
|
||||
wind: string;
|
||||
}
|
||||
> = {
|
||||
home: { weather: 'Light rain', temperature: '18°C', wind: 'Wind 12 km/h' },
|
||||
roadside: { weather: 'Cloud break', temperature: '16°C', wind: 'Wind 18 km/h' },
|
||||
old_store: { weather: 'Dust drift', temperature: '20°C', wind: 'Wind 9 km/h' },
|
||||
village_market: { weather: 'Dry overcast', temperature: '22°C', wind: 'Wind 7 km/h' },
|
||||
rain_farm: { weather: 'Wet fog', temperature: '14°C', wind: 'Wind 10 km/h' },
|
||||
city_edge: { weather: 'Soot haze', temperature: '19°C', wind: 'Wind 15 km/h' },
|
||||
subway_entrance: { weather: 'Cold draft', temperature: '11°C', wind: 'Wind 6 km/h' },
|
||||
sewer: { weather: 'Toxic mist', temperature: '13°C', wind: 'Air still' },
|
||||
vault_gate: { weather: 'Ash fall', temperature: '9°C', wind: 'Wind 4 km/h' },
|
||||
};
|
||||
|
||||
export function edgeDestination(view: GameView, edge: EdgeView) {
|
||||
return view.map.places.find((place) => place.id === edge.to);
|
||||
}
|
||||
@@ -38,12 +87,16 @@ export function getCurrentPlace(view: GameView) {
|
||||
return view.map.places.find((place) => place.current) ?? null;
|
||||
}
|
||||
|
||||
export function getPlaceAtmosphere(placeId: string) {
|
||||
return PLACE_ATMOSPHERE[placeId] ?? { weather: 'Dry static', temperature: '17°C', wind: 'Wind 10 km/h' };
|
||||
}
|
||||
|
||||
export function getLatestLog(view: GameView) {
|
||||
return view.logs[0] ?? null;
|
||||
}
|
||||
|
||||
export function getVisibleInventory(view: GameView, limit = 6) {
|
||||
return view.player.inventory.slice(0, limit);
|
||||
export function getVisibleRoutes(view: GameView, limit = 3) {
|
||||
return view.map.edges.slice(0, limit);
|
||||
}
|
||||
|
||||
export function getQuickUseItems(view: GameView, limit = 6) {
|
||||
@@ -132,10 +185,10 @@ export function getStatusEffects(view: GameView) {
|
||||
export function getExpeditionStatus(view: GameView) {
|
||||
const { stats, maxStats } = view.player;
|
||||
const readiness = Math.round(
|
||||
(stats.life / maxStats.life) * 34 +
|
||||
(stats.energy / maxStats.energy) * 28 +
|
||||
(stats.thirst / maxStats.thirst) * 20 +
|
||||
(stats.hunger / maxStats.hunger) * 18,
|
||||
(stats.life / maxStats.life) * READINESS_WEIGHTS.life +
|
||||
(stats.energy / maxStats.energy) * READINESS_WEIGHTS.energy +
|
||||
(stats.thirst / maxStats.thirst) * READINESS_WEIGHTS.thirst +
|
||||
(stats.hunger / maxStats.hunger) * READINESS_WEIGHTS.hunger,
|
||||
);
|
||||
const loadPercent = Math.round((view.player.inventoryUsage / view.player.inventoryCapacity) * 100);
|
||||
const threatCount = view.survival.alerts.length;
|
||||
@@ -144,7 +197,7 @@ export function getExpeditionStatus(view: GameView) {
|
||||
readiness,
|
||||
loadPercent,
|
||||
threatCount,
|
||||
tone: readiness >= 74 && threatCount === 0 ? 'good' : readiness >= 48 ? 'warn' : 'danger',
|
||||
tone: readiness >= READINESS_TONE_THRESHOLD_GOOD && threatCount === 0 ? 'good' : readiness >= READINESS_TONE_THRESHOLD_WARN ? 'warn' : 'danger',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -152,17 +205,17 @@ export function getSurvivorMetrics(view: GameView) {
|
||||
const protection = Math.min(
|
||||
100,
|
||||
Math.round(
|
||||
(view.player.equipment.body ? 34 : 12) +
|
||||
view.player.attributes.str * 5 +
|
||||
(view.player.stats.life / view.player.maxStats.life) * 30,
|
||||
(view.player.equipment.body ? PROTECTION_BASE_ARMOR : PROTECTION_BASE_UNARMORED) +
|
||||
view.player.attributes.str * PROTECTION_STR_FACTOR +
|
||||
(view.player.stats.life / view.player.maxStats.life) * PROTECTION_LIFE_FACTOR,
|
||||
),
|
||||
);
|
||||
const stealth = Math.min(
|
||||
100,
|
||||
Math.round(
|
||||
view.player.attributes.agi * 8 +
|
||||
view.player.attributes.per * 5 +
|
||||
(view.player.stats.sanity / view.player.maxStats.sanity) * 20,
|
||||
view.player.attributes.agi * STEALTH_AGI_FACTOR +
|
||||
view.player.attributes.per * STEALTH_PER_FACTOR +
|
||||
(view.player.stats.sanity / view.player.maxStats.sanity) * STEALTH_SANITY_FACTOR,
|
||||
),
|
||||
);
|
||||
const mobility = Math.max(
|
||||
@@ -170,9 +223,9 @@ export function getSurvivorMetrics(view: GameView) {
|
||||
Math.min(
|
||||
100,
|
||||
Math.round(
|
||||
view.player.attributes.agi * 7 +
|
||||
(view.player.stats.energy / view.player.maxStats.energy) * 42 -
|
||||
(view.player.inventoryUsage / view.player.inventoryCapacity) * 18,
|
||||
view.player.attributes.agi * MOBILITY_AGI_FACTOR +
|
||||
(view.player.stats.energy / view.player.maxStats.energy) * MOBILITY_ENERGY_FACTOR -
|
||||
(view.player.inventoryUsage / view.player.inventoryCapacity) * MOBILITY_LOAD_PENALTY,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -271,8 +324,8 @@ export function getLoadoutEntries(view: GameView) {
|
||||
}
|
||||
|
||||
export function getRouteRiskTier(risk: number) {
|
||||
if (risk <= 0.35) return { label: '低风险', tone: 'safe' as const };
|
||||
if (risk <= 0.65) return { label: '中风险', tone: 'warn' as const };
|
||||
if (risk <= RISK_LOW_MAX) return { label: '低风险', tone: 'safe' as const };
|
||||
if (risk <= RISK_MID_MAX) return { label: '中风险', tone: 'warn' as const };
|
||||
return { label: '高风险', tone: 'danger' as const };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user