<?

// 24.11.2006

class replayer extends users {
      public $proc;
      public $procid;
      public $num;
      public $type = "replayer";
      public $stdin;
      public $stdout;
      public $stderr;
      public $game = -1;
      public $map = -1;
      public $autoreplay = 2;
      public $demofile;
      public $demodir;
      public $liverec = -1;
      public $islive = false;
      public $latchedmaps = 0;
      public $lastcrash = 0;
      public $replaydelay = 0;
      public $playqueue = array();
      public $queuerepeat = false;
   function __construct($slots = 10, $port = "27960", $private = 0, $game = 0, $config = "replayer.cfg") {
      global $procs;
      $this->port = intval($port);
      if($this->port == "") $this->port = "27960";
      $this->slots = intval($slots);
      $this->config = $config;
      if($this->config == "") $this->config = "replayer.cfg";
      $this->private = intval($private);

      $repnum = 1;
      while(findreplayer($repnum)) {
         $repnum++;
      }
      $this->num = $repnum;

      debug("Replayer $this->num created\n");
      $this->logfile = fopen("replayer$repnum.log", "a");

      $this->starttask();
      if($game) {
         $this->replaygame($game);
      }
      else {
         $this->demodone();
      }
   }

   function starttask() {
      global $procs, $pipes, $pipetoproc;
      $descriptorspec = array(0 => array("pipe", "r"),  2 => array("pipe", "w"));

      $hunk = round(48 + ($this->slots * 0.8));
      debug("Slots: $this->slots - Hunk: $hunk Mb");

      $cmd = "./ettv.x86 +set fs_game etpro +set net_port $this->port +set net_ip ".ETTVIP." +set com_hunkMegs $hunk +set sv_maxclients $this->slots +set sv_privateclients $this->private +set fs_homepath play +exec $this->config +set ettv_autoplay 1";

      $this->proc = proc_open($cmd, $descriptorspec, $mypipes, REPDIR);
      if($this->procid < 1) {
         $this->procid = intval($this->proc);  // WARNING: procid is not neccesarly the real procid anymore, its more a way to create a unique id !!
      }
      debug("Procid is: $this->procid");

      $pipes['stdin'][$this->procid] = $mypipes[0];
      $pipes['stdout'][$this->procid] = $mypipes[2];

      $pipetoproc[intval($mypipes[2])] = $this->procid; // This allows to find a proc belonging to a task faster

      $this->viewers = array();

   }

   function restart() {
      debug("[$this->type$this->num] Restart of server requested\n");
      $this->closeproc();
      $this->starttask();
      if(time() - $this->lastcrash < 10*60) {
         $this->nextmap();
         debug("[$this->type$this->num] Recursive crash on this map, switching to next\n");
      }
      $this->calldemo();
      $this->lastcrash = time();
   }

   function demodone() {
      parent::demodone();
      if($this->islive && $this->latchedmaps > 0) {
         $this->send("cp Waiting for next livemap\n");
         debug("[$this->type$this->num] Waiting for next livemap\n");
      }
      else {
         if($this->islive) {
            $this->game = -1;
            $this->map = -1;
            $this->islive = 0;
         }
         $this->nextmap();
         $this->calldemo();
      }
   }

   function watchdog() { // Try to get smth running again
      parent::watchdog();
      if($this->islive) {
         $this->sendat("cp ".C4."Live broadcast lost".C1."Picking it back up in a few seconds", time() + 3);
         $this->sendat("cp ".C4."Live broadcast lost".C1."Picking it back up in a few seconds", time() + 5);
         $this->game = -1;
         $this->map = -1;
         $this->islive = 0;
      }
      $this->nextmap();
      $this->calldemo();
   }

   function gameanounce() {
      if($this->warmup == 1) {
         if($this->islive) {
            $this->send("cp ".CLIVE."LIVE: ".C1."$this->gamename ".C2."Map ".C1.($this->map+1));
         }
         else {
            if($this->replaydelay) {
               $this->send("cp ".CSEMI."Semi-LIVE: ".C1."$this->gamename ".C2."Map: ".C1.($this->map+1)." ".C2."Delay: ".C1.round($this->replaydelay/60)." min");
            }
            else {
               list($date) = explode("-", $this->demodir);
               $this->send("cp ".CREPL."Replay: ".C1."$this->gamename ".C2."Map: ".C1.($this->map+1)." ".C2."Played on: ".C1."$date");
            }
         }
         addtimer(time() + GAME_A_INTERVAL, "if(\$procs[$this->procid]) \$procs[$this->procid]->gameanounce();");
         $this->anouncer = true;
      }
      else {
         $this->anouncer = false;
      }
   }

   function warmupstart() {
      parent::warmupstart();
      if(!$this->anouncer) {
         $this->gameanounce();
      }
   }

   function roundstart() {
      parent::roundstart();
      if($this->map == 0 && $this->round == 1) {
         // The only thing i ask for if you want to use my scripts - do not remove or change this line - thank you
         $this->queuebanner("^&This server is powered by ^>gtv^3'^0+^7arni^0+^&'s ettvd scripts");
         $this->queuebanner("^&This server is powered by ^>gtv^3'^0+^7arni^0+^&'s ettvd scripts");
      }
   }

   function ettvchat($id, $nick, $chat) {
      parent::ettvchat($id, $nick, $chat);
      $this->viewers[$id]['chat'][] = array("time" => time(), "chat" => $chat);
      $spamscore = -ANTISPAM_REPSCORE; // Repetition filter will always trigger once ...
      foreach($this->viewers[$id]['chat'] AS $cnt => $entry) {
         $time = $entry['time'];
         $line = $entry['chat'];
         if(time() - $time > ANTISPAM_LONG) {
            unset($this->viewers[$id]['chat'][$cnt]);
         }
         else {
            $spamscore += ANTISPAM_LONGSCORE;
            if(time() - $time <= ANTISPAM_SHORT) {
               $spamscore += ANTISPAM_SHORTSCORE;
            }
            if(time() - $time <= ANTISPAM_REPLIFETIME && $line == $chat) {
               $spamscore += ANTISPAM_REPSCORE;
            }
         }
      }
      // $this->queuebanner("$nick ^7spamscore: $spamscore", "right");
      $uid = $this->viewers[$id]['uid'];
      if($spamscore >= ANTISPAM_MUTESCORE) {
         if($this->viewers[$id]['muted'] < time()) {
            $mutetime = ANTISPAM_MUTEBASE * pow(ANTISPAM_MUTEMULTI, $this->viewers[$id]['spamcount']);
            $this->muteid($id, $mutetime, true);
            $this->queuebanner("$nick ".C4."spammed ".$this->viewers[$id]['spamcount']. " times\n".C4."He will be muted for $mutetime minutes", "right");
         }
      }
      else {
         if($this->viewers[$id]['muted'] > time()) {
            debug("Flaw - $nick should be muted but he's not - server probably ignored prior mute :-/");
            $this->send("mute $id");
         }
      }
      debug("[$this->type$this->num] $nick - $id #$spamscore# $chat\n");
      if(substr($chat, 0, 1) == "!") {
         global $adminips;
         $isadmin = false;
         foreach($adminips AS $aip => $time) {
            if($time < time() - 3600*6) {
               unset($adminips[$aip]);
            }
            else {
               if($this->viewers[$id]['ip'] == $aip) {
                  $isadmin = true;
                  break;
               }
            }
         }
         if($isadmin) {
            @list($first, $second, $third) = $pieces = explode(" ", $chat);
            if($first == "!admintest") {
               $this->send("qsay ".$this->viewers[$id]['nick']."^7 has admin access");
            }
            elseif($first == "!recordmode") {
               $second = intval($second);
               $procid = findrecorder($second);
               global $procs;
               if($procid) {
                  if($procs[$procid]->recordmode($third)) {
                     $this->send("qsay Set recordmode '$third' for recorder $second");
                  }
                  else {
                     $this->send("qsay Recorder $second did not understand mode '$third'");
                  }
               }
               else {
                  $this->send("qsay Recorder with id $second not running");
               }
            }
            elseif($first == "!nextmap") {
               $this->send("qsay Now playing next map (game)");
               $this->nextmap();
               $this->calldemo();
            }
            elseif($first == "!autoreplaynum") {
               $third = intval($second);
               $this->send("qsay Now automatically replaying the $second last games");
               $this->autoreplay = $second;
            }
            elseif($first == "!reclist") {
               global $procs;
               foreach($procs AS &$proc) {
                  if($proc->type == "recorder") {
                     if($proc->demolinked) {
                        if($proc->recording) {
                           $recm = "recording";
                        }
                        else {
                           $recm = "paused";
                        }
                     }
                     else {
                        $recm = "stopped";
                     }
                     $this->send("qsay $proc->num. $proc->gamename Mode: $proc->recordmode Status: $recm Ready: $proc->readyplayers");
                  }
               }
               unset($proc);
            }
            elseif($first == "!findviewer") {
               $result = $this->findviewer($second);
               $this->send("qsay Matches(".$result['count']."): ".$result['matches']);
            }
            elseif($first == "!mute") {
               $result = $this->findviewer($second);
               if($result['count'] == 1) {
                  $mutetime = intval($third);
                  if($mutetime == 0) $mutetime = 5;
                  $this->muteid($result['lastid'], $mutetime, false);
                  $this->send("qsay Muted ".$this->viewers[$result['lastid']]['nick']." ^7 for $mutetime min");
               }
               else {
                  $this->send("qsay Possible matches(".$result['count']."): ".$result['matches']);
               }
            }
            elseif($first == "!unmute") {
               $result = $this->findviewer($second);
               if($result['count'] == 1) {
                  if($this->viewers[$result['lastid']]['muted'] > time()) {
                     $this->unmuteid($result['lastid']);
                  }
                  else {
                     $this->send("qsay ".$this->viewers[$result['lastid']]['nick']."^7 is not muted");
                  }
               }
               else {
                  $this->send("qsay Possible matches(".$result['count']."): ".$result['matches']);
               }
            }
            elseif($first == "!kick") {
               $result = $this->findviewer($second);
               if($result['count'] == 1) {
                  $this->send("clientkick ".$result['lastid']);
                  $this->send("qsay Kicked ".$this->viewers[$result['lastid']]['nick']);
               }
               else {
                  $this->send("qsay Possible matches(".$result['count']."): ".$result['matches']);
               }
            }
         }
      }
   }

   function findviewer($search) {
      $found = "";
      $count = 0;
      $last = 0;
      $search = strtolower($search);
      foreach($this->viewers AS $id => $viewer) {
         if(strpos(ereg_replace("\^[^\^]", "", strtolower($viewer['nick'])), $search) !== false) {
            $count++;
            $found .= $viewer['nick']." ^7";
            $last = $id;
         }
      }
      return array("matches" => $found, "count" => $count, "lastid" => $last);
   }


   function mapevent($event) {
      parent::mapevent($event);
      list($event) = explode(".", $event);
      list($event) = explode(" and", $event);
      if($this->warmup == 0 && $this->starttime != 0) {
         if($this->round == 1) {
            if(!isset($this->events[$event])) {
               $this->events[$event] = (time() - $this->starttime) - $this->pausetime;
               // debug("Triggered for the first time\n");
               $tot = date("i:s", (time() - $this->starttime) - $this->pausetime);
               $imed = C3."Objective time set to: ".C2."$tot";
               $this->queuebanner(C1."$event\n$imed", "left");
               $this->queuebanner(C1."$event\n$imed", "left");
            }
         }
         else {
           $tot = date("i:s", (time() - $this->starttime) - $this->pausetime);
           if(!isset($this->events[$event]) || $this->events[$event] > 0) {
              if(isset($this->events[$event])) {
                 $diff = ((time() - $this->starttime) - $this->pausetime) - $this->events[$event];
                 // debug("This is $diff faster\n");
                 $imed = date("i:s", abs($diff));
                 if($diff > 0) {
                    $imed = C3."After ".C2."$tot".C3." - This is ^1$imed SLOWER ".C3."than first round";
                 }
                 else {
                    $imed = C3."After ".C2."$tot".C3." - This is ^2$imed FASTER ".C3."than first round";
                 }
              }
              else {
                 $imed = C3."After ".C2."$tot".C3." - Objective was not reached first round";
              }
              $this->queuebanner(C1."$event\n$imed", "left");
              $this->queuebanner(C1."$event\n$imed", "left");
              $this->events[$event] = 0;
            }
         }
      }
   }

   function endoflive() {
      if(defined("REPNAME$this->num")) {
         $playerbasename = constant("REPNAME$this->num");
      }
      else {
         $playerbasename = REPNAME.$this->num;
      }
      $this->sendat("cp ".C1."Live broadcast ended. ".C2."Thanks for visiting $playerbasename", time() + ETTV_DELAY - 10);
      $this->sendat("cp ".C1."Live broadcast ended. ".C2."Thanks for visiting $playerbasename", time() + ETTV_DELAY - 7);
      $this->sendat("cp ".C1."Live broadcast ended. ".C2."Thanks for visiting $playerbasename", time() + ETTV_DELAY - 4);
      $this->sendat("cp ".C1."Live broadcast ended. ".C2."Thanks for visiting $playerbasename", time() + ETTV_DELAY - 1);
   }

   function playdelayed($game, $map) {
      debug("[$this->type$this->num] Latched demofile Game: $game - Map: $map in ".ETTV_DELAY." sec\n");
      addtimer(time() + ETTV_DELAY, "if(\$procs[$this->procid]) \$procs[$this->procid]->playlive('$game', '$map');");
      if(!$this->islive) {
         global $demolist;
         $gamename = $demolist[$game]['name'];
         $this->sendat("cp ".CLIVE."Next Live: ".C1."$gamename ".C2."Switching in 1 minute", time() + ETTV_DELAY - 60);
         $this->sendat("cp ".CLIVE."Next Live: ".C1."$gamename ".C2."Switching in 30 seconds", time() + ETTV_DELAY - 30);
         $this->sendat("cp ".CLIVE."Next Live: ".C1."$gamename ".C2."Switching in 10 seconds", time() + ETTV_DELAY - 10);
      }
      $this->latchedmaps++;
   }

   function playlive($game, $map) {
      global $demolist;
      $this->game = $game;
      $this->map = $map;
      $this->demofile = $demolist[$this->game]['demos'][$this->map];
      $this->demodir = $demolist[$this->game]['dir'];
      $this->gamename = $demolist[$this->game]['name'];
      $this->islive = true;
      $this->latchedmaps--;
      if($this->latchedmaps < 0) {
         $this->latchedmaps = 0;
      }
      $this->calldemo();
   }

   function replaygame($game) {
      global $demolist;
      $this->game = $game;
      $this->map = 0;
      $this->demofile = $demolist[$this->game]['demos'][$this->map];
      $this->demodir = $demolist[$this->game]['dir'];
      $this->gamename = $demolist[$this->game]['name'];
      $this->islive = false;
      $this->calldemo();
   }

   function isliveon($rec) {
      $this->liverec = $rec;
      debug("[$this->type$this->num] Now live broadcasting after Recorder $rec\n");
   }

   function gameinit() {
      parent::gameinit();
      if($this->islive) {
      }
   }

   function viewerconnect($slot) {
      parent::viewerconnect($slot);
      if(!$this->queuestatus || time() - $this->laststatus > 10) { // Allow resend if server did not respond for 10 seconds
         addtimer(time() + 5, "if(\$procs[$this->procid]) \$procs[$this->procid]->send('status');");   // checking new users every 5 sec is enough
         $this->laststatus = time();
         $this->queuestatus = true;
      }
   }

   function normalspeed() {
      $this->send("timescale 1\n");
   }

   function calldemo() {
      $size = @filesize(DEMODIR."/$this->demodir/$this->demofile.tv_84");
      if($size === false) {
         if($this->islive) {
            debug("[$this->type$this->num] Announced livedemo not available - live cast interrupted");
         }
         else {
            debug("[$this->type$this->num] Please reload the democache next time you delete stuff");
            demolist();
            $this->nextmap();
         }
      }
      else {
         $size = round($size / 1024 / 1024, 2);
         debug("[$this->type$this->num] Demo is now: $this->demodir/$this->demofile - Game: $this->game - $size Mb\n");
         $this->source = "$this->demodir/$this->demofile";
         $this->sethostname();
         $this->send("demo $this->demodir/$this->demofile\n");
      }
   }

   function sethostname() {
      if(defined("REPNAME$this->num")) {
         $playerbasename = constant("REPNAME$this->num");
      }
      else {
         $playerbasename = REPNAME.$this->num;
      }

      if($this->islive) {
         $this->send("sv_hostname \"$playerbasename ".CLIVE."LIVE: ".C1."$this->gamename ".C2."Map ".C1.($this->map+1)."\"");
      }
      else {
         if($this->replaydelay) {
            $this->send("sv_hostname \"$playerbasename ".CSEMI."Semi-LIVE: ".C1."$this->gamename ".C2."Map: ".C1.($this->map+1)." ".C2."Delay: ".C1.round($this->replaydelay/60)." min");
         }
         else {
            list($date) = explode("-", $this->demodir);
            $this->send("sv_hostname \"$playerbasename ".CREPL."Replay: ".C1."$this->gamename ".C2."Map: ".C1.($this->map+1)." ".C2."Played on: ".C1."$date\"");
         }
      }
   }

   function addqueue($id) {
      $this->playqueue[] = $id;
   }

   function repeatqueue($val) {
      $this->queuerepeat = $val;
   }

   function delqueue() {
      $this->playqueue = array();
   }

   function nextgame() {
      global $demolist;
      $playlist = false;
      if($this->autoreplay == 0) {
         $this->shutdown();
      }
      else {
         while(count($this->playqueue) && !$playlist) {
            $next = array_shift($this->playqueue);
            if(isset($demolist[$next])) {
               $this->map = 0;
               $this->game = $next;
               $playlist = true;
               if($this->queuerepeat) {
                  $this->playqueue[] = $next;
               }
            }
         }
         if(!$playlist) {
            if(isset($demolist[$this->game-1])) {
               $this->game--;
               $this->map = 0;
               if($this->game < count($demolist) - $this->autoreplay) {
                  $this->game = count($demolist) - 1;
               }
            }
            else {
               $this->game = count($demolist) - 1;
               $this->map = 0;
            }
         }
      }
   }

   function nextmap() {
      global $demolist;
      if(isset($demolist[$this->game]['demos'][$this->map+1])) {
         $this->map++;
      }
      else {
         $this->nextgame();
      }
      while(isset($demolist[$this->game]['recordtime'][$this->map]) && $demolist[$this->game]['recordtime'][$this->map] > time() - ETTV_DELAY) {
         debug("[$this->type$this->num] Cannot play Game: $this->game Map: $this->map - disallowed by delay");
         if(isset($demolist[$this->game-1])) {
            $this->game--;
            $this->map = 0;
         }
         else {
            debug("[$this->type$this->num] Cannot play any game acording to ".ETTV_DELAY." - shutting down");
            $this->shutdown();
         }
      }
      $this->islive = false;
      $this->demofile = $demolist[$this->game]['demos'][$this->map];
      $this->demodir = $demolist[$this->game]['dir'];
      $this->gamename = $demolist[$this->game]['name'];
      if(isset($demolist[$this->game]['recordtime'][$this->map]) && $demolist[$this->game]['recordtime'][$this->map] > time() - 3600) {
         $this->replaydelay = time() - $demolist[$this->game]['recordtime'][$this->map];
         unset($demolist[$this->game]['recordtime'][$this->map]);
      }
      else {
         $this->replaydelay = 0;
      }
   }

   function shutdown() {
      $this->send("quit");
      addtimer(time() + 5, "unset(\$procs[$this->procid]);");
      debug("Sent quit to Replayer $this->num");

      $this->defunct = true;
   }

   function closeproc() {
      global $pipes;
      debug("Replayer $this->num closing the process");

      @fclose($pipes['stdin'][$this->procid]);
      unset($pipes['stdin'][$this->procid]);

      @fclose($pipes['stdout'][$this->procid]);
      unset($pipes['stdout'][$this->procid]);

      proc_terminate($this->proc);
      @proc_close($this->proc);
   }

   function __destruct() {
      $this->closeproc();
      debug("Replayer $this->num object removed");
   }

}