feat(vida): 20 atividades e necessidades no estilo The Sims

Quatorze cenas novas para o bonequinho: cochilar na caminha (cobertor
respirando e Zzz), café (vapor e gole), cozinhar (vira a panqueca no
fogão), regar a plantinha (cresce e dá flor), violão, dançar (globo
espelhado), pular corda, meditar flutuando, telescópio (céu estrelado e
estrela cadente), pipa com rabiola, bolhas de sabão, skate (com ollie),
computador (código aparecendo) e pescar (o peixe pula preso na linha).

Necessidades puxam comportamento, como no The Sims:
- energia < 40%: às vezes cochila; < 20%: dorme de verdade com sono
  respeitado (som comum, movimento e PIR não acordam) até 95%; acordar
  de propósito (toque, painel) encerra o sono pesado;
- alegria abaixo de 60%: o rosto vai ficando emburrado (zanga 0 a 1:
  testa franzida, boca para baixo, olhos bravos); < 30%: mal-humorado;
- diversão < 45%: faz uma atividade de 2 a 5 min escolhida pelo pique
  (cansado prefere calmas, café dá energia); pode ser interrompido;
- durante a atividade a diversão sobe e a energia cai conforme o tipo.

Com o modo vida ligado o celular não adormece mais por 20 s de silêncio:
quem decide o sono é a energia. A cor escolhida nunca muda com a zanga.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 21:28:09 -03:00
co-authored by Claude Opus 5.5
parent d7a17b5683
commit e73897d9c1
11 changed files with 1073 additions and 76 deletions
+25 -8
View File
@@ -212,7 +212,7 @@ function caminhoBoca(tipo) {
// repouso: leve, com as microexpressões (canto que sobe, boca de lado, entreaberta)
const f = FEICAO.atual, w = l * 0.30, mx = cx + f.dx * l * 0.10;
const yE = cy - Math.max(0, f.lado) * l * 0.12, yD = cy - Math.max(0, -f.lado) * l * 0.12;
const curva = l * (0.18 + f.curva * 0.35);
const curva = l * (0.18 + f.curva * 0.35 - ZANGA * 0.3);
let d = "M " + (mx - w) + "," + yE + " Q " + mx + "," + (cy + curva - f.abre * l * 0.12) + " " + (mx + w) + "," + yD;
if (f.abre > 0.04) d += " Q " + mx + "," + (cy + curva + f.abre * l * 0.30) + " " + (mx - w) + "," + yE;
return d;
@@ -224,7 +224,8 @@ function caminhoSobrancelha(i, esc) {
const ab = i === 0 ? r.ab : (r.ab2 === null ? r.ab : r.ab2);
const piscando = 1 - Math.min(1, Math.max(0, ab));
const y = c[1] - metadeOlho(esc) - (EXTRAS.distSobrancelha + 4) * ESC - (f.sob[i] - piscando * 3) * ESC;
const inc = ((r.forma === 'bravo' ? 18 : r.forma === 'triste' ? -14 : 0) + f.inc[i]) * lado * ESC;
const base = r.forma === 'bravo' ? 18 : r.forma === 'triste' ? -14 : ZANGA * 14; // zanga franze a testa
const inc = (base + f.inc[i]) * lado * ESC;
const ele = r.esc > 1.1 ? -15 * ESC : 0;
return 'M ' + (c[0] - largura) + ',' + (y - inc + ele) + ' Q ' + c[0] + ',' + (y - 10 * ESC + ele) + ' ' + (c[0] + largura) + ',' + (y + inc + ele);
}
@@ -401,12 +402,17 @@ const LIVRES = ["feliz", "olhar_esq", "olhar_dir", "olhar_baixo",
const LIVRES_HUMOR = {
feliz: ["feliz", "alegre", "piscadinha", "coracao", "olhar_esq", "olhar_dir"],
tranquilo: ["olhar_esq", "olhar_dir", "feliz", "piscadinha", "olhar_baixo", "timido"],
bravo: ["bravo", "olhar_baixo", "olhar_esq", "olhar_dir", "confuso"],
entediado: ["olhar_esq", "olhar_dir", "olhar_baixo", "pensar", "bocejar", "confuso"],
carente: ["olhar_baixo", "triste", "olhar_esq", "olhar_dir", "timido"],
triste: ["triste", "olhar_baixo", "olhar_baixo"],
cansado: ["olhar_baixo", "piscar2", "bocejar"],
};
let HUMOR = "normal";
/* Modo vida: zanga (0 a 1) deixa o rosto emburrado aos poucos quando a alegria cai; sono profundo =
dormiu de cansaço, e aí som, movimento e câmera não acordam (como na madrugada). */
let ZANGA = 0;
let SONO_PROFUNDO = false;
/* Microexpressões acordado: de vez em quando a boca e as sobrancelhas mexem sozinhas (um canto
da boca sobe, uma sobrancelha arqueia, um suspiro) e voltam devagar ao neutro. O humor do modo
@@ -431,6 +437,7 @@ const MICRO_HUMOR = {
carente: ["preocupado", "preocupado", "suspiro", "meioSorriso"],
triste: ["preocupado", "suspiro", "pensativo"],
cansado: ["suspiro", "suspiro", "ladinho"],
bravo: ["pensativo", "pensativo", "ladinho", "suspiro"],
normal: ["meioSorriso", "arquear", "sorrisinho", "ladinho", "interesse", "pensativo", "suspiro"],
};
function atualizarFeicao(s) {
@@ -495,11 +502,11 @@ function passo(dt) { // avanca a fila; true se ha gesto em
/* Atividade em andamento (rosto-motor.js): para onde olhar e que cara fazer. Atualizado por quadro. */
let ATIV = null;
function atividade(nome) {
function atividade(nome, segundos) {
if (!window.MotorRosto || !window.MotorRosto.atividade) return;
if (nome === "parar" || !nome) { window.MotorRosto.atividade(null); return; }
if (r.dormindo) { r.dormindo = false; animar("surpreso"); }
if (window.MotorRosto.atividade(nome) && nome === "cantar") tocarSom("musica");
if (window.MotorRosto.atividade(nome, segundos) && nome === "cantar") tocarSom("musica");
}
function repouso(t) { // pose calma: olha devagar, "respira"
@@ -526,6 +533,8 @@ function repouso(t) { // pose calma: olha devagar, "respira
if (ATIV.forma) r.forma = ATIV.forma;
if (ATIV.boca) r.boca = ATIV.boca;
if (ATIV.ab) r.ab = ATIV.ab;
} else if (ZANGA >= 0.7) { // alegria caindo: emburrado (a cor escolhida fica)
r.forma = "bravo";
}
}
@@ -634,7 +643,13 @@ function trata(ordem) {
} else if (ordem.indexOf("ritmo ") === 0) {
PERFIL = PERFIS[ordem.slice(6).trim()] || PERFIS.normal;
} else if (ordem.indexOf("atividade ") === 0) {
atividade(ordem.slice(10).trim());
const [nome, seg] = ordem.slice(10).trim().split(/\s+/); // "atividade bola 180"
atividade(nome, parseFloat(seg) || 0);
} else if (ordem.indexOf("zanga ") === 0) {
const z = parseFloat(ordem.slice(6));
if (!isNaN(z)) ZANGA = Math.max(0, Math.min(1, z));
} else if (ordem === "sono profundo" || ordem === "sono leve") {
SONO_PROFUNDO = ordem === "sono profundo";
} else if (ordem === "dormir" || ordem === "sono") {
r.dormindo = true;
atividade(null);
@@ -790,6 +805,7 @@ const MIC = {
/* Acordar com som: precisa de N barulhos em 1 minuto (ajuste "Sons para acordar"). Na madrugada
silenciosa só barulho ALTO conta, e movimento/câmera não acordam (alguém se mexendo na cama). */
const DESPERTAR = { sons: 3, noite: true, ini: 23, fim: 7 };
function sonoRespeitado() { return madrugada() || SONO_PROFUNDO; }
function madrugada() {
if (!DESPERTAR.noite || DESPERTAR.ini === DESPERTAR.fim) return false;
const h = new Date().getHours();
@@ -855,7 +871,7 @@ function som(nivel) {
if (r.dormindo) {
const precisa = Math.max(1, Math.round(DESPERTAR.sons * PERFIL.acordarSom));
if (contar(CONTA.somAcordar, madrugada() ? alto : temSom, agora, precisa, "ouvido: barulho")) { // acordou
if (contar(CONTA.somAcordar, sonoRespeitado() ? alto : temSom, agora, precisa, "ouvido: barulho")) { // acordou
r.dormindo = false;
animar("surpreso");
MIC.proxSurpresa = agora + MIC.cooldown;
@@ -878,6 +894,7 @@ function checarSilencio(agora) {
if (!MIC.ativo) return; // telinha do Pi: sem ouvido
if (agora - MIC.visto > 2) return; // o ouvido parou de mandar dado
if (r.dormindo) return;
if (HUMOR !== "normal") return; // modo vida ligado: quem manda dormir é a energia
// movimento tambem conta como "tem alguem aqui": nao adormece com gente passando
const atividade = Math.max(MIC.ultimoSom, MOV.ativo ? MOV.ultimoMov : 0);
if (agora - atividade >= MIC.segSilencio) {
@@ -949,7 +966,7 @@ function rastrear(t) {
MOV.olharY+=((t.y-.5)*2*MOV.olharMax*.6-MOV.olharY)*.65;
if(t.atividade===true) {
MOV.ultimoMov=agora;
if(r.dormindo && !madrugada() && PERFIL !== PERFIS.calmo) { r.dormindo=false;animar('surpreso'); }
if(r.dormindo && !sonoRespeitado() && PERFIL !== PERFIS.calmo) { r.dormindo=false;animar('surpreso'); }
}
}
function movimento(vel, cx, cy) {
@@ -981,7 +998,7 @@ function movimento(vel, cx, cy) {
const forte = v >= limiar(MOV.limiarForte);
if (r.dormindo) {
// acordou: algo se mexeu na frente (de dia; calmo precisa de vários movimentos)
if (!madrugada() && contar(CONTA.movAcordar, mexeu, agora, PERFIL.acordarMov, "presenca: movimento")) {
if (!sonoRespeitado() && contar(CONTA.movAcordar, mexeu, agora, PERFIL.acordarMov, "presenca: movimento")) {
r.dormindo = false;
animar("surpreso");
MOV.proxSurpresa = agora + MOV.cooldown;