Файл: system/hall_records.php
Строк: 789
<?php
/**
* Залы славы клана: личные и клановые рекорды.
* Доступны только участникам собственного клана.
* Попытка длится 24 часа, установка/побитие запускается вручную.
* by Метриум (Стэлп)
*/
if (!function_exists('game_hall_bootstrap')) {
function game_hall_bootstrap(): void {
static $done=false; if($done) return; $done=true;
// На обычном запросе больше не выполняем пять CREATE TABLE. Схема создаётся
// только при первой установке/обновлении, а затем используется дешёвая версия в game_meta.
$ver=(int)(function_exists('game_meta_get') ? game_meta_get('hall_schema','0') : 0);
if($ver<4){
db_query("CREATE TABLE IF NOT EXISTS `hall_records` (
`scope` varchar(16) NOT NULL,
`code` varchar(32) NOT NULL,
`holder_id` int NOT NULL DEFAULT 0,
`score` bigint NOT NULL DEFAULT 0,
`updated_at` int NOT NULL DEFAULT 0,
PRIMARY KEY (`scope`,`code`), KEY `holder_id` (`holder_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `hall_record_awards` (
`scope` varchar(16) NOT NULL,
`code` varchar(32) NOT NULL,
`entity_id` int NOT NULL,
`awarded_at` int NOT NULL DEFAULT 0,
PRIMARY KEY (`scope`,`code`,`entity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `hall_record_attempts` (
`scope` varchar(16) NOT NULL,
`code` varchar(32) NOT NULL,
`entity_id` int NOT NULL,
`started_at` int NOT NULL DEFAULT 0,
`expires_at` int NOT NULL DEFAULT 0,
`target_score` bigint NOT NULL DEFAULT 0,
`baseline_score` bigint NOT NULL DEFAULT 0,
`actor_user` int NOT NULL DEFAULT 0,
`clan_id` int NOT NULL DEFAULT 0,
PRIMARY KEY (`scope`,`code`,`entity_id`),
KEY `entity_active` (`scope`,`entity_id`),
KEY `expires_at` (`expires_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `hall_record_history` (
`scope` varchar(16) NOT NULL,
`code` varchar(32) NOT NULL,
`entity_id` int NOT NULL,
`score` bigint NOT NULL DEFAULT 0,
`achieved_at` int NOT NULL DEFAULT 0,
PRIMARY KEY (`scope`,`code`,`entity_id`),
KEY `record_top` (`scope`,`code`,`score`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `hall_personal_clan_history` (
`clan_id` int NOT NULL,
`code` varchar(32) NOT NULL,
`user_id` int NOT NULL,
`score` bigint NOT NULL DEFAULT 0,
`achieved_at` int NOT NULL DEFAULT 0,
PRIMARY KEY (`clan_id`,`code`,`user_id`),
KEY `clan_record_top` (`clan_id`,`code`,`score`),
KEY `user_records` (`clan_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
foreach([
'expires_at'=>'int NOT NULL DEFAULT 0',
'target_score'=>'bigint NOT NULL DEFAULT 0',
'baseline_score'=>'bigint NOT NULL DEFAULT 0',
'actor_user'=>'int NOT NULL DEFAULT 0',
'clan_id'=>'int NOT NULL DEFAULT 0',
] as $col=>$sql){
$exists=db_fetch_assoc(db_query("SHOW COLUMNS FROM `hall_record_attempts` LIKE '".db_real_escape_string($col)."'"));
if(!$exists) db_query("ALTER TABLE `hall_record_attempts` ADD COLUMN `".$col."` ".$sql);
}
db_query("UPDATE `hall_record_attempts` SET `expires_at`=`started_at`+86400 WHERE `expires_at`=0");
if($ver<3){
// Формат старой попытки не хранил корректную исходную точку — безопаснее сбросить её один раз.
db_query("DELETE FROM `hall_record_attempts`");
db_query("INSERT INTO `hall_personal_clan_history` (`clan_id`,`code`,`user_id`,`score`,`achieved_at`)
SELECT cm.`clan`,h.`code`,h.`entity_id`,h.`score`,h.`achieved_at`
FROM `hall_record_history` h
INNER JOIN `clan_memb` cm ON cm.`user`=h.`entity_id`
WHERE h.`scope`='personal'
ON DUPLICATE KEY UPDATE `score`=GREATEST(`score`,VALUES(`score`)),
`achieved_at`=IF(VALUES(`score`)>`score`,VALUES(`achieved_at`),`achieved_at`)");
}
db_query("INSERT IGNORE INTO `hall_record_history` (`scope`,`code`,`entity_id`,`score`,`achieved_at`)
SELECT `scope`,`code`,`holder_id`,`score`,`updated_at` FROM `hall_records` WHERE `holder_id`>0 AND `score`>0");
if(function_exists('game_meta_set')) game_meta_set('hall_schema','4');
}
// Очистка просроченных попыток максимум раз в минуту, а не на каждом просмотре страницы.
$stamp=dirname(__DIR__).'/logs/hall-cleanup.tick'; $now=time();
if(!is_file($stamp) || (int)@filemtime($stamp)<$now-60){
@touch($stamp);
db_query("DELETE FROM `hall_record_attempts` WHERE `expires_at`>0 AND `expires_at`<=".$now);
}
}
function game_hall_personal_defs(): array { return [
'arena'=>['title'=>'Титан арены','description'=>'Установи лучший результат побед на арене','field'=>'arena_pobed','goal'=>1,'icon'=>'/images/icon/arena.png','target'=>'/arena/'],
'stone'=>['title'=>'Поиск камней','description'=>'Установи рекорд по найденным камням','field'=>'stone','goal'=>1,'icon'=>'/images/icon/stone.png','target'=>'/cave/'],
'grass'=>['title'=>'Поиск травы','description'=>'Установи рекорд по найденной траве','field'=>'grass','goal'=>1,'icon'=>'/images/icon/grass.png','target'=>'/cave/'],
'dungeon'=>['title'=>'Монстры подземелья','description'=>'Победи как можно больше монстров подземелья','field'=>'bos_1','goal'=>1,'icon'=>'/images/icon/hellworld.png','target'=>'/clandungeon/'],
'relic'=>['title'=>'Очки реликвий','description'=>'Набери как можно больше очков реликвий','field'=>'plat','goal'=>1,'icon'=>'/images/icon/relic.png','target'=>'/relic/'],
'gold'=>['title'=>'Золотая лихорадка','description'=>'Установи рекорд по золоту','field'=>'g','goal'=>1,'icon'=>'/images/icon/gold.png','target'=>'/shop/'],
'league'=>['title'=>'Титан лиги','description'=>'Поднимись как можно выше в лиге','field'=>'league_place','goal'=>1,'mode'=>'league','icon'=>'/images/icon/league.png','target'=>'/league/'],
'alchemy'=>['title'=>'Алхимик','description'=>'Повысь уровень мастерства разбора и алхимии','field'=>'parse','goal'=>1,'icon'=>'/images/icon/alchemy.png','target'=>'/lab/alchemy/'],
]; }
function game_hall_clan_defs(): array { return [
'arena'=>['title'=>'Титаны арены','description'=>'Суммарные победы участников клана на арене','field'=>'arena_pobed','goal'=>10,'icon'=>'/images/icon/arena.png','target'=>'/arena/'],
'stone'=>['title'=>'Искатели камней','description'=>'Суммарный результат поиска камней участниками клана','field'=>'stone','goal'=>10,'icon'=>'/images/icon/stone.png','target'=>'/cave/'],
'grass'=>['title'=>'Искатели травы','description'=>'Суммарный результат поиска травы участниками клана','field'=>'grass','goal'=>10,'icon'=>'/images/icon/grass.png','target'=>'/cave/'],
'dungeon'=>['title'=>'Покорители подземелья','description'=>'Суммарные победы над монстрами подземелья','field'=>'bos_1','goal'=>10,'icon'=>'/images/icon/hellworld.png','target'=>'/clandungeon/'],
'relic'=>['title'=>'Хранители реликвий','description'=>'Суммарные очки реликвий участников клана','field'=>'plat','goal'=>10,'icon'=>'/images/icon/relic.png','target'=>'/relic/'],
'gold'=>['title'=>'Золотой клан','description'=>'Суммарный запас золота участников клана','field'=>'g','goal'=>100,'icon'=>'/images/icon/gold.png','target'=>'/shop/'],
'league'=>['title'=>'Клан титанов лиги','description'=>'Суммарное продвижение участников клана в лиге','field'=>'league_place','goal'=>10,'mode'=>'league','icon'=>'/images/icon/league.png','target'=>'/league/'],
'alchemy'=>['title'=>'Клан алхимиков','description'=>'Суммарное мастерство алхимии участников клана','field'=>'parse','goal'=>10,'icon'=>'/images/icon/alchemy.png','target'=>'/lab/alchemy/'],
]; }
function game_hall_user_clan(int $uid): ?array {
if($uid<=0) return null;
$r=db_fetch_assoc(db_query('SELECT `clan`,`rank` FROM `clan_memb` WHERE `user`='.$uid.' LIMIT 1'));
return $r ?: null;
}
function game_hall_record(string $scope,string $code): array {
game_hall_bootstrap();
if(!in_array($scope,['personal','clan'],true)) return ['scope'=>$scope,'code'=>$code,'holder_id'=>0,'score'=>0,'updated_at'=>0];
$defs=$scope==='clan'?game_hall_clan_defs():game_hall_personal_defs();
if(!isset($defs[$code])) return ['scope'=>$scope,'code'=>$code,'holder_id'=>0,'score'=>0,'updated_at'=>0];
return db_fetch_assoc(db_query("SELECT * FROM `hall_records` WHERE `scope`='".$scope."' AND `code`='".db_real_escape_string($code)."' LIMIT 1")) ?: ['scope'=>$scope,'code'=>$code,'holder_id'=>0,'score'=>0,'updated_at'=>0];
}
function game_hall_personal_clan_best(int $clanId,string $code): array {
game_hall_bootstrap();
if($clanId<=0 || !isset(game_hall_personal_defs()[$code])) return ['holder_id'=>0,'score'=>0,'achieved_at'=>0];
$r=db_fetch_assoc(db_query("SELECT `user_id` holder_id,`score`,`achieved_at` FROM `hall_personal_clan_history` WHERE `clan_id`=".$clanId." AND `code`='".db_real_escape_string($code)."' ORDER BY `score` DESC,`achieved_at` ASC LIMIT 1"));
return $r ?: ['holder_id'=>0,'score'=>0,'achieved_at'=>0];
}
function game_hall_required_score(string $scope,string $code,int $clanId=0): int {
$defs=$scope==='clan'?game_hall_clan_defs():game_hall_personal_defs();
if(!isset($defs[$code])) return PHP_INT_MAX;
if($scope==='personal'){
$rec=game_hall_personal_clan_best($clanId,$code);
return max((int)$defs[$code]['goal'],(int)$rec['score']+((int)$rec['holder_id']>0?1:0));
}
$rec=game_hall_record('clan',$code);
return max((int)$defs[$code]['goal'],(int)$rec['score']+((int)$rec['holder_id']>0?1:0));
}
function game_hall_personal_score(array $u,array $def): int {
if(($def['mode']??'')==='league'){
$place=(int)($u['league_place']??0);
return ($place>=1 && $place<=1000) ? max(0,1001-$place) : 0;
}
return max(0,(int)($u[$def['field']]??0));
}
function game_hall_clan_score(int $clanId,array $def): int {
if($clanId<=0) return 0;
$field=(string)($def['field']??'');
$allowed=['arena_pobed','stone','grass','bos_1','plat','g','league_place','parse'];
if(!in_array($field,$allowed,true)) return 0;
if(($def['mode']??'')==='league'){
return max(0,(int)(db_result(db_query("SELECT COALESCE(SUM(CASE WHEN u.`league_place` BETWEEN 1 AND 1000 THEN 1001-u.`league_place` ELSE 0 END),0) FROM `clan_memb` cm INNER JOIN `users` u ON u.`id`=cm.`user` WHERE cm.`clan`=".$clanId),0)?:0));
}
return max(0,(int)(db_result(db_query("SELECT COALESCE(SUM(CAST(COALESCE(NULLIF(u.`".$field."`,''),'0') AS UNSIGNED)),0) FROM `clan_memb` cm INNER JOIN `users` u ON u.`id`=cm.`user` WHERE cm.`clan`=".$clanId),0)?:0));
}
function game_hall_attempt_row(string $scope,string $code,int $entityId): ?array {
game_hall_bootstrap(); if($entityId<=0) return null;
$r=db_fetch_assoc(db_query("SELECT * FROM `hall_record_attempts` WHERE `scope`='".db_real_escape_string($scope)."' AND `code`='".db_real_escape_string($code)."' AND `entity_id`=".$entityId." LIMIT 1"));
if(!$r) return null;
if((int)$r['expires_at']>0 && (int)$r['expires_at']<=time()){
db_query("DELETE FROM `hall_record_attempts` WHERE `scope`='".db_real_escape_string($scope)."' AND `code`='".db_real_escape_string($code)."' AND `entity_id`=".$entityId." LIMIT 1");
return null;
}
return $r;
}
function game_hall_attempt_active(string $scope,string $code,int $entityId): bool { return game_hall_attempt_row($scope,$code,$entityId)!==null; }
function game_hall_entity_active_attempt(string $scope,int $entityId): ?array {
game_hall_bootstrap(); if($entityId<=0) return null;
$r=db_fetch_assoc(db_query("SELECT * FROM `hall_record_attempts` WHERE `scope`='".db_real_escape_string($scope)."' AND `entity_id`=".$entityId." ORDER BY `started_at` DESC LIMIT 1"));
if(!$r) return null;
if((int)$r['expires_at']>0 && (int)$r['expires_at']<=time()){
db_query("DELETE FROM `hall_record_attempts` WHERE `scope`='".db_real_escape_string($scope)."' AND `entity_id`=".$entityId);
return null;
}
return $r;
}
function game_hall_personal_top(array $def,int $limit=5,int $clanId=0): array {
if($clanId<=0) return [];
$defs=game_hall_personal_defs(); $code=''; foreach($defs as $k=>$d){ if(($d['field']??'')===($def['field']??'') && ($d['mode']??'')===($def['mode']??'')){$code=$k;break;} } if($code==='') return [];
$rows=[]; $q=db_query("SELECT u.`id`,u.`login`,u.`r`,u.`level`,h.`score`,h.`achieved_at` FROM `hall_personal_clan_history` h INNER JOIN `users` u ON u.`id`=h.`user_id` WHERE h.`clan_id`=".$clanId." AND h.`code`='".db_real_escape_string($code)."' ORDER BY h.`score` DESC,h.`achieved_at` ASC LIMIT ".max(1,min(20,$limit)));
while($r=db_fetch_assoc($q)) $rows[]=$r; return $rows;
}
function game_hall_clan_top(array $def,int $limit=5): array {
$defs=game_hall_clan_defs(); $code=''; foreach($defs as $k=>$d){ if(($d['field']??'')===($def['field']??'') && ($d['mode']??'')===($def['mode']??'')){$code=$k;break;} } if($code==='') return [];
$rows=[]; $q=db_query("SELECT c.`id`,c.`name`,c.`r`,c.`gerb`,h.`score`,h.`achieved_at` FROM `hall_record_history` h INNER JOIN `clans` c ON c.`id`=h.`entity_id` WHERE h.`scope`='clan' AND h.`code`='".db_real_escape_string($code)."' ORDER BY h.`score` DESC,h.`achieved_at` ASC LIMIT ".max(1,min(20,$limit)));
while($r=db_fetch_assoc($q)) $rows[]=$r; return $rows;
}
function game_hall_start_attempt(string $scope,string $code,int $entityId,int $actorUser=0,int $clanId=0): array {
game_hall_bootstrap();
if(!in_array($scope,['personal','clan'],true) || $entityId<=0) return [false,'Некорректный рекорд.'];
$defs=$scope==='clan'?game_hall_clan_defs():game_hall_personal_defs(); if(!isset($defs[$code])) return [false,'Некорректный рекорд.'];
if($scope==='personal' && $clanId<=0) return [false,'Личный Зал славы доступен только участникам клана.'];
$active=game_hall_entity_active_attempt($scope,$entityId);
if($active && (string)$active['code']!==$code) return [false,'Сначала завершите или отмените текущую попытку рекорда.'];
if($active && (string)$active['code']===$code) return [true,'Попытка уже активна.'];
$target=game_hall_required_score($scope,$code,$clanId);
if($scope==='personal'){
$fresh=db_fetch_assoc(db_query("SELECT * FROM `users` WHERE `id`=".$entityId." LIMIT 1"));
$baseline=$fresh ? game_hall_personal_score($fresh,$defs[$code]) : 0;
} else {
$baseline=game_hall_clan_score($entityId,$defs[$code]);
}
$now=time(); $expires=$now+86400;
db_query("INSERT INTO `hall_record_attempts` (`scope`,`code`,`entity_id`,`started_at`,`expires_at`,`target_score`,`baseline_score`,`actor_user`,`clan_id`) VALUES ('".$scope."','".db_real_escape_string($code)."',".$entityId.",".$now.",".$expires.",".$target.",".$baseline.",".max(0,$actorUser).",".max(0,$clanId).") ON DUPLICATE KEY UPDATE `started_at`=VALUES(`started_at`),`expires_at`=VALUES(`expires_at`),`target_score`=VALUES(`target_score`),`baseline_score`=VALUES(`baseline_score`),`actor_user`=VALUES(`actor_user`),`clan_id`=VALUES(`clan_id`)");
return [true,'Попытка рекорда начата на 24 часа.'];
}
function game_hall_cancel_attempt(string $scope,string $code,int $entityId): void {
if(!in_array($scope,['personal','clan'],true)||$entityId<=0) return;
$defs=$scope==='clan'?game_hall_clan_defs():game_hall_personal_defs(); if(!isset($defs[$code])) return;
db_query("DELETE FROM `hall_record_attempts` WHERE `scope`='".$scope."' AND `code`='".db_real_escape_string($code)."' AND `entity_id`=".$entityId." LIMIT 1");
}
function game_hall_try_personal(array $user,string $code,int $clanId=0): array {
game_hall_bootstrap(); $defs=game_hall_personal_defs(); if(!isset($defs[$code])) return [false,'Неизвестный рекорд.'];
$uid=(int)$user['id']; $attempt=game_hall_attempt_row('personal',$code,$uid); if(!$attempt) return [false,''];
$attemptClan=(int)($attempt['clan_id']??0); if($clanId<=0) $clanId=$attemptClan;
if($clanId<=0 || ($attemptClan>0 && $attemptClan!==$clanId)){ game_hall_cancel_attempt('personal',$code,$uid); return [false,'']; }
$def=$defs[$code]; $current=game_hall_personal_score($user,$def); $score=max(0,$current-(int)($attempt['baseline_score']??0)); $need=max(1,(int)($attempt['target_score']??0));
if($score<$need) return [false,''];
db_query('START TRANSACTION');
try {
db_query("INSERT INTO `hall_personal_clan_history` (`clan_id`,`code`,`user_id`,`score`,`achieved_at`) VALUES (".$clanId.",'".db_real_escape_string($code)."',".$uid.",".$score.",".time().") ON DUPLICATE KEY UPDATE `score`=GREATEST(`score`,VALUES(`score`)),`achieved_at`=IF(VALUES(`score`)>`score`,VALUES(`achieved_at`),`achieved_at`)");
db_query("INSERT INTO `hall_record_history` (`scope`,`code`,`entity_id`,`score`,`achieved_at`) VALUES ('personal','".db_real_escape_string($code)."',".$uid.",".$score.",".time().") ON DUPLICATE KEY UPDATE `score`=GREATEST(`score`,VALUES(`score`)),`achieved_at`=IF(VALUES(`score`)>`score`,VALUES(`achieved_at`),`achieved_at`)");
$global=game_hall_record('personal',$code);
if($score>(int)$global['score']) db_query("INSERT INTO `hall_records` (`scope`,`code`,`holder_id`,`score`,`updated_at`) VALUES ('personal','".db_real_escape_string($code)."',".$uid.",".$score.",".time().") ON DUPLICATE KEY UPDATE `holder_id`=VALUES(`holder_id`),`score`=VALUES(`score`),`updated_at`=VALUES(`updated_at`)");
db_query("DELETE FROM `hall_record_attempts` WHERE `scope`='personal' AND `code`='".db_real_escape_string($code)."' AND `entity_id`=".$uid);
$awarded=(int)(db_result(db_query("SELECT COUNT(*) FROM `hall_record_awards` WHERE `scope`='personal' AND `code`='".db_real_escape_string($code)."' AND `entity_id`=".$uid),0)?:0);
if(!$awarded){
// +200 к сумме основных параметров, а не +200 к каждому.
db_query("UPDATE `users` SET `str`=`str`+50,`vit`=`vit`+50,`agi`=`agi`+50,`def`=`def`+50 WHERE `id`=".$uid." LIMIT 1");
db_query("INSERT IGNORE INTO `hall_record_awards` (`scope`,`code`,`entity_id`,`awarded_at`) VALUES ('personal','".db_real_escape_string($code)."',".$uid.",".time().")");
}
db_query('COMMIT'); return [true,'Новый личный рекорд установлен!'];
} catch(Throwable $e){ db_query('ROLLBACK'); throw $e; }
}
function game_hall_try_clan(array $user,int $clanId,string $code): array {
game_hall_bootstrap(); $defs=game_hall_clan_defs(); if(!isset($defs[$code])) return [false,'Неизвестный рекорд.'];
$attempt=game_hall_attempt_row('clan',$code,$clanId); if(!$attempt) return [false,''];
$def=$defs[$code]; $current=game_hall_clan_score($clanId,$def); $score=max(0,$current-(int)($attempt['baseline_score']??0)); $need=max(1,(int)($attempt['target_score']??0)); if($score<$need) return [false,''];
db_query('START TRANSACTION');
try {
$lock=db_fetch_assoc(db_query("SELECT * FROM `hall_records` WHERE `scope`='clan' AND `code`='".db_real_escape_string($code)."' FOR UPDATE"));
$current=(int)($lock['score']??0);
if($score<$need || $score<=$current){db_query('ROLLBACK');return [false,''];}
db_query("INSERT INTO `hall_records` (`scope`,`code`,`holder_id`,`score`,`updated_at`) VALUES ('clan','".db_real_escape_string($code)."',".$clanId.",".$score.",".time().") ON DUPLICATE KEY UPDATE `holder_id`=VALUES(`holder_id`),`score`=VALUES(`score`),`updated_at`=VALUES(`updated_at`)");
db_query("INSERT INTO `hall_record_history` (`scope`,`code`,`entity_id`,`score`,`achieved_at`) VALUES ('clan','".db_real_escape_string($code)."',".$clanId.",".$score.",".time().") ON DUPLICATE KEY UPDATE `score`=GREATEST(`score`,VALUES(`score`)),`achieved_at`=IF(VALUES(`score`)>`score`,VALUES(`achieved_at`),`achieved_at`)");
db_query("DELETE FROM `hall_record_attempts` WHERE `scope`='clan' AND `code`='".db_real_escape_string($code)."' AND `entity_id`=".$clanId);
$awarded=(int)(db_result(db_query("SELECT COUNT(*) FROM `hall_record_awards` WHERE `scope`='clan' AND `code`='".db_real_escape_string($code)."' AND `entity_id`=".$clanId),0)?:0);
if(!$awarded){
db_query("UPDATE `users` u INNER JOIN `clan_memb` cm ON cm.`user`=u.`id` SET u.`str`=u.`str`+10,u.`vit`=u.`vit`+10,u.`agi`=u.`agi`+10,u.`def`=u.`def`+10 WHERE cm.`clan`=".$clanId);
db_query("INSERT IGNORE INTO `hall_record_awards` (`scope`,`code`,`entity_id`,`awarded_at`) VALUES ('clan','".db_real_escape_string($code)."',".$clanId.",".time().")");
}
db_query('COMMIT'); return [true,'Новый клановый рекорд установлен!'];
} catch(Throwable $e){db_query('ROLLBACK');throw $e;}
}
function game_hall_target(string $scope,string $code): string {
$defs=$scope==='clan'?game_hall_clan_defs():game_hall_personal_defs();
return isset($defs[$code]) ? (string)($defs[$code]['target']??($scope==='clan'?'/hall-clan/':'/hall-personal/')) : ($scope==='clan'?'/hall-clan/':'/hall-personal/');
}
function game_hall_my_records(int $uid,int $clanId): array {
game_hall_bootstrap(); $out=[];
if($uid<=0||$clanId<=0) return $out;
$q=db_query("SELECT `code`,`score`,`achieved_at` FROM `hall_personal_clan_history` WHERE `clan_id`=".$clanId." AND `user_id`=".$uid);
while($r=db_fetch_assoc($q)) $out[(string)$r['code']]=$r;
return $out;
}
function game_hall_personal_bonus(int $uid): int {
game_hall_bootstrap(); if($uid<=0) return 0;
$count=(int)(db_result(db_query("SELECT COUNT(*) FROM `hall_record_awards` WHERE `scope`='personal' AND `entity_id`=".$uid),0)?:0);
return $count*200;
}
function game_hall_clan_bonus(int $clanId): int {
game_hall_bootstrap(); if($clanId<=0) return 0;
$count=(int)(db_result(db_query("SELECT COUNT(*) FROM `hall_record_awards` WHERE `scope`='clan' AND `entity_id`=".$clanId),0)?:0);
return $count*40;
}
function game_hall_attempt_progress(string $scope,string $code,int $entityId,array $user=[],int $clanId=0): ?array {
$attempt=game_hall_attempt_row($scope,$code,$entityId); if(!$attempt) return null;
$defs=$scope==='clan'?game_hall_clan_defs():game_hall_personal_defs(); if(!isset($defs[$code])) return null;
$def=$defs[$code];
if($scope==='personal'){
$fresh=$user;
if(!$fresh || (int)($fresh['id']??0)!==$entityId) $fresh=db_fetch_assoc(db_query("SELECT * FROM `users` WHERE `id`=".$entityId." LIMIT 1")) ?: [];
$current=$fresh?game_hall_personal_score($fresh,$def):0;
}else{
$current=game_hall_clan_score($entityId,$def);
}
$progress=max(0,$current-(int)($attempt['baseline_score']??0));
$target=max(1,(int)($attempt['target_score']??1));
$pct=(int)max(0,min(100,floor($progress/$target*100)));
$previous=max(0,$target-1);
return ['scope'=>$scope,'code'=>$code,'def'=>$def,'attempt'=>$attempt,'progress'=>$progress,'target'=>$target,'previous'=>$previous,'percent'=>$pct,'current'=>$current,'clan_id'=>$clanId];
}
function game_hall_progress_instruction(array $p): string {
$code=(string)($p['code']??''); $scope=(string)($p['scope']??'personal'); $prev=(int)($p['previous']??0); $target=(int)($p['target']??1);
$more=$prev>0;
if($scope==='clan'){
return match($code){
'arena'=>$more?"Клану нужно одержать более ".n_f($prev)." побед на арене.":"Клану нужно одержать ".n_f($target)." побед на арене.",
'stone'=>$more?"Клану нужно найти более ".n_f($prev)." камней.":"Клану нужно найти ".n_f($target)." камней.",
'grass'=>$more?"Клану нужно найти более ".n_f($prev)." травы.":"Клану нужно найти ".n_f($target)." травы.",
'dungeon'=>$more?"Клану нужно победить более ".n_f($prev)." монстров подземелья.":"Клану нужно победить ".n_f($target)." монстров подземелья.",
'relic'=>$more?"Клану нужно набрать более ".n_f($prev)." очков реликвий.":"Клану нужно набрать ".n_f($target)." очков реликвий.",
'gold'=>$more?"Клану нужно добыть более ".n_f($prev)." золота.":"Клану нужно добыть ".n_f($target)." золота.",
'league'=>$more?"Клану нужно набрать более ".n_f($prev)." очков продвижения в лиге.":"Клану нужно набрать ".n_f($target)." очков продвижения в лиге.",
'alchemy'=>$more?"Клану нужно получить более ".n_f($prev)." мастерства алхимии.":"Клану нужно получить ".n_f($target)." мастерства алхимии.",
default=>'Побейте действующий клановый рекорд.'
};
}
return match($code){
'arena'=>$more?"Победи на арене более ".n_f($prev)." раз.":"Победи на арене ".n_f($target)." раз.",
'stone'=>$more?"Найди более ".n_f($prev)." камней.":"Найди ".n_f($target)." камней.",
'grass'=>$more?"Найди более ".n_f($prev)." травы.":"Найди ".n_f($target)." травы.",
'dungeon'=>$more?"Победи более ".n_f($prev)." монстров подземелья.":"Победи ".n_f($target)." монстров подземелья.",
'relic'=>$more?"Набери более ".n_f($prev)." очков реликвий.":"Набери ".n_f($target)." очков реликвий.",
'gold'=>$more?"Добудь более ".n_f($prev)." золота.":"Добудь ".n_f($target)." золота.",
'league'=>$more?"Поднимись в лиге более чем на ".n_f($prev)." очков продвижения.":"Набери ".n_f($target)." очков продвижения в лиге.",
'alchemy'=>$more?"Получи более ".n_f($prev)." мастерства алхимии.":"Получи ".n_f($target)." мастерства алхимии.",
default=>'Побей действующий рекорд.'
};
}
function game_hall_active_progress_for_user(array $user): ?array {
$uid=(int)($user['id']??0); if($uid<=0) return null;
$cm=game_hall_user_clan($uid); if(!$cm) return null; $cid=(int)$cm['clan'];
$a=game_hall_entity_active_attempt('personal',$uid);
if($a){ $p=game_hall_attempt_progress('personal',(string)$a['code'],$uid,$user,$cid); if($p) return $p; }
$a=game_hall_entity_active_attempt('clan',$cid);
if($a){ $p=game_hall_attempt_progress('clan',(string)$a['code'],$cid,$user,$cid); if($p) return $p; }
return null;
}
function game_hall_progress_should_render(array $p,string $requestUri): bool {
$target=(string)($p['def']['target']??''); if($target==='') return false;
$path=(string)(parse_url($requestUri,PHP_URL_PATH)??'/');
$targetPath=(string)(parse_url($target,PHP_URL_PATH)??'/');
$path='/'.trim($path,'/').'/'; $targetPath='/'.trim($targetPath,'/').'/';
if($targetPath==='//') return $path==='//';
return str_starts_with($path,$targetPath);
}
function game_hall_progress_banner(array $user,string $requestUri): string {
$p=game_hall_active_progress_for_user($user); if(!$p || !game_hall_progress_should_render($p,$requestUri)) return '';
$instruction=game_hall_progress_instruction($p);
$progress=(int)$p['progress']; $target=(int)$p['target']; $pct=(int)$p['percent'];
$remaining=max(0,$target-$progress); $until=max(0,(int)$p['attempt']['expires_at']-time());
return '<div class="hall-live-progress" role="status">'
.'<div class="hall-live-progress__goal">'.security_html($instruction).'</div>'
.'<div class="hall-live-progress__numbers"><span class="hall-blue">Прогресс:</span> <b>'.n_f($progress).'</b> из <b>'.n_f($target).'</b>'
.($remaining>0?' <span class="grey">(осталось '.n_f($remaining).')</span>':' <span class="dgreen">цель достигнута</span>').'</div>'
.'<div class="hall-classic-progress hall-live-progress__bar"><span style="width:'.$pct.'%"></span></div>'
.'<div class="small grey">До конца попытки: <span data-hall-until="'.(int)$p['attempt']['expires_at'].'">'._time($until).'</span></div>'
.'</div>';
}
function game_hall_pending_tick(array $user): void {
game_hall_bootstrap(); $uid=(int)($user['id']??0); if($uid<=0) return;
$cm=game_hall_user_clan($uid);
if(!$cm){
db_query("DELETE FROM `hall_record_attempts` WHERE (`scope`='personal' AND `entity_id`=".$uid.") OR (`actor_user`=".$uid." AND `clan_id`>0)");
return;
}
$fresh=db_fetch_assoc(db_query("SELECT * FROM `users` WHERE `id`=".$uid." LIMIT 1")) ?: $user;
$cid=(int)$cm['clan']; $messages=[];
$q=db_query("SELECT `code` FROM `hall_record_attempts` WHERE `scope`='personal' AND `entity_id`=".$uid." AND (`expires_at`=0 OR `expires_at`>".time().")");
while($a=db_fetch_assoc($q)){ [$ok,$msg]=game_hall_try_personal($fresh,(string)$a['code'],$cid); if($ok&&$msg!=='')$messages[]=$msg; }
$q=db_query("SELECT `code` FROM `hall_record_attempts` WHERE `scope`='clan' AND `entity_id`=".$cid." AND (`expires_at`=0 OR `expires_at`>".time().")");
while($a=db_fetch_assoc($q)){ [$ok,$msg]=game_hall_try_clan($fresh,$cid,(string)$a['code']); if($ok&&$msg!=='')$messages[]=$msg; }
if($messages){ $_SESSION['ok']=implode('<br>',$messages); }
}
}