Valithor Obsidion 4d5c345528 functions.php
2025-08-14 15:47:25 -04:00

558 lines
23 KiB
PHP

<?php
declare(strict_types=1);
/*
* This file is a part of the Dragon-Knight project.
*
* Copyright (c) 2024-present Sharkk
*
* This file is subject to the MIT license that is bundled
* with this source code in the LICENSE.md file.
*/
namespace DragonKnight\Actions;
use DragonKnight\Router;
class Fight
{
public static function register_routes(Router $r): Router
{
$r->form('/fight', 'Fights\fight');
$r->get('/victory', 'Fights\victory');
$r->form('/drop', 'Fights\drop');
$r->get('/dead', 'Fights\dead');
return $r;
}
/**
* One big long public static function that determines the outcome of the fight.
*/
public static function fight()
{
if (user()->currentaction !== 'Fighting') {
exit('Cheat attempt detected.<br><br>Get a life, loser.');
}
$page = ['magiclist' => '', 'yourturn' => '', 'monsterturn' => '', 'monsterhp' => '', 'command' => ''];
$playerisdead = 0;
// Generate spell list
$user_spells = user()->spells();
if (! empty($user_spells)) {
$page['magiclist'] = '<select name="userspell">';
foreach ($user_spells as $spell) {
$page['magiclist'] .= "<option value=\"{$spell['id']}\">{$spell['name']}</option>\n";
}
$page['magiclist'] .= '</select> <input type="submit" name="spell" value="Spell"><br><br>';
}
// Determine initial combat parameters
$chancetoswingfirst = rand(1, 10) + (int) ceil(sqrt(user()->dexterity));
if (user()->currentfight === 1) {
$maxlevel = (int) floor(max(abs(user()->latitude) + 5, abs(user()->longitude) + 5) / 5);
$minlevel = max(1, $maxlevel - 2);
$monster = db()->query('SELECT * FROM monsters WHERE level >= ? AND level <= ? ORDER BY RANDOM() LIMIT 1;', [
$minlevel, $maxlevel,
])->fetchArray(SQLITE3_ASSOC);
user()->currentmonster = $monster['id'];
user()->currentmonsterhp = rand((int) (($monster['maxhp'] / 5) * 4), $monster['maxhp']);
user()->currentmonstersleep = 0;
user()->currentmonsterimmune = $monster['immune'];
$chancetoswingfirst = ($chancetoswingfirst > (rand(1, 7) + (int) ceil(sqrt($monster['maxdam'])))) ? 1 : 0;
}
// Get monster statistics
$monster = get_monster(user()->currentmonster);
$page['monstername'] = $monster['name'];
// Run action
if (isset($_POST['run'])) {
$chancetorun = rand(4, 10) + (int) ceil(sqrt(user()->dexterity));
if ($chancetorun <= (rand(1, 5) + (int) ceil(sqrt($monster['maxdam'])))) {
$page['yourturn'] = 'You tried to run away, but were blocked in front!<br><br>';
$page['monsterhp'] = "Monster's HP: ".user()->currentmonsterhp.'<br><br>';
// Monster turn logic (similar to original public static function)
$page['monsterturn'] = self::handleMonsterTurn($userrow, $monster);
user()->currentaction = 'Exploring';
user()->save();
redirect('/');
}
}
// Fight action
if (isset($_POST['fight'])) {
// Player's attack
$min = (int) (user()->attackpower * 0.75);
$max = (int) (user()->attackpower / 3);
$tohit = (int) ceil(mt_rand(min($min, $max), max($min, $max)));
$toexcellent = rand(1, 150);
if ($toexcellent <= sqrt(user()->strength)) {
$tohit *= 2;
$page['yourturn'] .= 'Excellent hit!<br>';
}
$min = (int) ($monster['armor'] * 0.75);
$max = (int) $monster['armor'];
$toblock = (int) ceil(rand(min($min, $max), max($min, $max)) / 3);
$tododge = rand(1, 100);
$monsterdamage = max(1, $tohit - $toblock);
if ($tododge <= sqrt($monster['armor'])) {
$monsterdamage = 0;
$page['yourturn'] .= 'The monster is dodging. No damage has been scored.<br>';
}
if (user()->currentuberdamage != 0) {
$monsterdamage += (int) ceil($monsterdamage * (user()->currentuberdamage / 100));
}
user()->currentmonsterhp -= $monsterdamage;
$page['yourturn'] .= "You attack the monster for $monsterdamage damage.<br><br>";
$page['monsterhp'] = "Monster's HP: ".user()->currentmonsterhp.'<br><br>';
// Check for monster defeat
if (user()->currentmonsterhp <= 0) {
user()->currentmonsterhp = 0;
user()->save();
redirect('/victory');
}
// Monster's turn
$page['monsterturn'] = self::handleMonsterTurn($userrow, $monster);
}
// Spell action
if (isset($_POST['spell'])) {
$pickedspell = $_POST['userspell'];
if ($pickedspell == 0) {
return 'You must select a spell first. Please go back and try again.';
}
$newspellrow = get_spell($pickedspell);
$spell = in_array($pickedspell, explode(',', user()->spells));
if (! $spell) {
return 'You have not yet learned this spell. Please go back and try again.';
}
if (user()->currentmp < $newspellrow['mp']) {
return 'You do not have enough Magic Points to cast this spell. Please go back and try again.';
}
// Spell type handling (similar to original public static function)
$page['yourturn'] = self::handleSpellCast($userrow, $newspellrow);
$page['monsterhp'] = "Monster's HP: ".user()->currentmonsterhp.'<br><br>';
// Check for monster defeat
if (user()->currentmonsterhp <= 0) {
user()->currentmonsterhp = 0;
user()->save();
redirect('/victory');
}
// Monster's turn
$page['monsterturn'] = self::handleMonsterTurn($userrow, $monster);
}
// Monster's turn if player lost first swing
if (! isset($_POST['run']) && ! isset($_POST['fight']) && ! isset($_POST['spell']) && $chancetoswingfirst == 0) {
$page['yourturn'] = 'The monster attacks before you are ready!<br><br>';
$page['monsterhp'] = "Monster's HP: ".user()->currentmonsterhp.'<br><br>';
$page['monsterturn'] = self::handleMonsterTurn($userrow, $monster);
}
// Prepare command or death message
if ($playerisdead != 1) {
$page['command'] = <<<HTML
Command?<br><br>
<form action="/fight" method="post" hx-post="/fight" hx-target="#middle">
<input type="submit" name="fight" value="Fight"><br><br>
{$page['magiclist']}
<input type="submit" name="run" value="Run"><br><br>
</form>
HTML;
user()->currentfight += 1;
} else {
$page['command'] = <<<HTML
<b>You have died.</b><br><br>
As a consequence, you've lost half of your gold. However, you have been given back a portion of your hit points to continue your journey.<br><br>
You may now continue back to <a href="/" hx-get="/" hx-target="#middle">town</a>, and we hope you fair better next time.
HTML;
}
user()->save();
// Finalize page and display it
$page = render('fight', ['page' => $page]);
return $page;
}
public static function victory()
{
if (user()->currentmonsterhp != 0) {
redirect('/fight');
}
if (user()->currentfight == 0) {
redirect('/');
}
$monsterrow = get_monster(user()->currentmonster);
$min = (int) (($monsterrow['maxexp'] / 6) * 5);
$max = (int) $monsterrow['maxexp'];
$exp = mt_rand(min($min, $max), max($min, $max));
if ($exp < 1) {
$exp = 1;
}
if (user()->expbonus != 0) {
$exp += ceil((user()->expbonus / 100) * $exp);
}
$min = (int) (($monsterrow['maxgold'] / 6) * 5);
$max = (int) $monsterrow['maxgold'];
$gold = mt_rand(min($min, $max), max($min, $max));
if ($gold < 1) {
$gold = 1;
}
if (user()->goldbonus != 0) {
$gold += ceil((user()->goldbonus / 100) * $exp);
}
if (user()->experience + $exp < 16777215) {
$newexp = user()->experience += $exp;
$warnexp = '';
} else {
$newexp = user()->experience;
$exp = 0;
$warnexp = 'You have maxed out your experience points.';
}
if (user()->gold + $gold < 16777215) {
$newgold = user()->gold += $gold;
$warngold = '';
} else {
$newgold = user()->gold;
$gold = 0;
$warngold = 'You have maxed out your gold.';
}
$levelrow = db()->query('SELECT * FROM levels WHERE id=? LIMIT 1;', [user()->level + 1])->fetchArray(SQLITE3_ASSOC);
if (user()->level < 100) {
if ($newexp >= $levelrow[user()->charclass.'_exp']) {
user()->maxhp += $levelrow[user()->charclass.'_hp'];
user()->maxmp += $levelrow[user()->charclass.'_mp'];
user()->maxtp += $levelrow[user()->charclass.'_tp'];
user()->strength += $levelrow[user()->charclass.'_strength'];
user()->dexterity += $levelrow[user()->charclass.'_dexterity'];
user()->attackpower += $levelrow[user()->charclass.'_strength'];
user()->defensepower += $levelrow[user()->charclass.'_dexterity'];
user()->level += 1;
$newlevel = $levelrow['id'];
if ($levelrow[user()->charclass.'_spells'] != 0) {
user()->spells .= ','.$levelrow[user()->charclass.'_spells'];
$spelltext = 'You have learned a new spell.<br>';
} else {
$spelltext = '';
$newspell = '';
}
$page = 'Congratulations. You have defeated the '.$monsterrow['name'].".<br>You gain $exp experience. $warnexp <br>You gain $gold gold. $warngold <br><br><b>You have gained a level!</b><br><br>You gain ".$levelrow[user()->charclass.'_hp'].' hit points.<br>You gain '.$levelrow[user()->charclass.'_mp'].' magic points.<br>You gain '.$levelrow[user()->charclass.'_tp'].' travel points.<br>You gain '.$levelrow[user()->charclass.'_strength'].' strength.<br>You gain '.$levelrow[user()->charclass.'_dexterity']." dexterity.<br>$spelltext<br>You can now continue <a href=\"/\" hx-get=\"/\" hx-target=\"#middle\">exploring</a>.";
$title = 'Courage and Wit have served thee well!';
$dropcode = '';
} else {
$page = 'Congratulations. You have defeated the '.$monsterrow['name'].".<br>You gain $exp experience. $warnexp <br>You gain $gold gold. $warngold <br><br>";
if (rand(1, 30) === 1) {
$droprow = db()->query('SELECT * FROM drops WHERE mlevel <= ? ORDER BY RANDOM() LIMIT 1;', [$monsterrow['level']])->fetchArray(SQLITE3_ASSOC);
$dropcode = "dropcode='".$droprow['id']."',";
$page .= 'This monster has dropped an item. <a href="/drop" hx-get="/drop" hx-target="#middle">Click here</a> to reveal and equip the item, or you may also move on and continue <a href="/" hx-get="/" hx-target="#middle">exploring</a>.';
} else {
$dropcode = '';
$page .= 'You can now continue <a href="/" hx-get="/" hx-target="#middle">exploring</a>.';
}
$title = 'Victory!';
}
}
user()->currentaction = 'Exploring';
user()->currentfight = 0;
user()->currentuberdamage = 0;
user()->currentuberdefense = 0;
user()->currentmonstersleep = 0;
user()->currentmonsterimmune = 0;
user()->save();
page_title($title);
return $page;
}
public static function drop()
{
if (user()->dropcode == 0) {
redirect('/');
}
$droprow = get_drop(user()->dropcode);
if (isset($_POST['submit'])) {
$slot = $_POST['slot'];
if ($slot === 0) {
return 'Please go back and select an inventory slot to continue.';
}
$slotstr = 'slot'.$slot.'id';
if (user()->$slotstr != 0) {
$slotrow = get_drop(user()->$slotstr);
$old1 = explode(',', $slotrow['attribute1']);
if ($slotrow['attribute2'] != 'X') {
$old2 = explode(',', $slotrow['attribute2']);
} else {
$old2 = [0 => 'maxhp',1 => 0];
}
$new1 = explode(',', $droprow['attribute1']);
if ($droprow['attribute2'] != 'X') {
$new2 = explode(',', $droprow['attribute2']);
} else {
$new2 = [0 => 'maxhp',1 => 0];
}
user()->$old1[0] -= $old1[1];
user()->$old2[0] -= $old2[1];
if ($old1[0] == 'strength') {
user()->attackpower -= $old1[1];
}
if ($old1[0] == 'dexterity') {
user()->defensepower -= $old1[1];
}
if ($old2[0] == 'strength') {
user()->attackpower -= $old2[1];
}
if ($old2[0] == 'dexterity') {
user()->defensepower -= $old2[1];
}
user()->$new1[0] += $new1[1];
user()->$new2[0] += $new2[1];
if ($new1[0] == 'strength') {
user()->attackpower += $new1[1];
}
if ($new1[0] == 'dexterity') {
user()->defensepower += $new1[1];
}
if ($new2[0] == 'strength') {
user()->attackpower += $new2[1];
}
if ($new2[0] == 'dexterity') {
user()->defensepower += $new2[1];
}
if (user()->currenthp > user()->maxhp) {
user()->currenthp = user()->maxhp;
}
if (user()->currentmp > user()->maxmp) {
user()->currentmp = user()->maxmp;
}
if (user()->currenttp > user()->maxtp) {
user()->currenttp = user()->maxtp;
}
$slot_s = 'slot'.$_POST['slot'];
$slot_name = "{$slot_s}name";
$slot_id = "{$slot_s}id";
user()->$slot_name = $droprow['name'];
user()->$slot_id = $droprow['id'];
} else {
$new1 = explode(',', $droprow['attribute1']);
if ($droprow['attribute2'] != 'X') {
$new2 = explode(',', $droprow['attribute2']);
} else {
$new2 = [0 => 'maxhp',1 => 0];
}
user()->$new1[0] += $new1[1];
user()->$new2[0] += $new2[1];
if ($new1[0] == 'strength') {
user()->attackpower += $new1[1];
}
if ($new1[0] == 'dexterity') {
user()->defensepower += $new1[1];
}
if ($new2[0] == 'strength') {
user()->attackpower += $new2[1];
}
if ($new2[0] == 'dexterity') {
user()->defensepower += $new2[1];
}
$slot_s = 'slot'.$_POST['slot'];
$slot_name = "{$slot_s}name";
$slot_id = "{$slot_s}id";
user()->$slot_name = $droprow['name'];
user()->$slot_id = $droprow['id'];
}
user()->save();
return 'The item has been equipped. You can now continue <a href="/" hx-get="/" hx-target="#middle">exploring</a>.';
}
$attributearray = ['maxhp' => 'Max HP',
'maxmp' => 'Max MP',
'maxtp' => 'Max TP',
'defensepower' => 'Defense Power',
'attackpower' => 'Attack Power',
'strength' => 'Strength',
'dexterity' => 'Dexterity',
'expbonus' => 'Experience Bonus',
'goldbonus' => 'Gold Bonus'];
$page = 'The monster dropped the following item: <b>'.$droprow['name'].'</b><br><br>';
$page .= 'This item has the following attribute(s):<br>';
$attribute1 = explode(',', $droprow['attribute1']);
$page .= $attributearray[$attribute1[0]];
if ($attribute1[1] > 0) {
$page .= ' +'.$attribute1[1].'<br>';
} else {
$page .= $attribute1[1].'<br>';
}
if ($droprow['attribute2'] != 'X') {
$attribute2 = explode(',', $droprow['attribute2']);
$page .= $attributearray[$attribute2[0]];
if ($attribute2[1] > 0) {
$page .= ' +'.$attribute2[1].'<br>';
} else {
$page .= $attribute2[1].'<br>';
}
}
$page .= '<br>Select an inventory slot from the list below to equip this item. If the inventory slot is already full, the old item will be discarded.';
$page .= '<form action="/drop" method="post"><select name="slot"><option value="0">Choose One</option><option value="1">Slot 1: '.user()->slot1name.'</option><option value="2">Slot 2: '.user()->slot2name.'</option><option value="3">Slot 3: '.user()->slot3name.'</option></select> <input type="submit" name="submit" value="Submit" /></form>';
$page .= 'You may also choose to just continue <a href="/" hx-get="/" hx-target="#middle">exploring</a> and give up this item.';
return $page;
}
public static function dead()
{
return <<<HTML
<b>You have died.</b><br><br>
As a consequence, you've lost half of your gold. However, you have been given back a portion of your hit points
to continue your journey.<br><br>
You may now continue back to <a href="/" hx-get="/" hx-target="#middle">town</a>, and we hope you fair better next time.
HTML;
}
public static function handleMonsterTurn(&$userrow, $monsterrow)
{
$pagearray = '';
if (user()->currentmonstersleep != 0) {
$chancetowake = rand(1, 15);
if ($chancetowake > user()->currentmonstersleep) {
user()->currentmonstersleep = 0;
$pagearray .= 'The monster has woken up.<br>';
} else {
$pagearray .= 'The monster is still asleep.<br>';
}
}
if (user()->currentmonstersleep == 0) {
$tohit = (int) ceil(mt_rand((int) ($monsterrow['maxdam'] * 0.5), (int) $monsterrow['maxdam']));
$toblock = (int) ceil(mt_rand((int) (user()->defensepower * 0.75), (int) user()->defensepower) / 4);
$tododge = rand(1, 150);
if ($tododge <= sqrt(user()->dexterity)) {
$tohit = 0;
$pagearray .= "You dodge the monster's attack. No damage has been scored.<br>";
$persondamage = 0;
} else {
$persondamage = max(1, $tohit - $toblock);
if (user()->currentuberdefense != 0) {
$persondamage -= (int) ceil($persondamage * (user()->currentuberdefense / 100));
}
$persondamage = max(1, $persondamage);
}
$pagearray .= "The monster attacks you for $persondamage damage.<br><br>";
user()->currenthp -= $persondamage;
if (user()->currenthp <= 0) {
$newgold = (int) ceil(user()->gold / 2);
$newhp = (int) ceil(user()->maxhp / 4);
db()->query("UPDATE users SET currenthp=?, currentaction='In Town', currentmonster=0, currentmonsterhp=0, currentmonstersleep=0, currentmonsterimmune=0, currentfight=0, latitude=0, longitude=0, gold=? WHERE id=?;", [
$newhp, $newgold, $userrow['id'],
]);
self::dead();
}
}
return $pagearray;
}
public static function handleSpellCast(&$userrow, $newspellrow)
{
$pagearray = '';
switch ($newspellrow['type']) {
case 1: // Heal spell
$newhp = min(user()->currenthp + $newspellrow['attribute'], user()->maxhp);
user()->currenthp = $newhp;
user()->currentmp -= $newspellrow['mp'];
$pagearray = "You have cast the {$newspellrow['name']} spell, and gained {$newspellrow['attribute']} Hit Points.<br><br>";
break;
case 2: // Hurt spell
if (user()->currentmonsterimmune == 0) {
$monsterdamage = mt_rand((int) (($newspellrow['attribute'] / 6) * 5), $newspellrow['attribute']);
user()->currentmonsterhp -= $monsterdamage;
$pagearray = "You have cast the {$newspellrow['name']} spell for $monsterdamage damage.<br><br>";
} else {
$pagearray = "You have cast the {$newspellrow['name']} spell, but the monster is immune to it.<br><br>";
}
user()->currentmp -= $newspellrow['mp'];
break;
case 3: // Sleep spell
if (user()->currentmonsterimmune != 2) {
user()->currentmonstersleep = $newspellrow['attribute'];
$pagearray = "You have cast the {$newspellrow['name']} spell. The monster is asleep.<br><br>";
} else {
$pagearray = "You have cast the {$newspellrow['name']} spell, but the monster is immune to it.<br><br>";
}
user()->currentmp -= $newspellrow['mp'];
break;
case 4: // +Damage spell
user()->currentuberdamage = $newspellrow['attribute'];
user()->currentmp -= $newspellrow['mp'];
$pagearray = "You have cast the {$newspellrow['name']} spell, and will gain {$newspellrow['attribute']}% damage until the end of this fight.<br><br>";
break;
case 5: // +Defense spell
user()->currentuberdefense = $newspellrow['attribute'];
user()->currentmp -= $newspellrow['mp'];
$pagearray = "You have cast the {$newspellrow['name']} spell, and will gain {$newspellrow['attribute']}% defense until the end of this fight.<br><br>";
break;
}
return $pagearray;
}
}