#!/usr/bin/perl -X
#the Northlands a perl based mud. Please enjoy and share this great software with
#anyone who wants it. 
#Copyright (C) 2000  Peter Smith creator@operamail.com
#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License
#as published by the Free Software Foundation; either version 2
#of the License, or (at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.

#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
$|=1;
#use a whole bloody bunch of modules and pull in the other files.
use POSIX;
use IO::Socket;
use IO::Select;
use Socket; 
use Fcntl;
use Tie::RefHash;
use Term::ANSIColor;
use Data::Dumper;
use Time::HiRes ("usleep");
require ("commands.pm");
require ("init.pm");
require("update.pm");
require("roomcommands.pm");
require("monster.pm");
require("combat.pm");
require("ooc.pm");
#use strict;
#I gave up on keeping strict happy, the mud works. If someone wants to make it 
#strict compliant go ahead
use vars qw(@node @elements $monstertotal $room $longupdate %inbuffer %outbuffer %ready $prompt %type @room %item %command $maxpool);
$SIG{HUP}="ignore";
srand;
#$last=time;
#set port and open the socket. port comes from command line or defaults to 4005
$port=($ARGV[0] or 4005);
$server=IO::Socket::INET->new(LocalPort =>$port, Listen=> 10, Reuse=>1) or die "Can't make server socket: $@\n";
$prompt=color("blue").": ".color("reset")." ";
%inbuffer = ();
%outbuffer = ();
%ready = ();
tie %ready, 'Tie::RefHash';
#this should be in a config file somewhere... controls monster spawning.
#monsters is an array of monster #s and room is the room to spawn into
$node[0]={monsters=>[0],prob=>10,room=>1,max=>10,maxused=>0};

nonblock($server);
$select= IO::Select->new($server);
@elements=("Earth", "Air", "Water", "Fire", "Life");
$longupdate=0;
$lastupdate=time;
$monstertotal=0;
#various init stuff here, load map, items, monsters, etc
loadattacks();
loaddesc();
loaditems();
loadmuddata();
loadrealitems();
loadmonster();
loadroom();
loadcommands();	
while (1){
	#main loop
	my $client;
	my $rv;
	my $data;
	if (-e "killmud"){
		#close down time, should do a save of all players first. 
		#but we aren't yet
		&shutdown;
		print "Closing down\n";
		exit;
		}
	
	#check for new information
	foreach $client ($select->can_read(0)) {
		$last{$client}=time;
		if ($client == $server) {
			#new connection
			$client= $server->accept();
			$select->add($client);
			nonblock($client);
			$outbuffer{$client}.="Welcome to the Northlands\r\n";
			$outbuffer{$client}.="Please tell me who you are:";
			$type{$client}="login";
		}else {
			#get data
			$data='';
			$rv = $client->recv($data, POSIX::BUFSIZ, 0);
			

			unless (defined($rv) && length $data) {
				#hang up
				&delete($client);
				next;
				}
			
		$inbuffer{$client}.=$data;
		while($inbuffer{$client} =~s/(.*\n)//) {
			push( @{$ready{$client}}, $1);}
		}
	}
	foreach $client ( keys %ready) {
		#full line of text has come from someone. run handle on it.
		handle($client);
		}
	foreach $client ($select->can_write(1)) {
		next unless exists $outbuffer{$client};
		#crazy word wrapping stuff here. meddle at your own risk
		my @b;
		while ($outbuffer{$client}=~s/(.*?\r\n)//s){
			push (@b, $1);
			}
		#split into lines
		push(@b,$outbuffer{$client});
		my $line;
		foreach $a (@b){
			my $length=0;
			my @c;
			#split into words
			while ($a=~s/(.*?\s)//s){
				push (@c, $1);
				}
			push (@c, $a);				
			my $maxlength=($user{$client}{screenwidth} or 75);
			foreach $d (@c){
				if ($length+length($d)>$maxlength) {
					if (length($d)>$maxlength) {
						$line.= "$d"."\r\n";
						$length=0;
						}
					else {
						$line.="\r\n"."$d";
						$length=length $d;
						}
					}
				else {
					$length+=length($d);
					$line.="$d";
					}
				}
			}
		unless ($line=~/\r\n\Q$prompt\E$/s){
			$line.="\r\n$prompt";
			}
		$outbuffer{$client}=$line;
		$rv=$client->send($outbuffer{$client}, 0);
		unless (defined($rv)){
			warn "I was told I could write but I can't";
			next;}
	
		if (($rv == length $outbuffer{$client}) || ($!== POSIX::EWOULDBLOCK)){
			substr($outbuffer{$client}, 0,$rv)="";
			delete $outbuffer{$client} unless length $outbuffer{$client};
			}		
		else{
			warn "hello. $! $outbuffer{$client} \r\n$rv\r\n";
			&delete($client);
			next;}
		}
	if (time-$lastupdate>1){
		update();
		$lastupdate++;
		}
	else{
		#very short sleep. to keep from putting CPU to 100%
		usleep(0.0001);}
}
sub handle {
local $client= shift;
main: foreach $request (@{$ready{$client}}) {
	#handle incoming data from user $client.
	#start with some simple clean up
	$request=~s/[\a|\r|\n]//g;
	$request=~s/\s*$//i;
	#$request=lc($request);
	if ($request eq "."){
		$request=$lastrequest{$client};
		}
	if ($request){
		$lastrequest{$client}="$request";
		}
	$request=~s/^'/say /i;
	#'
	local @input=split(/ /,$request);	
	$input[0]=lc($input[0]);
		if ($request){
	#check for room commands first, since they can over rule others
	if ($user{$client}{room}){
		if (defined($room[$user{$client}{room}]{commands}{$input[0]})){
			unless (defined(&{$commandtype{$type{$client}}{$input[0]}})){
				$outbuffer{$client}.="That sub doesn't exists, this is a serious error.\r\n$prompt";
				warn("$commandtype{$type{$client}}{$input[0]} doesn't exist");
				next;
				}
			
			&{$commandtype{$type{$client}}{$input[0]}};
			next;	
		}
	}
	#if it's an input type, that is just grabbing what the user types then we go here.
		if (defined($commandtype{$type{$client}}{input})){
			unless (defined(&{$commandtype{$type{$client}}{input}})){
				$outbuffer{$client}.="That sub doesn't exists, this is a serious error.\r\n$prompt";
				next;
				}
			
			&{$commandtype{$type{$client}}{input}};
			next;
			}
		#check current command set for valid commands
		if (defined($commands{$type{$client}}{$input[0]})){
			unless (defined(&{$commands{$type{$client}}{$input[0]}})){
				$outbuffer{$client}.="That sub doesn't exists, this is a serious error.\r\n$prompt";
				next;
				}
			
			&{$commands{$type{$client}}{$input[0]}};
			}
		#check basic command set for valid commands
		elsif ($user{$client}{god} and defined($commands{builder}{$input[0]})){
			unless (defined(&{$commands{builder}{$input[0]}})){
				$outbuffer{$client}.="That sub doesn't exists, this is a serious error.\r\n$prompt";
				next;
				}
			&{$commands{builder}{$input[0]}};
		
		}
		elsif(defined($commands{basic}{$input[0]})){
			unless (defined(&{$commands{basic}{$input[0]}})){
				$outbuffer{$client}.="That sub doesn't exists, this is a serious error.\r\n$prompt";
				next;
				}
			&{$commands{basic}{$input[0]}};
			}
		
		else {
		#invalid command
		$outbuffer{$client}.="$commandtype{$type{$client}}{failed}\r\n$prompt";
			}
		}
	else{
		#user just hit enter
		$outbuffer{$client}.="\r\n$prompt";
		}
	}
delete $ready{$client};
}

sub nonblock {
my $socket= shift;
my $flags;
$flags= fcntl($socket, F_GETFL, 0) or die "can't get flags for socket $!\n";
fcntl($socket, F_SETFL, $flags | O_NONBLOCK) or die "can't make socket nonblocking";
}
sub roundcheck {
if ($user{$client}{stun}){
	$outbuffer{$client}.="You are stunned.\r\n$prompt";
	return 1;
	}
if ($user{$client}{round}){
	$outbuffer{$client}.="Please wait $user{$client}{round} more seconds.\r\n$prompt";
	return 1;
	}

}
sub shutdown {
	foreach $a (keys %user){
		if ($user{$a}{monster}){
			next;
		}
		saveplayer($a);
		removefromroom($a);
	}
	&save_room;
	
}