@@ -353,7 +427,7 @@
let face;
if (location.hash.length <= 1) {
- face = faces.generate();
+ face = faces.generate({ aging: { enabled: true } });
} else {
try {
face = JSON.parse(atob(location.hash.slice(1)));
} catch (error) {
console.error(error);
- face = faces.generate();
+ face = faces.generate({ aging: { enabled: true } });
}
}
@@ -904,13 +978,13 @@
Array.from(document.getElementsByClassName("random-attribute")).forEach(
(elem) => {
if (!elem.checked) {
- const attr = elem.id.split("-").slice(1);
- if (attr.length === 1) newFace[attr[0]] = oldFace[attr[0]];
- else if (!isNaN(parseInt(attr[1]))) {
- const idx = parseInt(attr[1]);
- newFace[attr[0]][idx] = oldFace[attr[0]][idx];
- } else if (attr.length === 2)
- newFace[attr[0]][attr[1]] = oldFace[attr[0]][attr[1]];
+ const parts = elem.id.split("-").slice(1);
+ if (parts.length === 1) newFace[parts[0]] = oldFace[parts[0]];
+ else if (!isNaN(parseInt(parts[1]))) {
+ const idx = parseInt(parts[1]);
+ newFace[parts[0]][idx] = oldFace[parts[0]][idx];
+ } else if (parts.length === 2)
+ newFace[parts[0]][parts[1]] = oldFace[parts[0]][parts[1]];
}
}
);
@@ -929,16 +1003,27 @@
};
const initializeSelectOptions = () => {
+ let selectElement, optionElement;
for (const feature of Object.keys(faces.svgsIndex)) {
const options = faces.svgsIndex[feature];
- const selectElement = document.getElementById(`${feature}-id`);
+ selectElement = document.getElementById(`${feature}-id`);
for (const option of options) {
- const optionElement = document.createElement("option");
+ optionElement = document.createElement("option");
optionElement.value = option;
optionElement.text = option;
selectElement.add(optionElement, null);
}
}
+ selectElement = document.getElementById("aging-enabled");
+ optionElement = document.createElement("option");
+ optionElement.selected = true;
+ optionElement.value = "true";
+ optionElement.text = "Enabled";
+ selectElement.add(optionElement, null);
+ optionElement = document.createElement("option");
+ optionElement.value = "false";
+ optionElement.text = "Disabled";
+ selectElement.add(optionElement, null);
};
const isValue = (obj) =>
@@ -975,6 +1060,12 @@
if (typeof oldValue === "number") {
return parseFloat(event.target.value);
}
+ if (
+ typeof oldValue === "boolean" &&
+ typeof event.target.value !== "boolean"
+ ) {
+ return event.target.value === "true";
+ }
if (typeof oldValue === "boolean") {
return event.target.checked;
}
@@ -1008,8 +1099,10 @@
}
document.getElementById("randomize").addEventListener("click", () => {
- const oldFace = face;
- face = randomizeFace(oldFace, faces.generate());
+ face = randomizeFace(
+ face,
+ faces.generate({ aging: { enabled: face.aging.enabled } })
+ );
initializeFormValues();
updateDisplay();
});
diff --git a/src/display.ts b/src/display.ts
index f5721c3..eadca99 100644
--- a/src/display.ts
+++ b/src/display.ts
@@ -97,11 +97,93 @@ type FeatureInfo = {
scaleFatness?: true;
};
+const hashCode = (str) => {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ const character = str.charCodeAt(i);
+ hash = (hash << 5) - hash + character;
+ hash = hash & hash; // Convert to 32bit integer
+ }
+ return Math.abs(hash);
+};
+
+const deterministicRandom = (face: Face) => {
+ const hash =
+ hashCode(face.body.id) +
+ hashCode(face.body.color) +
+ hashCode(face.head.id) +
+ hashCode("" + face.fatness) +
+ hashCode(face.hair.id) +
+ hashCode(face.hair.color);
+ return (hash % 1000) / 1000;
+};
+
+const ageHair = (hairId: String) => {
+ switch (hairId) {
+ case "afro":
+ return "short";
+ case "afro2":
+ return "short";
+ case "blowoutFade":
+ return "cropFade2";
+ case "cornrows":
+ return "short-fade";
+ case "curly3":
+ return "short3";
+ case "dreads":
+ return "short-fade";
+ case "emo":
+ return "short2";
+ case "faux-hawk":
+ return "short3";
+ case "fauxhawk-fade":
+ return "short-fade";
+ case "high":
+ return "short";
+ case "juice":
+ return "short2";
+ case "longHair":
+ return "short-fade";
+ case "shaggy2":
+ return "shaggy1";
+ case "short-bald":
+ return "short-bald";
+ case "shortBangs":
+ return "short-bald";
+ case "spike":
+ return "short";
+ case "spike2":
+ return "short";
+ case "spike3":
+ return "short";
+ case "spike4":
+ return "short";
+ case "tall-fade":
+ return "crop-fade";
+ default:
+ return "short-fade";
+ }
+};
+
const drawFeature = (svg: SVGSVGElement, face: Face, info: FeatureInfo) => {
- const feature = face[info.name];
+ const feature = Object.assign({}, face[info.name]);
if (!feature || !svgs[info.name]) {
return;
}
+ if (face.aging && face.aging.enabled) {
+ if (
+ info.name === "hair" &&
+ face.aging.age + face.aging.maturity / 2 >= 30 &&
+ deterministicRandom(face) < 0.5
+ )
+ feature.id = ageHair(feature.id);
+ else if (
+ info.name === "hairBg" &&
+ face.aging.age + face.aging.maturity / 2 >= 27 &&
+ deterministicRandom(face) < 0.75
+ )
+ feature.id = "none";
+ }
// @ts-ignore
let featureSVGString = svgs[info.name][feature.id];
@@ -111,14 +193,15 @@ const drawFeature = (svg: SVGSVGElement, face: Face, info: FeatureInfo) => {
// @ts-ignore
if (feature.shave) {
+ let shave;
+ if (face.aging && face.aging.enabled)
+ if (face.aging.age + face.aging.maturity > 23) shave = feature.shave;
+ else shave = "rgba(0,0,0,0)";
+ else shave = feature.shave;
// @ts-ignore
- featureSVGString = featureSVGString.replace("$[faceShave]", feature.shave);
- }
-
- // @ts-ignore
- if (feature.shave) {
+ featureSVGString = featureSVGString.replace("$[faceShave]", shave);
// @ts-ignore
- featureSVGString = featureSVGString.replace("$[headShave]", feature.shave);
+ featureSVGString = featureSVGString.replace("$[headShave]", shave);
}
featureSVGString = featureSVGString.replace("$[skinColor]", face.body.color);
@@ -311,6 +394,33 @@ const display = (
];
for (const info of featureInfos) {
+ if (face.aging && face.aging.enabled) {
+ if (
+ info.name === "miscLine" &&
+ face.aging.age + face.aging.maturity >= 22 &&
+ face.miscLine.id.startsWith("freckles")
+ )
+ continue;
+ if (
+ info.name === "miscLine" &&
+ face.aging.age + face.aging.maturity < 25 &&
+ face.miscLine.id.startsWith("chin")
+ )
+ continue;
+ if (
+ info.name === "smileLine" &&
+ face.aging.age + face.aging.maturity < 27
+ )
+ continue;
+ if (info.name === "eyeLine" && face.aging.age + face.aging.maturity < 30)
+ continue;
+ if (
+ info.name === "miscLine" &&
+ face.aging.age + face.aging.maturity < 34 &&
+ face.miscLine.id.startsWith("forehead")
+ )
+ continue;
+ }
drawFeature(svg, face, info);
}
};
diff --git a/src/generate.ts b/src/generate.ts
index 5847a31..eff6656 100644
--- a/src/generate.ts
+++ b/src/generate.ts
@@ -1,9 +1,13 @@
import override, { Overrides } from "./override";
import svgsIndex from "./svgs-index";
-const getID = (type: string): string => {
- // @ts-ignore
- return svgsIndex[type][Math.floor(Math.random() * svgsIndex[type].length)];
+const getID = (type: string, canBeNone?: true): string => {
+ let id;
+ do {
+ // @ts-ignore
+ id = svgsIndex[type][Math.floor(Math.random() * svgsIndex[type].length)];
+ } while (!canBeNone && id == "none");
+ return id;
};
type Race = "asian" | "black" | "brown" | "white";
@@ -74,7 +78,22 @@ const generate = (overrides?: Overrides, options?: { race?: Race }) => {
palette.hair[Math.floor(Math.random() * palette.hair.length)];
const isFlipped = Math.random() < 0.5;
+ let aging;
+ if (overrides && overrides.aging) {
+ aging = overrides.aging;
+ // @ts-ignore
+ if (!overrides.aging.age) aging.age = Math.floor(Math.random() * 16 + 19);
+ // @ts-ignore
+ if (!overrides.aging.maturity)
+ aging.maturity = Math.floor(Math.random() * 5 - 2);
+ } else
+ aging = {
+ enabled: true,
+ age: Math.floor(Math.random() * 16 + 19),
+ maturity: Math.floor(Math.random() * 5 - 1),
+ };
const face = {
+ aging: aging,
fatness: roundTwoDecimals(Math.random()),
teamColors: defaultTeamColors,
hairBg: {
@@ -98,14 +117,26 @@ const generate = (overrides?: Overrides, options?: { race?: Race }) => {
})`,
},
eyeLine: {
- id: Math.random() < 0.75 ? getID("eyeLine") : "none",
+ // @ts-ignore
+ id:
+ aging.enabled || Math.random() < 0.75
+ ? getID("eyeLine", !aging.enabled)
+ : "none",
},
smileLine: {
- id: Math.random() < 0.75 ? getID("smileLine") : "none",
+ // @ts-ignore
+ id:
+ aging.enabled || Math.random() < 0.75
+ ? getID("smileLine", !aging.enabled)
+ : "none",
size: roundTwoDecimals(0.25 + 2 * Math.random()),
},
miscLine: {
- id: Math.random() < 0.5 ? getID("miscLine") : "none",
+ // @ts-ignore
+ id:
+ aging.enabled || Math.random() < 0.5
+ ? getID("miscLine", !aging.enabled)
+ : "none",
},
facialHair: {
id: Math.random() < 0.5 ? getID("facialHair") : "none",
diff --git a/tools/process-svgs.js b/tools/process-svgs.js
index 89c5b30..de26288 100644
--- a/tools/process-svgs.js
+++ b/tools/process-svgs.js
@@ -15,11 +15,13 @@ const processSVGs = async () => {
const svgs = {};
for (const folder of folders) {
+ if (folder === ".DS_Store") continue;
svgs[folder] = {};
const subfolder = path.join(svgFolder, folder);
const files = fs.readdirSync(subfolder);
for (const file of files) {
+ if (file === ".DS_Store") continue;
const key = path.basename(file, ".svg");
const contents = fs.readFileSync(path.join(subfolder, file), "utf8");
@@ -38,7 +40,7 @@ const processSVGs = async () => {
);
const svgsIndex = {
- ...svgs
+ ...svgs,
};
for (const key of Object.keys(svgsIndex)) {
svgsIndex[key] = Object.keys(svgsIndex[key]);