Compare commits

..

2 Commits

Author SHA1 Message Date
slawkens
6353febfd0 Add example top-5 2025-12-13 21:12:54 +01:00
slawkens
c8475ad65e Add skills to getTopPlayers 2025-12-13 21:12:14 +01:00
32 changed files with 264 additions and 271 deletions

View File

@@ -22,7 +22,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php-versions: [ '8.1', '8.2', '8.3', '8.4', '8.5' ] php-versions: [ '8.1', '8.2', '8.3', '8.4' ]
ots: ['tfs-1.4', 'canary-3.1.2'] # TODO: add 'tfs-master' (actually doesn't work cause AAC doesn't support reading .env configuration) ots: ['tfs-1.4', 'canary-3.1.2'] # TODO: add 'tfs-master' (actually doesn't work cause AAC doesn't support reading .env configuration)
name: Cypress (PHP ${{ matrix.php-versions }}, ${{ matrix.ots }}) name: Cypress (PHP ${{ matrix.php-versions }}, ${{ matrix.ots }})
steps: steps:

View File

@@ -1,18 +1,5 @@
# Changelog # Changelog
## [1.8.6 - 14.12.2025]
### Added
* Added hook for adding custom rules to validate new character name (https://github.com/slawkens/myaac/commit/8e6749c59984631288e8e9803819b2f0ff389761)
### Fixed
* Highscores: Fix ordering by different skills (Adjust order by desc: skill_tries, manaspent, experience) - More exact results (https://github.com/slawkens/myaac/commit/c86257e6dacbad773aa09c0958eeaa106a967f2d)
* Fix exception shown on first install, when there is no vendor - Before it displayed 500 white page, now it display the exception (https://github.com/slawkens/myaac/commit/18a1178e4b93607a350259679e0366cb83fb4126)
* Fix typo $up -> $down, in migration nr 7, was failing due that (https://github.com/slawkens/myaac/commit/fd74f01291d0e9cdb92ee1b95021c9d7b591ad7c)
### Changed
* Ini set html_errors = 0, to show html code in exceptions (https://github.com/slawkens/myaac/commit/9ed06782e67772826d927ad847a077b99df5060d)
## [1.8.5 - 21.11.2025] ## [1.8.5 - 21.11.2025]
### Added ### Added

View File

@@ -148,17 +148,16 @@ if(!IS_CLI) {
/** @var array $config */ /** @var array $config */
ini_set('log_errors', 1); ini_set('log_errors', 1);
if(isset($config['env']) && $config['env'] !== 'dev' && !defined('MYAAC_INSTALL')) { if(@$config['env'] === 'dev' || defined('MYAAC_INSTALL')) {
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
}
else {
ini_set('html_errors', 0);
ini_set('display_errors', 1); ini_set('display_errors', 1);
ini_set('display_startup_errors', 1); ini_set('display_startup_errors', 1);
error_reporting(E_ALL); error_reporting(E_ALL);
} }
else {
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
}
$autoloadFile = VENDOR . 'autoload.php'; $autoloadFile = VENDOR . 'autoload.php';
if (!is_file($autoloadFile)) { if (!is_file($autoloadFile)) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -5,6 +5,8 @@ $deprecatedConfig = [
'genders', 'genders',
'template', 'template',
'template_allow_change', 'template_allow_change',
'vocations_amount',
'vocations',
'client', 'client',
'session_prefix', 'session_prefix',
'friendly_urls', 'friendly_urls',

View File

@@ -17,6 +17,8 @@ use MyAAC\Models\Guild;
use MyAAC\Models\House; use MyAAC\Models\House;
use MyAAC\Models\Pages; use MyAAC\Models\Pages;
use MyAAC\Models\Player; use MyAAC\Models\Player;
use MyAAC\Models\PlayerDeath;
use MyAAC\Models\PlayerKillers;
use MyAAC\News; use MyAAC\News;
use MyAAC\Plugins; use MyAAC\Plugins;
use MyAAC\Settings; use MyAAC\Settings;
@@ -1135,11 +1137,29 @@ function csrfProtect(): void
} }
} }
function getTopPlayers($limit = 5, $skill = 'level') { function getTopPlayers($limit = 5, $skill = POT::SKILL_LEVEL)
{
global $db; global $db;
if ($skill === 'level') { $skillOriginal = $skill;
$skill = 'experience';
if (is_string($skill)) {
$skills = [
'level' => POT::SKILL_LEVEL,
'experience' => POT::SKILL_LEVEL,
'magic' => POT::SKILL_MAGIC,
'maglevel' => POT::SKILL_MAGIC,
'balance' => SKILL_BALANCE,
'frags' => SKILL_FRAGS,
];
$skill = $skills[$skill] ?? null;
}
if (!is_numeric($skill)) {
throw new RuntimeException("getTopPlayers: Invalid skill: $skillOriginal");
} }
return Cache::remember("top_{$limit}_{$skill}", 2 * 60, function () use ($db, $limit, $skill) { return Cache::remember("top_{$limit}_{$skill}", 2 * 60, function () use ($db, $limit, $skill) {
@@ -1160,15 +1180,64 @@ function getTopPlayers($limit = 5, $skill = 'level') {
$columns[] = 'lookmount'; $columns[] = 'lookmount';
} }
return Player::query() $query = Player::query()
->select($columns) ->select($columns)
->withOnlineStatus() ->withOnlineStatus()
->notDeleted() ->notDeleted()
->where('group_id', '<', setting('core.highscores_groups_hidden')) ->where('group_id', '<', setting('core.highscores_groups_hidden'))
->whereNotIn('id', setting('core.highscores_ids_hidden')) ->whereNotIn('id', setting('core.highscores_ids_hidden'))
->where('account_id', '!=', 1) ->where('account_id', '!=', 1)
->orderByDesc($skill) ->orderByDesc('value');
->limit($limit)
if ($limit > 0) {
$query->limit($limit);
}
if ($skill >= POT::SKILL_FIRST && $skill <= POT::SKILL_LAST) { // skills
if ($db->hasColumn('players', 'skill_fist')) {// tfs 1.0
$skill_ids = array(
POT::SKILL_FIST => 'skill_fist',
POT::SKILL_CLUB => 'skill_club',
POT::SKILL_SWORD => 'skill_sword',
POT::SKILL_AXE => 'skill_axe',
POT::SKILL_DIST => 'skill_dist',
POT::SKILL_SHIELD => 'skill_shielding',
POT::SKILL_FISH => 'skill_fishing',
);
$query
->addSelect($skill_ids[$skill] . ' as value')
->orderByDesc($skill_ids[$skill] . '_tries');
} else {
$query
->join('player_skills', 'player_skills.player_id', '=', 'players.id')
->where('skillid', $skill)
->addSelect('player_skills.value as value');
}
} else if ($skill == SKILL_FRAGS) // frags
{
if ($db->hasTable('player_killers')) {
$query->addSelect(['value' => PlayerKillers::whereColumn('player_killers.player_id', 'players.id')->selectRaw('COUNT(*)')]);
} else {
$query->addSelect(['value' => PlayerDeath::unjustified()->whereColumn('player_deaths.killed_by', 'players.name')->selectRaw('COUNT(*)')]);
}
} else if ($skill == SKILL_BALANCE) // balance
{
$query
->addSelect('players.balance as value');
} else {
if ($skill == POT::SKILL_MAGIC) {
$query
->addSelect('players.maglevel as value', 'players.maglevel')
->orderByDesc('manaspent');
} else { // level
$query
->addSelect('players.level as value', 'players.experience')
->orderByDesc('experience');
}
}
return $query
->get() ->get()
->map(function ($e, $i) { ->map(function ($e, $i) {
$row = $e->toArray(); $row = $e->toArray();

View File

@@ -14,7 +14,6 @@ use MyAAC\CsrfToken;
use MyAAC\Hooks; use MyAAC\Hooks;
use MyAAC\Plugins; use MyAAC\Plugins;
use MyAAC\Models\Town; use MyAAC\Models\Town;
use MyAAC\Server\XML\Vocations;
use MyAAC\Settings; use MyAAC\Settings;
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
@@ -215,5 +214,3 @@ if (count($towns) <= 0) {
config(['towns', $towns]); config(['towns', $towns]);
unset($towns); unset($towns);
new Vocations();

View File

@@ -26,7 +26,6 @@ use MyAAC\Cache\Cache;
*/ */
class OTS_DB_MySQL extends OTS_Base_DB class OTS_DB_MySQL extends OTS_Base_DB
{ {
private bool $hasCacheChanged = false;
private array $has_table_cache = []; private array $has_table_cache = [];
private array $has_column_cache = []; private array $has_column_cache = [];
private array $get_column_info_cache = []; private array $get_column_info_cache = [];
@@ -165,7 +164,7 @@ class OTS_DB_MySQL extends OTS_Base_DB
$cache->delete('database_columns_info'); $cache->delete('database_columns_info');
$cache->delete('database_checksum'); $cache->delete('database_checksum');
} }
else if ($this->hasCacheChanged) { else {
$cache->set('database_tables', serialize($this->has_table_cache), 3600); $cache->set('database_tables', serialize($this->has_table_cache), 3600);
$cache->set('database_columns', serialize($this->has_column_cache), 3600); $cache->set('database_columns', serialize($this->has_column_cache), 3600);
$cache->set('database_columns_info', serialize($this->get_column_info_cache), 3600); $cache->set('database_columns_info', serialize($this->get_column_info_cache), 3600);
@@ -229,8 +228,6 @@ class OTS_DB_MySQL extends OTS_Base_DB
private function hasTableInternal($name): bool private function hasTableInternal($name): bool
{ {
$this->hasCacheChanged = true;
return ($this->has_table_cache[$name] = $this->query('SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = ' . $this->quote(config('database_name')) . ' AND `TABLE_NAME` = ' . $this->quote($name) . ' LIMIT 1;')->rowCount() > 0); return ($this->has_table_cache[$name] = $this->query('SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = ' . $this->quote(config('database_name')) . ' AND `TABLE_NAME` = ' . $this->quote($name) . ' LIMIT 1;')->rowCount() > 0);
} }
@@ -244,8 +241,6 @@ class OTS_DB_MySQL extends OTS_Base_DB
} }
private function hasColumnInternal($table, $column): bool { private function hasColumnInternal($table, $column): bool {
$this->hasCacheChanged = true;
return $this->hasTable($table) && ($this->has_column_cache[$table . '.' . $column] = count($this->query('SHOW COLUMNS FROM `' . $table . "` LIKE " . $this->quote($column))->fetchAll()) > 0); return $this->hasTable($table) && ($this->has_column_cache[$table . '.' . $column] = count($this->query('SHOW COLUMNS FROM `' . $table . "` LIKE " . $this->quote($column))->fetchAll()) > 0);
} }
@@ -277,8 +272,6 @@ class OTS_DB_MySQL extends OTS_Base_DB
return false; return false;
} }
$this->hasCacheChanged = true;
$formatResult = function ($result) { $formatResult = function ($result) {
return [ return [
'field' => $result['Field'], 'field' => $result['Field'],

View File

@@ -852,7 +852,13 @@ class OTS_Player extends OTS_Row_DAO
throw new E_OTS_NotLoaded(); throw new E_OTS_NotLoaded();
} }
return \OTS_Toolbox::getVocationFromPromotion($this->data['vocation'], $this->data['promotion'] ?? 0); if(isset($this->data['promotion'])) {
global $config;
if((int)$this->data['promotion'] > 0)
return ($this->data['vocation'] + ($this->data['promotion'] * $config['vocations_amount']));
}
return $this->data['vocation'];
} }

View File

@@ -13,8 +13,6 @@
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public License, Version 3 * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public License, Version 3
*/ */
use MyAAC\Server\XML\Vocations;
/** /**
* Toolbox for common operations. * Toolbox for common operations.
* *
@@ -112,21 +110,14 @@ class OTS_Toolbox
$list->setFilter($filter); $list->setFilter($filter);
return $list; return $list;
} }
public static function getVocationFromPromotion($id, $promotion = 0): int
public static function getVocationName($id, $promotion = 0): string
{ {
if($promotion > 0) { if($promotion > 0) {
for ($i = 0; $i < $promotion; $i++) { $id = ($id + ($promotion * config('vocations_amount')));
if ($_id = Vocations::getPromoted($id)) {
$id = $_id;
}
}
} }
return $id; return config('vocations')[$id] ?? 'Unknown';
}
public static function getVocationName($id, $promotion = 0): string {
return config('vocations')[self::getVocationFromPromotion($id, $promotion)] ?? 'Unknown';
} }
} }

View File

@@ -9,7 +9,7 @@ $up = function () use ($db) {
} }
}; };
$down = function () use ($db) { $up = function () use ($db) {
if (!$db->hasColumn(TABLE_PREFIX . 'screenshots', 'name')) { if (!$db->hasColumn(TABLE_PREFIX . 'screenshots', 'name')) {
$db->addColumn(TABLE_PREFIX . 'screenshots', 'name', 'VARCHAR(30) NOT NULL'); $db->addColumn(TABLE_PREFIX . 'screenshots', 'name', 'VARCHAR(30) NOT NULL');
} }

View File

@@ -0,0 +1,29 @@
<?php
/**
* Example of using getTopPlayers() function
* to display the best players for each skill
*/
defined('MYAAC') or die('Direct access not allowed!');
$skills = [
'magic', 'level',
'balance', 'frags',
POT::SKILL_FIST, POT::SKILL_CLUB,
POT::SKILL_SWORD, POT::SKILL_AXE,
POT::SKILL_DISTANCE, POT::SKILL_SHIELD,
POT::SKILL_FISH
];
foreach ($skills as $skill) {?>
<ul>
<?php
echo '<strong>' . ucwords(is_string($skill) ? $skill : getSkillName($skill)) . '</strong>';
foreach (getTopPlayers(5, $skill) as $player) {?>
<li><?= $player['rank'] . '. ' . $player['name'] . ' - ' . $player['value']; ?></li>
<?php
}
?>
</ul>
<?php
}

View File

@@ -452,8 +452,10 @@ WHERE killers.death_id = '".$death['id']."' ORDER BY killers.final_hit DESC, kil
if($query->rowCount() > 0) { if($query->rowCount() > 0) {
echo 'Did you mean:<ul>'; echo 'Did you mean:<ul>';
foreach($query as $player) { foreach($query as $player) {
$player['vocation'] = OTS_Toolbox::getVocationFromPromotion($player['vocation'], $player['promotion'] ?? 0); if(isset($player['promotion'])) {
if((int)$player['promotion'] > 0)
$player['vocation'] += ($player['promotion'] * $config['vocations_amount']);
}
echo '<li>' . getPlayerLink($player['name']) . ' (<small><strong>level ' . $player['level'] . ', ' . $config['vocations'][$player['vocation']] . '</strong></small>)</li>'; echo '<li>' . getPlayerLink($player['name']) . ' (<small><strong>level ' . $player['level'] . ', ' . $config['vocations'][$player['vocation']] . '</strong></small>)</li>';
} }
echo '</ul>'; echo '</ul>';

View File

@@ -13,7 +13,6 @@ use MyAAC\Cache\Cache;
use MyAAC\Models\Player; use MyAAC\Models\Player;
use MyAAC\Models\PlayerDeath; use MyAAC\Models\PlayerDeath;
use MyAAC\Models\PlayerKillers; use MyAAC\Models\PlayerKillers;
use MyAAC\Server\XML\Vocations;
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
$title = 'Highscores'; $title = 'Highscores';
@@ -36,20 +35,24 @@ if(!is_numeric($page) || $page < 1 || $page > PHP_INT_MAX) {
$query = Player::query(); $query = Player::query();
$configVocations = config('vocations'); $configVocations = config('vocations');
$configVocationsAmount = config('vocations_amount');
$vocationId = null; $vocationId = null;
if($vocation !== 'all') { if($vocation !== 'all') {
foreach($configVocations as $id => $name) { foreach($configVocations as $id => $name) {
if(strtolower($name) == $vocation) { if(strtolower($name) == $vocation) {
$vocationId = $id; $vocationId = $id;
$filterVocations = [$id]; $add_vocs = [$id];
while($tmpVoc = Vocations::getPromoted($id)) { if ($id !== 0) {
$id = $tmpVoc; $i = $id + $configVocationsAmount;
$filterVocations[] = $tmpVoc; while (isset($configVocations[$i])) {
$add_vocs[] = $i;
$i += $configVocationsAmount;
}
} }
$query->whereIn('players.vocation', $filterVocations); $query->whereIn('players.vocation', $add_vocs);
break; break;
} }
} }
@@ -173,9 +176,7 @@ if (empty($highscores)) {
POT::SKILL_FISH => 'skill_fishing', POT::SKILL_FISH => 'skill_fishing',
); );
$query $query->addSelect($skill_ids[$skill] . ' as value');
->addSelect($skill_ids[$skill] . ' as value')
->orderByDesc($skill_ids[$skill] . '_tries');
} else { } else {
$query $query
->join('player_skills', 'player_skills.player_id', '=', 'players.id') ->join('player_skills', 'player_skills.player_id', '=', 'players.id')
@@ -197,11 +198,11 @@ if (empty($highscores)) {
if ($skill == POT::SKILL__MAGLEVEL) { if ($skill == POT::SKILL__MAGLEVEL) {
$query $query
->addSelect('players.maglevel as value', 'players.maglevel') ->addSelect('players.maglevel as value', 'players.maglevel')
->orderByDesc('manaspent'); ->orderBy('manaspent');
} else { // level } else { // level
$query $query
->addSelect('players.level as value', 'players.experience') ->addSelect('players.level as value', 'players.experience')
->orderByDesc('experience'); ->orderBy('experience');
$list = 'experience'; $list = 'experience';
} }
} }
@@ -322,5 +323,4 @@ $twig->display('highscores.html.twig', [
'page' => $page, 'page' => $page,
'baseLink' => $baseLink, 'baseLink' => $baseLink,
'updatedAt' => $updatedAt, 'updatedAt' => $updatedAt,
'baseVocations' => Vocations::getBase(true),
]); ]);

View File

@@ -12,7 +12,6 @@
use MyAAC\Cache\Cache; use MyAAC\Cache\Cache;
use MyAAC\Models\ServerConfig; use MyAAC\Models\ServerConfig;
use MyAAC\Models\ServerRecord; use MyAAC\Models\ServerRecord;
use MyAAC\Server\XML\Vocations;
defined('MYAAC') or die('Direct access not allowed!'); defined('MYAAC') or die('Direct access not allowed!');
$title = 'Who is online?'; $title = 'Who is online?';
@@ -57,14 +56,15 @@ $cached = Cache::remember("online_$order", setting('core.online_cache_ttl') * 60
$vocations = array_map(function ($name) { $vocations = array_map(function ($name) {
return 0; return 0;
}, config('vocations')); }, setting('core.vocations'));
if($db->hasTable('players_online')) // tfs 1.0 if($db->hasTable('players_online')) // tfs 1.0
$playersOnline = $db->query('SELECT `accounts`.`country`, `players`.`name`, `players`.`level`, `players`.`vocation`' . $outfit . ', `' . $skull_time . '` as `skulltime`, `' . $skull_type . '` as `skull` FROM `accounts`, `players`, `players_online` WHERE `players`.`id` = `players_online`.`player_id` AND `accounts`.`id` = `players`.`account_id` ORDER BY ' . $orderSql); $playersOnline = $db->query('SELECT `accounts`.`country`, `players`.`name`, `players`.`level`, `players`.`vocation`' . $outfit . ', `' . $skull_time . '` as `skulltime`, `' . $skull_type . '` as `skull` FROM `accounts`, `players`, `players_online` WHERE `players`.`id` = `players_online`.`player_id` AND `accounts`.`id` = `players`.`account_id` ORDER BY ' . $orderSql);
else else
$playersOnline = $db->query('SELECT `accounts`.`country`, `players`.`name`, `players`.`level`, `players`.`vocation`' . $outfit . ', ' . $promotion . ' `' . $skull_time . '` as `skulltime`, `' . $skull_type . '` as `skull` FROM `accounts`, `players` WHERE `players`.`online` > 0 AND `accounts`.`id` = `players`.`account_id` ORDER BY ' . $orderSql); $playersOnline = $db->query('SELECT `accounts`.`country`, `players`.`name`, `players`.`level`, `players`.`vocation`' . $outfit . ', ' . $promotion . ' `' . $skull_time . '` as `skulltime`, `' . $skull_type . '` as `skull` FROM `accounts`, `players` WHERE `players`.`online` > 0 AND `accounts`.`id` = `players`.`account_id` ORDER BY ' . $orderSql);
$configVocations = config('vocations'); $settingVocations = setting('core.vocations');
$settingVocationsAmount = setting('core.vocations_amount');
$players = []; $players = [];
foreach($playersOnline as $player) { foreach($playersOnline as $player) {
@@ -81,19 +81,22 @@ $cached = Cache::remember("online_$order", setting('core.online_cache_ttl') * 60
} }
} }
$player['vocation'] = OTS_Toolbox::getVocationFromPromotion($player['vocation'], $player['promotion'] ?? 0); if(isset($player['promotion'])) {
if((int)$player['promotion'] > 0)
$player['vocation'] += ($player['promotion'] * $settingVocationsAmount);
}
$players[] = array( $players[] = array(
'name' => getPlayerLink($player['name']), 'name' => getPlayerLink($player['name']),
'player' => $player, 'player' => $player,
'level' => $player['level'], 'level' => $player['level'],
'vocation' => $configVocations[$player['vocation']], 'vocation' => $settingVocations[$player['vocation']],
'skull' => $skull, 'skull' => $skull,
'country_image' => getFlagImage($player['country']), 'country_image' => getFlagImage($player['country']),
'outfit' => setting('core.outfit_images_url') . '?id=' . $player['looktype'] . ($outfit_addons ? '&addons=' . $player['lookaddons'] : '') . '&head=' . $player['lookhead'] . '&body=' . $player['lookbody'] . '&legs=' . $player['looklegs'] . '&feet=' . $player['lookfeet'], 'outfit' => setting('core.outfit_images_url') . '?id=' . $player['looktype'] . ($outfit_addons ? '&addons=' . $player['lookaddons'] : '') . '&head=' . $player['lookhead'] . '&body=' . $player['lookbody'] . '&legs=' . $player['looklegs'] . '&feet=' . $player['lookfeet'],
); );
$vocations[Vocations::getOriginal($player['vocation'])]++; $vocations[($player['vocation'] > $settingVocationsAmount ? $player['vocation'] - $settingVocationsAmount : $player['vocation'])]++;
} }
$record = ''; $record = '';
@@ -139,7 +142,6 @@ $twig->display('online.html.twig', array(
'vocations' => $cached['vocations'], 'vocations' => $cached['vocations'],
'vocs' => $cached['vocations'], // deprecated, to be removed 'vocs' => $cached['vocations'], // deprecated, to be removed
'order' => $order, 'order' => $order,
'baseVocations' => Vocations::getBase(false),
)); ));
// search bar // search bar

View File

@@ -219,14 +219,7 @@ return [
'cache_engine' => [ 'cache_engine' => [
'name' => 'Cache Engine', 'name' => 'Cache Engine',
'type' => 'options', 'type' => 'options',
'options' => [ 'options' => ['auto' => 'Auto', 'file' => 'Files', 'apc' => 'APC', 'apcu' => 'APCu', 'disable' => 'Disable'],
'auto' => 'Auto',
'file' => 'Files',
'apc' => 'APC',
'apcu' => 'APCu',
'php' => 'PHP',
'disable' => 'Disable',
],
'desc' => 'Auto is most reasonable. It will detect the best cache engine', 'desc' => 'Auto is most reasonable. It will detect the best cache engine',
'default' => 'auto', 'default' => 'auto',
'is_config' => true, 'is_config' => true,
@@ -319,6 +312,23 @@ return [
}, },
], ],
], ],
'vocations_amount' => [
'name' => 'Vocations Amount',
'type' => 'number',
'desc' => 'How much basic vocations your server got (without promotion)',
'default' => 4,
],
'vocations' => [
'name' => 'Vocation Names',
'type' => 'textarea',
'desc' => 'Separated by comma. Must be in the same order as in vocations.xml, starting with id: 0.',
'default' => 'None, Sorcerer, Druid, Paladin, Knight, Master Sorcerer, Elder Druid,Royal Paladin, Elite Knight',
'callbacks' => [
'get' => function ($value) {
return array_map('trim', explode(',', $value));
},
],
],
[ [
'type' => 'category', 'type' => 'category',
'title' => 'Database', 'title' => 'Database',

View File

@@ -13,8 +13,8 @@ namespace MyAAC\Cache;
class APC class APC
{ {
private string $prefix; private $prefix;
private bool $enabled; private $enabled;
public function __construct($prefix = '') public function __construct($prefix = '')
{ {
@@ -22,14 +22,14 @@ class APC
$this->enabled = function_exists('apc_fetch'); $this->enabled = function_exists('apc_fetch');
} }
public function set($key, $var, $ttl = 0): void public function set($key, $var, $ttl = 0)
{ {
$key = $this->prefix . $key; $key = $this->prefix . $key;
apc_delete($key); apc_delete($key);
apc_store($key, $var, $ttl); apc_store($key, $var, $ttl);
} }
public function get($key): string public function get($key)
{ {
$tmp = ''; $tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) { if ($this->fetch($this->prefix . $key, $tmp)) {
@@ -39,15 +39,18 @@ class APC
return ''; return '';
} }
public function fetch($key, &$var): bool { public function fetch($key, &$var)
{
return ($var = apc_fetch($this->prefix . $key)) !== false; return ($var = apc_fetch($this->prefix . $key)) !== false;
} }
public function delete($key): void { public function delete($key)
{
apc_delete($this->prefix . $key); apc_delete($this->prefix . $key);
} }
public function enabled(): bool { public function enabled()
{
return $this->enabled; return $this->enabled;
} }
} }

View File

@@ -13,8 +13,8 @@ namespace MyAAC\Cache;
class APCu class APCu
{ {
private string $prefix; private $prefix;
private bool $enabled; private $enabled;
public function __construct($prefix = '') public function __construct($prefix = '')
{ {
@@ -22,14 +22,14 @@ class APCu
$this->enabled = function_exists('apcu_fetch'); $this->enabled = function_exists('apcu_fetch');
} }
public function set($key, $var, $ttl = 0): void public function set($key, $var, $ttl = 0)
{ {
$key = $this->prefix . $key; $key = $this->prefix . $key;
apcu_delete($key); apcu_delete($key);
apcu_store($key, $var, $ttl); apcu_store($key, $var, $ttl);
} }
public function get($key): string public function get($key)
{ {
$tmp = ''; $tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) { if ($this->fetch($this->prefix . $key, $tmp)) {
@@ -39,15 +39,18 @@ class APCu
return ''; return '';
} }
public function fetch($key, &$var): bool { public function fetch($key, &$var)
{
return ($var = apcu_fetch($this->prefix . $key)) !== false; return ($var = apcu_fetch($this->prefix . $key)) !== false;
} }
public function delete($key): void { public function delete($key)
{
apcu_delete($this->prefix . $key); apcu_delete($this->prefix . $key);
} }
public function enabled(): bool { public function enabled()
{
return $this->enabled; return $this->enabled;
} }
} }

View File

@@ -83,7 +83,7 @@ class Cache
/** /**
* @return string * @return string
*/ */
public static function detect(): string public static function detect()
{ {
if (function_exists('apc_fetch')) if (function_exists('apc_fetch'))
return 'apc'; return 'apc';
@@ -98,7 +98,8 @@ class Cache
/** /**
* @return bool * @return bool
*/ */
public function enabled(): bool { public function enabled()
{
return false; return false;
} }

View File

@@ -12,22 +12,18 @@ namespace MyAAC\Cache;
class File class File
{ {
private string $prefix; private $prefix;
private string $dir; private $dir;
private bool $enabled; private $enabled;
public function __construct($prefix = '', $dir = '') public function __construct($prefix = '', $dir = '')
{ {
$this->prefix = $prefix; $this->prefix = $prefix;
$this->dir = $dir; $this->dir = $dir;
ensureFolderExists($this->dir);
ensureIndexExists($this->dir);
$this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir)); $this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir));
} }
public function set($key, $var, $ttl = 0): void public function set($key, $var, $ttl = 0)
{ {
$file = $this->_name($key); $file = $this->_name($key);
file_put_contents($file, $var); file_put_contents($file, $var);
@@ -39,7 +35,7 @@ class File
touch($file, time() + $ttl); touch($file, time() + $ttl);
} }
public function get($key): string public function get($key)
{ {
$tmp = ''; $tmp = '';
if ($this->fetch($key, $tmp)) { if ($this->fetch($key, $tmp)) {
@@ -49,7 +45,7 @@ class File
return ''; return '';
} }
public function fetch($key, &$var): bool public function fetch($key, &$var)
{ {
$file = $this->_name($key); $file = $this->_name($key);
if (!file_exists($file) || filemtime($file) < time()) { if (!file_exists($file) || filemtime($file) < time()) {
@@ -60,7 +56,7 @@ class File
return true; return true;
} }
public function delete($key): void public function delete($key)
{ {
$file = $this->_name($key); $file = $this->_name($key);
if (file_exists($file)) { if (file_exists($file)) {
@@ -68,11 +64,13 @@ class File
} }
} }
public function enabled(): bool { public function enabled()
{
return $this->enabled; return $this->enabled;
} }
private function _name($key): string { private function _name($key)
{
return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key)); return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key));
} }
} }

View File

@@ -12,40 +12,39 @@ namespace MyAAC\Cache;
class PHP class PHP
{ {
private string $prefix; private $prefix;
private string $dir; private $dir;
private bool $enabled; private $enabled;
public function __construct($prefix = '', $dir = '') public function __construct($prefix = '', $dir = '')
{ {
$this->prefix = $prefix; $this->prefix = $prefix;
$this->dir = $dir; $this->dir = $dir;
$this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir));
}
public function set($key, $var, $ttl = 0)
{
$var = var_export($var, true);
ensureFolderExists($this->dir); ensureFolderExists($this->dir);
ensureIndexExists($this->dir); ensureIndexExists($this->dir);
$this->enabled = (file_exists($this->dir) && is_dir($this->dir) && is_writable($this->dir)); // Write to temp file first to ensure atomicity
} $tmp = $this->dir . "tmp_$key." . uniqid('', true) . '.tmp';
file_put_contents($tmp, '<?php $var = ' . $var . ';', LOCK_EX);
public function set($key, $var, $ttl = 0): void $file = $this->_name($key);
{ rename($tmp, $file);
$var = var_export($var, true);
if ($ttl === 0) { if ($ttl === 0) {
$ttl = 365 * 24 * 60 * 60; // 365 days $ttl = 365 * 24 * 60 * 60; // 365 days
} }
$expires = time() + $ttl; touch($file, time() + $ttl);
// Write to temp file first to ensure atomicity
$tmp = $this->dir . "tmp_$key." . uniqid('', true) . '.tmp';
file_put_contents($tmp, "<?php return ['expires' => $expires, 'var' => $var];", LOCK_EX);
$file = $this->_name($key);
rename($tmp, $file);
} }
public function get($key): string public function get($key)
{ {
$tmp = ''; $tmp = '';
if ($this->fetch($key, $tmp)) { if ($this->fetch($key, $tmp)) {
@@ -55,23 +54,19 @@ class PHP
return ''; return '';
} }
public function fetch($key, &$var): bool public function fetch($key, &$var)
{ {
$file = $this->_name($key); $file = $this->_name($key);
if (!file_exists($file)) { if (!file_exists($file) || filemtime($file) < time()) {
return false; return false;
} }
$content = include $file; @include $file;
if (!isset($content) || $content['expires'] < time()) { $var = isset($var) ? $var : null;
return false;
}
$var = $content['var'];
return true; return true;
} }
public function delete($key): void public function delete($key)
{ {
$file = $this->_name($key); $file = $this->_name($key);
if (file_exists($file)) { if (file_exists($file)) {
@@ -79,11 +74,13 @@ class PHP
} }
} }
public function enabled(): bool { public function enabled()
{
return $this->enabled; return $this->enabled;
} }
private function _name($key): string { private function _name($key)
{
return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key) . '.php'); return sprintf('%s%s%s', $this->dir, $this->prefix, sha1($key) . '.php');
} }
} }

View File

@@ -13,8 +13,8 @@ namespace MyAAC\Cache;
class XCache class XCache
{ {
private string $prefix; private $prefix;
private bool $enabled; private $enabled;
public function __construct($prefix = '') public function __construct($prefix = '')
{ {
@@ -22,14 +22,14 @@ class XCache
$this->enabled = function_exists('xcache_get') && ini_get('xcache.var_size'); $this->enabled = function_exists('xcache_get') && ini_get('xcache.var_size');
} }
public function set($key, $var, $ttl = 0): void public function set($key, $var, $ttl = 0)
{ {
$key = $this->prefix . $key; $key = $this->prefix . $key;
xcache_unset($key); xcache_unset($key);
xcache_set($key, $var, $ttl); xcache_set($key, $var, $ttl);
} }
public function get($key): string public function get($key)
{ {
$tmp = ''; $tmp = '';
if ($this->fetch($this->prefix . $key, $tmp)) { if ($this->fetch($this->prefix . $key, $tmp)) {
@@ -39,7 +39,7 @@ class XCache
return ''; return '';
} }
public function fetch($key, &$var): bool public function fetch($key, &$var)
{ {
$key = $this->prefix . $key; $key = $this->prefix . $key;
if (!xcache_isset($key)) { if (!xcache_isset($key)) {
@@ -50,11 +50,13 @@ class XCache
return true; return true;
} }
public function delete($key): void { public function delete($key)
{
xcache_unset($this->prefix . $key); xcache_unset($this->prefix . $key);
} }
public function enabled(): bool { public function enabled()
{
return $this->enabled; return $this->enabled;
} }
} }

View File

@@ -46,8 +46,14 @@ class Player extends Model {
}); });
} }
public function getVocationNameAttribute() { public function getVocationNameAttribute()
return \OTS_Toolbox::getVocationName($this->vocation, $this->promotion ?? 0); {
$vocation = $this->vocation;
if (isset($this->promotion) && $this->promotion > 0) {
$vocation += ($this->promotion * setting('core.vocations_amount'));
}
return config('vocations')[$vocation] ?? 'Unknown';
} }
public function getIsDeletedAttribute() public function getIsDeletedAttribute()

View File

@@ -1,93 +0,0 @@
<?php
namespace MyAAC\Server\XML;
use MyAAC\Cache\Cache;
class Vocations
{
private static array $vocations;
private static array $vocationsFrom;
public function __construct()
{
$cached = Cache::remember('vocations', 10 * 60, function () {
$this->load();
$from = $this->getFrom();
$amount = 0;
foreach ($from as $vocId => $fromVocation) {
if ($vocId != 0 && $vocId == $fromVocation) {
$amount++;
}
}
return ['vocations' => $this->get(), 'vocationsFrom' => $from, 'amount' => $amount];
});
self::$vocations = $cached['vocations'];
self::$vocationsFrom = $cached['vocationsFrom'];
config(['vocations', self::$vocations]);
config(['vocations_amount', $cached['amount']]);
}
public function load(): void
{
if(!class_exists('DOMDocument')) {
throw new \RuntimeException('Please install PHP xml extension. MyAAC will not work without it.');
}
$vocationsXML = new \DOMDocument();
$file = config('data_path') . 'XML/vocations.xml';
if(!@file_exists($file)) {
$file = config('data_path') . 'vocations.xml';
}
if(!$vocationsXML->load($file)) {
throw new \RuntimeException('ERROR: Cannot load <i>vocations.xml</i> - the file is malformed. Check the file with xml syntax validator.');
}
foreach($vocationsXML->getElementsByTagName('vocation') as $vocation) {
$id = $vocation->getAttribute('id');
self::$vocations[$id] = $vocation->getAttribute('name');
$fromVocation = (int) $vocation->getAttribute('fromvoc');
self::$vocationsFrom[$id] = $fromVocation;
}
}
public static function get(): array {
return self::$vocations;
}
public static function getFrom(): array {
return self::$vocationsFrom;
}
public static function getPromoted(int $id): ?int {
foreach (self::$vocationsFrom as $vocId => $fromVocation) {
if ($id == $fromVocation && $vocId != $id) {
return $vocId;
}
}
return null;
}
public static function getOriginal(int $id): ?int {
return self::$vocationsFrom[$id] ?? null;
}
public static function getBase($includingRook = true): array {
$vocations = [];
foreach (self::$vocationsFrom as $vocId => $fromVoc) {
if ($vocId == $fromVoc && ($vocId != 0 || $includingRook)) {
$vocations[] = $vocId;
}
}
return $vocations;
}
}

View File

@@ -342,16 +342,6 @@ class Validator
} }
} }
global $hooks;
$params = ['name' => $name, 'error' => ''];
$hooks->triggerFilter(HOOK_FILTER_VALIDATE_CHARACTER_NEW_NAME, $params);
if (!empty($params['error'])) {
self::$lastError = $params['error'];
return false;
}
return true; return true;
} }

View File

@@ -108,7 +108,6 @@ define('HOOK_FILTER_ROUTES', ++$i);
define('HOOK_FILTER_TWIG_DISPLAY', ++$i); define('HOOK_FILTER_TWIG_DISPLAY', ++$i);
define('HOOK_FILTER_TWIG_RENDER', ++$i); define('HOOK_FILTER_TWIG_RENDER', ++$i);
define('HOOK_FILTER_THEME_FOOTER', ++$i); define('HOOK_FILTER_THEME_FOOTER', ++$i);
define('HOOK_FILTER_VALIDATE_CHARACTER_NEW_NAME', ++$i);
const HOOK_FIRST = HOOK_INIT; const HOOK_FIRST = HOOK_INIT;
define('HOOK_LAST', $i); define('HOOK_LAST', $i);

View File

@@ -18,7 +18,7 @@
<label for="vocationFilter">Choose a vocation</label> <label for="vocationFilter">Choose a vocation</label>
<select onchange="location = this.value;" id="vocationFilter"> <select onchange="location = this.value;" id="vocationFilter">
<option value="{{ getLink('highscores') }}/{{ list|urlencode }}" class="size_xs">[ALL]</option> <option value="{{ getLink('highscores') }}/{{ list|urlencode }}" class="size_xs">[ALL]</option>
{% for i in baseVocations %} {% for i in 0..config.vocations_amount %}
<option value="{{ getLink('highscores') }}/{{ list|urlencode }}/{{ config.vocations[i]|lower|urlencode }}" class="size_xs" {% if vocationId is not null and vocationId == i %}selected{% endif %}>{{ config.vocations[i]}}</option> <option value="{{ getLink('highscores') }}/{{ list|urlencode }}/{{ config.vocations[i]|lower|urlencode }}" class="size_xs" {% if vocationId is not null and vocationId == i %}selected{% endif %}>{{ config.vocations[i]}}</option>
{% endfor %} {% endfor %}
</select> </select>
@@ -120,7 +120,7 @@
<tr bgcolor="{{ config.lightborder }}"> <tr bgcolor="{{ config.lightborder }}">
<td> <td>
<a href="{{ getLink('highscores') }}/{{ list|urlencode }}" class="size_xs">[ALL]</a><br/> <a href="{{ getLink('highscores') }}/{{ list|urlencode }}" class="size_xs">[ALL]</a><br/>
{% for i in baseVocations %} {% for i in 0..config.vocations_amount %}
<a href="{{ getLink('highscores') }}/{{ list|urlencode }}/{{ config.vocations[i]|lower|urlencode }}" class="size_xs">{{ config.vocations[i]}}</a><br/> <a href="{{ getLink('highscores') }}/{{ list|urlencode }}/{{ config.vocations[i]|lower|urlencode }}" class="size_xs">{{ config.vocations[i]}}</a><br/>
{% endfor %} {% endfor %}
</td> </td>

View File

@@ -10,19 +10,22 @@
{% if setting('core.online_vocations_images') %} {% if setting('core.online_vocations_images') %}
<table width="200" cellspacing="1" cellpadding="0" border="0" align="center"> <table width="200" cellspacing="1" cellpadding="0" border="0" align="center">
<tr bgcolor="{{ config.darkborder }}"> <tr bgcolor="{{ config.darkborder }}">
{% for vocationId in baseVocations %} <td><img src="images/sorcerer.png" /></td>
<td><img src="images/{{ config('vocations')[vocationId]|lower }}.png" width="150" height="200"/></td> <td><img src="images/druid.png" /></td>
{% endfor %} <td><img src="images/paladin.png" /></td>
<td><img src="images/knight.png" /></td>
</tr> </tr>
<tr bgcolor="{{ config.vdarkborder }}"> <tr bgcolor="{{ config.vdarkborder }}">
{% for vocationId in baseVocations %} <td class="white" style="text-align: center;"><strong>Sorcerers</strong></td>
<td class="white" style="text-align: center;"><strong>{{ config('vocations')[vocationId] }}s</strong></td> <td class="white" style="text-align: center;"><strong>Druids</strong></td>
{% endfor %} <td class="white" style="text-align: center;"><strong>Paladins</strong></td>
<td class="white" style="text-align: center;"><strong>Knights</strong></td>
</tr> </tr>
<tr bgcolor="{{ config.lightborder }}"> <tr bgcolor="{{ config.lightborder }}">
{% for vocationId in baseVocations %} <td style="text-align: center;">{{ vocs[1] }}</td>
<td style="text-align: center;">{{ vocs[vocationId] }}</td> <td style="text-align: center;">{{ vocs[2] }}</td>
{% endfor %} <td style="text-align: center;">{{ vocs[3] }}</td>
<td style="text-align: center;">{{ vocs[4] }}</td>
</tr> </tr>
</table> </table>
<div style="text-align: center;">&nbsp;</div> <div style="text-align: center;">&nbsp;</div>
@@ -32,14 +35,11 @@
<td class="white" colspan="2"><b>Vocation statistics</b></td> <td class="white" colspan="2"><b>Vocation statistics</b></td>
</tr> </tr>
{% set i = 0 %} {% for i in 1..config.vocations_amount %}
{% for vocationId in baseVocations %} <tr bgcolor="{{ getStyle(i) }}">
<tr bgcolor="{{ getStyle(i) }}"> <td width="25%">{{ config.vocations[i] }}</td>
<td width="25%">{{ config.vocations[vocationId] }}</td> <td width="75%">{{ vocs[i] }}</td>
<td width="75%">{{ vocs[vocationId] }}</td> </tr>
</tr>
{% set i = i + 1 %}
{% endfor %} {% endfor %}
</table> </table>
<br/> <br/>