Вход Регистрация
Файл: 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(!
$existsdb_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<=|| !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>=&& $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']>&& (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']>&& (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<=|| ($attemptClan>&& $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); }
}
}
Онлайн: 4
Реклама