world-simulator / frontend /src /scene /characterUtils.ts
DeltaZN
feat: Visual improvements & reduce attack range
a3fd468
Raw
History Blame Contribute Delete
3.83 kB
export type RolePalette = {
coat: string;
front: string;
sleeves: string;
legs: string;
accent: string;
hair: string;
};
export type RoleMeta = {
icon: string;
label: string;
color: string;
};
export function roleMeta(role: string): RoleMeta {
switch (role) {
case "guard":
return { icon: "🗡️", label: "Guard", color: "#e2675a" };
case "builder":
return { icon: "🔨", label: "Builder", color: "#5b96f0" };
case "forager":
case "gatherer":
return { icon: "🌾", label: "Gatherer", color: "#6fc46a" };
default:
return { icon: "👤", label: role || "Villager", color: "#d9b35a" };
}
}
export function rolePalette(role: string): RolePalette {
switch (role) {
case "builder":
return {
coat: "#3f78d3",
front: "#76a9f8",
sleeves: "#2f62b5",
legs: "#56687f",
accent: "#f0b34b",
hair: "#4c3a2f",
};
case "forager":
case "gatherer":
return {
coat: "#5faa59",
front: "#91d67b",
sleeves: "#468e45",
legs: "#4d6e4b",
accent: "#e6d16a",
hair: "#5a3c2a",
};
case "guard":
return {
coat: "#b94f45",
front: "#df7d68",
sleeves: "#983d38",
legs: "#4e5361",
accent: "#d7d3c1",
hair: "#312b29",
};
case "scribe":
return {
coat: "#805ec8",
front: "#aa8ce4",
sleeves: "#684aae",
legs: "#5d5574",
accent: "#f0df8a",
hair: "#463245",
};
default:
return {
coat: "#c98f35",
front: "#f0bd59",
sleeves: "#a9772c",
legs: "#635948",
accent: "#7dc95e",
hair: "#6a4028",
};
}
}
export type FaceVariant = {
skin: string;
skinShade: string;
eyes: string;
mouthColor: string;
mouthWidth: number;
brows: boolean;
beard: boolean;
hairStyle: "cap" | "long" | "buzz" | "bald";
};
const SKIN_TONES = [
{ skin: "#f7c89b", shade: "#eab184" },
{ skin: "#e8b483", shade: "#d89e6c" },
{ skin: "#c98a5b", shade: "#b87748" },
{ skin: "#9c6b43", shade: "#8a5a35" },
{ skin: "#7a4f30", shade: "#6a4226" },
] as const;
const EYE_COLORS = ["#23231f", "#37261a", "#2c4a6e", "#3f5d3a"] as const;
const MOUTH_COLORS = ["#7d4a39", "#94584a", "#6b3d30"] as const;
const HAIR_STYLES = ["cap", "long", "buzz", "bald"] as const;
/** Deterministic per-NPC facial features so villagers differ beyond role clothes. */
export function faceVariant(id: string): FaceVariant {
const tone = pickByHash(SKIN_TONES, id, "skin");
return {
skin: tone.skin,
skinShade: tone.shade,
eyes: pickByHash(EYE_COLORS, id, "eyes"),
mouthColor: pickByHash(MOUTH_COLORS, id, "mouth"),
mouthWidth: 0.1 + hashToUnit(`${id}:mouthWidth`) * 0.08,
brows: hashToUnit(`${id}:brows`) > 0.3,
beard: hashToUnit(`${id}:beard`) > 0.65,
hairStyle: pickByHash(HAIR_STYLES, id, "hairStyle"),
};
}
function pickByHash<T>(options: readonly T[], id: string, salt: string): T {
return options[Math.floor(hashToUnit(`${id}:${salt}`) * options.length)];
}
export function hashToUnit(input: string): number {
let hash = 0;
for (let index = 0; index < input.length; index += 1) {
hash = (hash * 31 + input.charCodeAt(index)) % 1000;
}
return hash / 1000;
}
export function dampAngle(
current: number,
target: number,
smoothing: number,
delta: number,
): number {
return current + signedAngleDelta(current, target) * (1 - Math.exp(-smoothing * delta));
}
export function angularDistance(current: number, target: number): number {
return Math.abs(signedAngleDelta(current, target));
}
function signedAngleDelta(current: number, target: number): number {
return Math.atan2(Math.sin(target - current), Math.cos(target - current));
}