243 lines
11 KiB
PHP
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();
|