Files
rangerhq-buddy/inc/state.php
T
ranger cba0df9439 chore: wp.org submission prep — v0.1.3
Rebrand to RangerHQ Buddy and prepare for the WordPress.org Plugin
Directory. Same workflow as rangerhq-radio v0.7.0 → v0.7.5.

Changes:
- Plugin Name: Buddy → RangerHQ Buddy (matches family naming)
- Plugin URI: icanhelp.ie/buddy → davidtkeane.com/rangerhq-buddy
- Author URI: rangersmyth.xyz → davidtkeane.com
- Text Domain: buddy → rangerhq-buddy (62 occurrences across 8 PHP files)
- Add LICENSE file (full GPL v2 text from gnu.org)
- Add wp.org-format readme.txt with all 8 required headers
- Remove inc/updater.php (self-hosted Gitea updater forbidden for
  wp.org-hosted plugins per the rangerhq-radio v0.7.3 walkback)
- Replace mt_rand with wp_rand in inc/state.php for better RNG
- Add 5 translator comments for printf-style i18n placeholders
- Wrap 2 dashboard-widget.php printf placeholders in (int) casts
- Add tests/ to .gitignore (PCP reports are local-only)

PCP audit: 85 issues → ~1 (the .gitignore file itself, stripped from
the submission zip).
Plugin Check Namer Tool: "Generally Allowable" verdict on both name
and slug.
Plugin URI https://davidtkeane.com/rangerhq-buddy/ returns HTTP 200.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 01:56:26 +01:00

124 lines
4.6 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', 'rangerhq-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
* ~5% chance per page-render of returning the "Cheeky" wink tone
* instead of the standard happy face. Rare enough to feel magical
* rather than disturbing — refresh the page a few dozen times and
* you'll catch it.
*/
function buddy_mood_label( $mood_score ) {
if ( $mood_score >= 75 && wp_rand( 1, 100 ) <= 5 ) {
return array( 'label' => __( 'Cheeky 😉', 'rangerhq-buddy' ), 'tone' => 'wink' );
}
if ( $mood_score >= 80 ) { return array( 'label' => __( 'Thriving', 'rangerhq-buddy' ), 'tone' => 'happy' ); }
if ( $mood_score >= 60 ) { return array( 'label' => __( 'Content', 'rangerhq-buddy' ), 'tone' => 'happy' ); }
if ( $mood_score >= 40 ) { return array( 'label' => __( 'Okay', 'rangerhq-buddy' ), 'tone' => 'neutral' ); }
if ( $mood_score >= 20 ) { return array( 'label' => __( 'Hungry', 'rangerhq-buddy' ), 'tone' => 'sad' ); }
return array( 'label' => __( 'Distressed', 'rangerhq-buddy' ), 'tone' => 'sad' );
}