Files
rangerhq-buddy/inc/state.php
T
ranger 8c38d38a3a feat: add winking expression (v0.1.1)
Buddy now has a fourth mood tone — wink — with one eye closed, an
asymmetric smirk, and rosier cheeks. Renders as a small variant
inside the existing inline-SVG sprite (still zero image files, no
new assets). When overall mood is >= 75, there's a 30% chance on
each page render that the wink replaces the standard happy face —
gives the pet a touch of unpredictable personality.

Why this commit exists: v0.1.0 had three tones (happy / neutral /
sad). Adding wink is the smallest possible demo that the SVG
expression engine is properly extensible — every future mood,
state, accessory or species can land via the same pattern. ~20
lines of PHP, ~2 lines of CSS, no bundle weight, no dependencies.

CHANGES
- inc/sprite.php: wink added to allowed-tones list. Left eye
  renders as a closed-eye arc instead of the open circle. Mouth
  shifts to an asymmetric smirk. Cheek opacity 0.55 → 0.75 for
  extra cheekiness.
- inc/state.php: buddy_mood_label() returns wink ~30% of the time
  when mood >= 75.
- assets/css/buddy.css: new .buddy-widget__mood--wink and
  .buddy-main__mood--wink rules — warm amber pill.
- About-page version-history leads with v0.1.1; v0.1.0 demoted.

VERSION
- buddy.php header 0.1.0 → 0.1.1
- BUDDY_VERSION constant 0.1.0 → 0.1.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 10:36:51 +01:00

123 lines
4.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Buddy state — persistence for the pet's stats and identity.
*
* Storage: per-user, in `wp_usermeta` under key `buddy_state`. A single
* JSON-shaped associative array. Picked user_meta over site-wide
* options so multiple admins on the same site each get their own pet,
* matching the per-user mental model.
*
* Stats range 0100. Higher = better. Decay happens via WP-cron later
* (Phase C); for Phase A the stats just persist as last set.
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
const BUDDY_META_KEY = 'buddy_state';
/**
* Default state for a freshly-adopted pet. Used the first time a user
* visits a Buddy-rendering page.
*/
function buddy_default_state() {
return array(
'name' => __( 'Buddy', 'buddy' ),
'species' => 'default',
'hunger' => 80,
'happiness' => 80,
'health' => 90,
'energy' => 70,
'born_at' => time(),
'last_tick' => time(),
);
}
/**
* Fetch the current user's Buddy state, falling back to defaults and
* persisting them on first read so the pet has a "birthday" timestamp.
*
* @param int $user_id Optional. Defaults to current user.
* @return array { name, species, hunger, happiness, health, energy, born_at, last_tick }
*/
function buddy_get_state( $user_id = 0 ) {
$user_id = $user_id ? (int) $user_id : get_current_user_id();
if ( ! $user_id ) { return buddy_default_state(); }
$state = get_user_meta( $user_id, BUDDY_META_KEY, true );
if ( ! is_array( $state ) || empty( $state ) ) {
$state = buddy_default_state();
update_user_meta( $user_id, BUDDY_META_KEY, $state );
}
// Merge against defaults so newly-added keys appear with sensible
// values for users adopted under earlier versions.
return array_merge( buddy_default_state(), $state );
}
/**
* Persist a partial state update for the current user. Unknown keys
* are silently dropped; values are clamped to valid ranges.
*
* @param array $patch Keys: name (str), species (str), hunger/happiness/health/energy (int 0100).
* @return array The full updated state.
*/
function buddy_update_state( array $patch, $user_id = 0 ) {
$user_id = $user_id ? (int) $user_id : get_current_user_id();
if ( ! $user_id ) { return buddy_default_state(); }
$state = buddy_get_state( $user_id );
$stat_keys = array( 'hunger', 'happiness', 'health', 'energy' );
foreach ( $stat_keys as $k ) {
if ( array_key_exists( $k, $patch ) ) {
$state[ $k ] = max( 0, min( 100, (int) $patch[ $k ] ) );
}
}
if ( array_key_exists( 'name', $patch ) ) {
$name = sanitize_text_field( (string) $patch['name'] );
if ( $name !== '' && mb_strlen( $name ) <= 32 ) {
$state['name'] = $name;
}
}
if ( array_key_exists( 'species', $patch ) ) {
$allowed_species = array( 'default' ); // Phase D will widen this.
$sp = sanitize_key( (string) $patch['species'] );
if ( in_array( $sp, $allowed_species, true ) ) {
$state['species'] = $sp;
}
}
update_user_meta( $user_id, BUDDY_META_KEY, $state );
return $state;
}
/**
* Average of the four stats — used as a single "mood" indicator for
* the dashboard widget. Returns 0100.
*/
function buddy_overall_mood( array $state ) {
$sum = (int) $state['hunger'] + (int) $state['happiness'] + (int) $state['health'] + (int) $state['energy'];
return (int) round( $sum / 4 );
}
/**
* Pick a one-word status label + sprite tone based on overall mood.
* Pure cosmetic, used in the dashboard widget header and the main
* admin page.
*
* Easter-egg: when Buddy is genuinely happy (mood >= 75) there's a
* ~30% chance per page-render of returning the "Cheeky" wink tone
* instead of the standard happy face. Gives the pet a touch of
* personality — refresh the page enough and you'll catch the wink.
*/
function buddy_mood_label( $mood_score ) {
if ( $mood_score >= 75 && mt_rand( 1, 100 ) <= 30 ) {
return array( 'label' => __( 'Cheeky 😉', 'buddy' ), 'tone' => 'wink' );
}
if ( $mood_score >= 80 ) { return array( 'label' => __( 'Thriving', 'buddy' ), 'tone' => 'happy' ); }
if ( $mood_score >= 60 ) { return array( 'label' => __( 'Content', 'buddy' ), 'tone' => 'happy' ); }
if ( $mood_score >= 40 ) { return array( 'label' => __( 'Okay', 'buddy' ), 'tone' => 'neutral' ); }
if ( $mood_score >= 20 ) { return array( 'label' => __( 'Hungry', 'buddy' ), 'tone' => 'sad' ); }
return array( 'label' => __( 'Distressed', 'buddy' ), 'tone' => 'sad' );
}