**Motivations:** - Initialisation du versionning git pour le projet **Root causes:** - N/A (Nouveau projet) **Correctifs:** - N/A **Evolutions:** - Structure initiale du projet - Ajout du .gitignore **Pages affectées:** - Tous les fichiers
87 lines
3.4 KiB
JavaScript
87 lines
3.4 KiB
JavaScript
/**
|
|
* Visitor incidents (thirst, bin full, bench required, animal too far, want photo).
|
|
* Appear more often during wait phases; resolve by click for bonus, or timeout applies penalty.
|
|
*/
|
|
|
|
import { GameConfig } from "./config.js";
|
|
|
|
/** Incident type keys for i18n and display. */
|
|
export const INCIDENT_TYPES = ["thirst", "bin", "bench", "animalFar", "photo"];
|
|
|
|
/** Emoji per incident type for bubble display. */
|
|
export const INCIDENT_EMOJI = { thirst: "💧", bin: "🗑️", bench: "🪑", animalFar: "🦌", photo: "📷" };
|
|
|
|
/**
|
|
* True when player is in a wait phase (truck moving, sale pending validation, etc.).
|
|
* @param {import("./types.js").GameState} state
|
|
* @returns {boolean}
|
|
*/
|
|
export function isInWaitPhase(state) {
|
|
if (state.eggPurchaseTruck && state.eggPurchaseTruck.startAt) return true;
|
|
if (state.truckSale && state.truckSale.startAt) return true;
|
|
const api = state.salesFromApi;
|
|
if (api && api.asBuyerUndelivered && api.asBuyerUndelivered.length > 0) {
|
|
const nowMs = Date.now();
|
|
for (const s of api.asBuyerUndelivered) {
|
|
const validatedAtMs = s.validated_at ? new Date(s.validated_at).getTime() : 0;
|
|
const pending = (s.status === "sold" || s.status === "validated") && validatedAtMs > nowMs;
|
|
if (pending) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Spawn and expire incidents. Call after tickVisitorArrivals.
|
|
* @param {import("./types.js").GameState} state
|
|
* @param {number} nowUnix
|
|
*/
|
|
export function tickVisitorIncidents(state, nowUnix) {
|
|
const arrivals = state.visitorArrivals ?? [];
|
|
const cfg = GameConfig.Visitor;
|
|
const baseChance = cfg?.IncidentChanceBase ?? 0.002;
|
|
const waitMult = cfg?.IncidentChanceWaitMultiplier ?? 4;
|
|
const timeoutSec = cfg?.IncidentTimeoutSeconds ?? 45;
|
|
const penalty = cfg?.IncidentUnresolvedAttractivityPenalty ?? 0.2;
|
|
const inWait = isInWaitPhase(state);
|
|
const chance = inWait ? baseChance * waitMult : baseChance;
|
|
const toRemove = [];
|
|
|
|
for (let i = 0; i < arrivals.length; i++) {
|
|
const v = arrivals[i];
|
|
if (v.incidentType !== null && v.incidentType !== undefined) {
|
|
if (nowUnix - (v.incidentSince ?? nowUnix) >= timeoutSec) {
|
|
state.attractivityBonusFromIncidents = (state.attractivityBonusFromIncidents ?? 0) - penalty;
|
|
toRemove.push(i);
|
|
}
|
|
} else if (Math.random() < chance) {
|
|
v.incidentType = INCIDENT_TYPES[Math.floor(Math.random() * INCIDENT_TYPES.length)];
|
|
v.incidentSince = nowUnix;
|
|
}
|
|
}
|
|
for (let r = toRemove.length - 1; r >= 0; r--) {
|
|
arrivals.splice(toRemove[r], 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve incident for visitor at index: clear incident, add coins and attractivity bonus. Mutates state.
|
|
* @param {import("./types.js").GameState} state
|
|
* @param {number} visitorIndex
|
|
* @returns {boolean} true if an incident was resolved
|
|
*/
|
|
export function resolveIncident(state, visitorIndex) {
|
|
const arrivals = state.visitorArrivals ?? [];
|
|
const v = arrivals[visitorIndex];
|
|
if (!v || (v.incidentType === null || v.incidentType === undefined)) return false;
|
|
const cfg = GameConfig.Visitor;
|
|
const coinBonus = cfg?.IncidentResolveCoinBonus ?? 8;
|
|
const attractivityBonus = cfg?.IncidentResolveAttractivityBonus ?? 0.15;
|
|
state.coins += coinBonus;
|
|
state.attractivityBonusFromIncidents = (state.attractivityBonusFromIncidents ?? 0) + attractivityBonus;
|
|
if (state.stats) state.stats.coinsEarned = (state.stats.coinsEarned ?? 0) + coinBonus;
|
|
delete v.incidentType;
|
|
delete v.incidentSince;
|
|
return true;
|
|
}
|