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(); /** * skooli - start of autoshutdown for ettvod */ addtimer(time() + 60 * 5, "if(\$procs[$this->procid] && \$procs[$this->procid]->autoreplay == 0 && count(\$procs[$this->procid]->viewers) == 0) { \$procs[$this->procid]->shutdown(); debug(\"[$this->type$this->num] Nobody connected after 5 minutes, shutting down\n\"); }"); /** * skooli - end of autoshutdown for ettvod */ } 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"); } /** * skooli - start of autoshutdown for ettvod */ function viewerquit($id) { parent::viewerquit($id); if($this->autoreplay == 0 && count($this->viewers) == 1) { // last viewer is disconnecting, start timer addtimer(time() + 60 * 2, "if(\$procs[$this->procid] && \$procs[$this->procid]->autoreplay == 0 && count(\$procs[$this->procid]->viewers) == 0) { \$procs[$this->procid]->shutdown(); debug(\"[$this->type$this->num] Nobody re-connected after 2 minutes, shutting down\n\"); }"); } } /** * skooli - end of autoshutdown for ettvod */ }