Files
2026-09-08 18:21:10 +01:00

243 lines
11 KiB
PHP

<?php
require_once __DIR__ . '/lib/bootstrap.php';
$me = require_login();
// ensure the MUD tables + world exist even on a long-running install
db(); // triggers migrate + seed_mud
$c = mud_char_for($me['id']);
if (!$c) {
// first time: pick a name + class
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['cname'])) {
require_csrf();
$nm = trim(substr($_POST['cname'], 0, 16));
$cls = $_POST['class'] ?? 'fighter';
if (preg_match('/^[\w ]{2,16}$/', $nm)) {
$c = mud_create_char($me, $nm, $cls);
mud_echo("$nm enters the village.", 1);
} else {
$err = "Name must be 2-16 letters/digits/space.";
}
}
if (!$c) {
page_start('MUD - New Character');
if (!empty($err)) p_err($err);
p_para('Create your adventurer. You can play here on WAP or live on the telnet BBS.');
p_form('/mud.php', [
['name' => 'cname', 'label' => 'Character name', 'maxlength' => 16],
['name' => 'class', 'label' => 'Class', 'type' => 'select',
'options' => ['fighter' => 'Fighter (+ATK/HP)', 'mage' => 'Mage (+ATK)', 'thief' => 'Thief (+DEF/HP)']],
], 'Enter the world', ['csrf' => csrf_token()]);
p_link('/games.php', 'Back to games');
page_end();
exit;
}
}
mud_respawn();
$msg = [];
// ---- command handling ----
if (isset($_GET['cmd'])) {
$raw = strtolower(trim($_GET['cmd']));
$parts = explode(' ', $raw, 2);
$cmd = $parts[0];
$arg = trim($parts[1] ?? ($_GET['arg'] ?? ''));
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['cmd'])) {
require_csrf();
$parts = explode(' ', trim($_POST['cmd']), 2);
$cmd = strtolower($parts[0]);
$arg = $parts[1] ?? '';
} else {
$cmd = ''; $arg = '';
}
switch ($cmd) {
case 'n': case 'north': case 's': case 'south': case 'e': case 'east':
case 'w': case 'west': case 'u': case 'up': case 'd': case 'down':
$dir = $cmd[0];
$room = mud_room($c['room_id']);
if (!empty($room['exits'][$dir])) {
$c['room_id'] = $room['exits'][$dir];
db()->prepare("UPDATE mud_chars SET room_id=?,last_cmd_at=? WHERE id=?")
->execute([$c['room_id'], time(), $c['id']]);
mud_echo("{$c['name']} heads {$dir}.", $c['room_id']);
} else {
$msg[] = "You can't go that way.";
}
break;
case 'look': case 'l':
$msg[] = 'You take in your surroundings.';
break;
case 'k': case 'kill': case 'attack':
if (!$arg) { $msg[] = 'Kill what?'; break; }
$spawns = mud_alive_spawns($c['room_id']);
$hit = null;
foreach ($spawns as $sp) { if (strpos($sp['key_name'], $arg) === 0 || strpos($sp['name'], $arg) !== false) { $hit = $sp; break; } }
if ($hit) $msg = array_merge($msg, mud_fight($hit['id'], $c));
else $msg[] = "There is no '$arg' here to fight.";
break;
case 'take': case 'get':
if (!$arg) { $msg[] = 'Take what?'; break; }
$ground = mud_ground_items($c['room_id']);
$got = null;
foreach ($ground as $g) if (strpos($g['key_name'], $arg) === 0) { $got = $g; break; }
if ($got) {
db()->prepare("DELETE FROM mud_ground WHERE id=?")->execute([$got['id']]);
$inv = json_decode($c['inv'], true); $inv[] = $got['key_name']; $inv = json_encode($inv);
db()->prepare("UPDATE mud_chars SET inv=? WHERE id=?")->execute([$inv, $c['id']]);
$c['inv'] = $inv;
mud_echo("{$c['name']} takes {$got['name']}.", $c['room_id']);
$msg[] = "You take {$got['name']}.";
} else $msg[] = "There is no '$arg' here.";
break;
case 'wear': case 'wield':
if (!$arg) { $msg[] = 'Wear what?'; break; }
$inv = json_decode($c['inv'], true);
$it = null;
foreach ($inv as $k) { if (strpos($k, $arg) === 0) { $it = mud_item($k); break; } }
if (!$it) { $msg[] = "You don't have that."; break; }
if ($it['slot'] === 'weapon') { $c['weapon'] = $it['key_name']; db()->prepare("UPDATE mud_chars SET weapon=? WHERE id=?")->execute([$it['key_name'], $c['id']]); $msg[] = "You wield {$it['name']}."; }
elseif ($it['slot'] === 'armor') { $c['armor'] = $it['key_name']; db()->prepare("UPDATE mud_chars SET armor=? WHERE id=?")->execute([$it['key_name'], $c['id']]); $msg[] = "You don {$it['name']}."; }
else { $msg[] = "You can't wear that."; }
break;
case 'drink': case 'quaff':
if (!$arg) { $msg[] = 'Drink what?'; break; }
$inv = json_decode($c['inv'], true);
$it = null; $idx = null;
foreach ($inv as $i => $k) { if (strpos($k, $arg) === 0) { $it = mud_item($k); $idx = $i; break; } }
if (!$it) { $msg[] = "You don't have that."; break; }
if ($it['slot'] !== 'potion') { $msg[] = "That's not a potion."; break; }
$heal = $it['heal'];
$c['hp'] = min($c['max_hp'], $c['hp'] + $heal);
unset($inv[$idx]); $inv = json_encode(array_values($inv));
db()->prepare("UPDATE mud_chars SET hp=?,inv=? WHERE id=?")->execute([$c['hp'], $inv, $c['id']]);
$c['inv'] = $inv;
$msg[] = "You drink {$it['name']} and recover $heal hp.";
break;
case 'inv': case 'i': case 'inventory':
$inv = json_decode($c['inv'], true);
$msg[] = $inv ? "You carry: " . implode(', ', array_map(fn($k) => mud_item($k)['name'], $inv))
: "Your pack is empty.";
break;
case 'score': case 'stats':
$msg[] = "Level {$c['level']} | HP {$c['hp']}/{$c['max_hp']} | ATK {$c['atk']} | DEF {$c['def']} | XP {$c['xp']} | Gold {$c['gold']} | Bank {$c['bank']} | Kills {$c['kills']} | Deaths {$c['deaths']}";
break;
case 'attack': case 'a':
if (!$arg) { $msg[] = 'Attack who?'; break; }
$v = mud_find_player_in_room($c['room_id'], $arg);
if ($v) {
if ($v['id'] == $c['id']) $msg[] = "You can't attack yourself.";
else $msg = array_merge($msg, mud_pvp($c, $v));
} else {
$here = array_values(array_filter(mud_players_in_room($c['room_id']), fn($p) => $p['id'] != $c['id']));
$who = $here ? implode(', ', array_map(fn($p) => $p['name'], $here)) : 'nobody';
$msg[] = "There is no '$arg' here to fight. Adventurers present: $who.";
}
break;
case 'buy': case 'b':
if (!$arg) { $msg[] = 'Buy what? (e.g. buy steel_sword)'; break; }
$msg = array_merge($msg, mud_buy($c, $arg));
break;
case 'bank':
if (!$arg) { $msg[] = 'bank <amount> to deposit, or bank -<amount> to withdraw.'; break; }
if ($arg[0] === '-') $msg = array_merge($msg, mud_withdraw($c, (int)substr($arg,1)));
else $msg = array_merge($msg, mud_deposit($c, (int)$arg));
break;
case 'tax':
$msg = array_merge($msg, mud_tax($c));
break;
case 'bounty':
$bp = explode(' ', $arg, 2);
if (count($bp) < 2 || !is_numeric($bp[1])) { $msg[] = 'bounty <player> <amount>'; break; }
$v = mud_find_player_in_room($c['room_id'], $bp[0]);
if ($v) {
if ($v['id'] == $c['id']) $msg[] = "You can't bounty yourself.";
else $msg = array_merge($msg, mud_set_bounty($c, $v, (int)$bp[1]));
} else $msg[] = "There is no '$bp[0]' here to bounty.";
break;
case 'board': case 'top':
$lb = mud_leaderboard(10);
$msg[] = 'Adventurers of renown:';
foreach ($lb as $i => $r) {
$cn = $GLOBALS['MUD_CLASSES'][$r['class']]['name'] ?? $r['class'];
$msg[] = sprintf(" %d. %s (%s) L%d G%d K%d/D%d", $i+1, $r['name'], $cn, $r['level'], $r['gold'], $r['kills'], $r['deaths']);
}
break;
case 'help': case 'h': case '':
$msg[] = "Commands: n/s/e/w/u/d (move), look, kill <mob>, attack <player>, take <item>, "
. "wear/wield <item>, drink <potion>, buy <item>, bank <+/->amt, tax, "
. "bounty <player> <amt>, board (leaderboard), inv, score, help. "
. "Also play live on the telnet BBS (port 12300).";
break;
default:
$msg[] = "Unknown command: $cmd (try 'help').";
}
// ---- render room ----
$room = mud_room($c['room_id']);
$spawns = mud_alive_spawns($c['room_id']);
$ground = mud_ground_items($c['room_id']);
$others = mud_players_in_room($c['room_id']);
$ev_st = db()->prepare("SELECT text FROM mud_events WHERE room_id=? ORDER BY id DESC LIMIT 5");
$ev_st->execute([$c['room_id']]);
$events = $ev_st->fetchAll(PDO::FETCH_COLUMN);
page_start('MUD - ' . $c['name']);
if ($msg) foreach ($msg as $m) p_para($m);
p_rule();
p_para($room['name']);
p_para($room['descr']);
if ($spawns) {
p_para('Here:');
foreach ($spawns as $sp) p_para(' ' . $sp['descr'] . ' (' . $sp['hp'] . ' hp)');
}
// other players present
$people = array_filter($others, fn($p) => $p['id'] != $c['id']);
if ($people) {
p_para('Adventurers here:');
foreach ($people as $p) {
$cn = $GLOBALS['MUD_CLASSES'][$p['class']]['name'] ?? $p['class'];
p_para(' ' . $p['name'] . ' (' . $cn . ', L' . $p['level'] . (($p['bounty']>0)?', bounty '.$p['bounty'].'g':'') . ')');
}
}
if ($ground) {
p_para('On the ground:');
foreach ($ground as $g) p_para(' ' . $g['name']);
}
if ($events) {
p_para('You see:');
foreach (array_reverse($events) as $ev) p_para(' ' . $ev);
}
p_rule();
p_para("HP {$c['hp']}/{$c['max_hp']} Lvl {$c['level']} XP {$c['xp']} Gold {$c['gold']} Bank {$c['bank']}");
// movement links + command form
$links = [];
foreach ($room['exits'] as $dir => $to) {
$labels = ['n'=>'North','s'=>'South','e'=>'East','w'=>'West','u'=>'Up','d'=>'Down'];
$links[] = ['/mud.php', $labels[$dir], ['cmd' => $dir]];
}
p_links($links);
// action quick links
$acts = [];
if ($spawns) $acts[] = ['/mud.php', 'Attack', ['cmd' => 'kill', 'arg' => substr($spawns[0]['key_name'],0,3)]];
if ($room['id'] == 9) {
foreach (mud_shop_list() as $it) $acts[] = ['/mud.php', 'Buy '.$it['name'].' ('.$it['price'].'g)', ['cmd' => 'buy', 'arg' => $it['key_name']]];
}
if ($room['id'] == 10) { $acts[] = ['/mud.php', 'Deposit 10g', ['cmd' => 'bank', 'arg' => '10']]; $acts[] = ['/mud.php', 'Withdraw 10g', ['cmd' => 'bank', 'arg' => '-10']]; }
if ($room['id'] == 11) { $acts[] = ['/mud.php', 'Pay the Taxman', ['cmd' => 'tax']]; }
if ($people) $acts[] = ['/mud.php', 'Attack ' . $people[array_key_first($people)]['name'], ['cmd' => 'attack', 'arg' => substr($people[array_key_first($people)]['name'],0,4)]];
$acts[] = ['/mud.php', 'Look', ['cmd' => 'look']];
$acts[] = ['/mud.php', 'Inventory', ['cmd' => 'inv']];
$acts[] = ['/mud.php', 'Score', ['cmd' => 'score']];
$acts[] = ['/mud.php', 'Leaderboard', ['cmd' => 'board']];
$acts[] = ['/mud.php', 'Help', ['cmd' => 'help']];
p_links($acts);
p_para('Or type a command:');
p_form('/mud.php', [
['name' => 'cmd', 'label' => '', 'maxlength' => 48],
], 'Go', ['csrf' => csrf_token()]);
p_link('/games.php', 'Back to games');
page_end();