Skip to content
Snippets Groups Projects
chat.cpp 50.7 KiB
Newer Older
/* Synchronet real-time chat functions */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * 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.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"

#define PCHAT_LEN 1000		/* Size of Private chat file */

const char *weekday[]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday"
				,"Saturday"};
const char *month[]={"January","February","March","April","May","June"
				,"July","August","September","October","November","December"};

/****************************************************************************/
/****************************************************************************/
void sbbs_t::multinodechat(int channel)
	char	line[256],str[256],ch,done
			,usrs,preusrs,qusrs,*gurubuf=NULL,savch,*p
			,pgraph[400],buf[400]
			,usr[MAX_NODES],preusr[MAX_NODES],qusr[MAX_NODES];
	char	guru_lastanswer[512];
	int 	file;
	long	i,j,k,n;
	node_t 	node;

	if(useron.rest&FLAG('C')) {
		bputs(text[R_Chat]);
	if(channel<1 || channel>cfg.total_chans)
		channel=1;

	if(!chan_access(channel-1))
		return;
	if(useron.misc&(RIP|WIP|HTML) ||!(useron.misc&EXPERT))
		menu("multchat");
	bputs(text[WelcomeToMultiChat]);
	if(getnodedat(cfg.node_num,&thisnode,true)==0) {
		thisnode.aux=channel;
	bprintf(text[WelcomeToChannelN],channel,cfg.chan[channel-1]->name);
	if(cfg.chan[channel-1]->misc&CHAN_GURU && cfg.chan[channel-1]->guru<cfg.total_gurus
rswindell's avatar
rswindell committed
		&& chk_ar(cfg.guru[cfg.chan[channel-1]->guru]->ar,&useron,&client)) {
		sprintf(str,"%s%s.dat",cfg.ctrl_dir,cfg.guru[cfg.chan[channel-1]->guru]->code);
		if((file=nopen(str,O_RDONLY))==-1) {
			errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
		if((gurubuf=(char *)malloc((size_t)filelength(file)+1))==NULL) {
			close(file);
			errormsg(WHERE,ERR_ALLOC,str,(size_t)filelength(file)+1);
		read(file,gurubuf,(size_t)filelength(file));
		gurubuf[filelength(file)]=0;
	usrs=0;
	for(i=1;i<=cfg.sys_nodes && i<=cfg.sys_lastnode;i++) {
		if(i==cfg.node_num)
			continue;
		getnodedat(i,&node,0);
		if(node.action!=NODE_MCHT || node.status!=NODE_INUSE)
			continue;
		if(node.aux && (node.aux&0xff)!=channel)
			continue;
		printnodedat(i,&node);
deuce's avatar
deuce committed
		preusr[usrs]=(char)i;
		usr[usrs++]=(char)i;
	preusrs=usrs;
	if(gurubuf)
		bprintf(text[NodeInMultiChatLocally]
			,cfg.sys_nodes+1,cfg.guru[cfg.chan[channel-1]->guru]->name,channel);
	bputs(text[YoureOnTheAir]);
	done=0;
	while(online && !done) {
		checkline();
		gettimeleft();
		action=NODE_MCHT;
		qusrs=usrs=0;
        for(i=1;i<=cfg.sys_nodes;i++) {
			if(i==cfg.node_num)
				continue;
			getnodedat(i,&node,0);
			if(node.action!=NODE_MCHT
				|| (node.aux && channel && (node.aux&0xff)!=channel))
				continue;
			if(node.status==NODE_QUIET)
				qusr[qusrs++]=(char)i;
			else if(node.status==NODE_INUSE)
				usr[usrs++]=(char)i;
		if(preusrs>usrs) {
			if(!usrs && channel && cfg.chan[channel-1]->misc&CHAN_GURU
				&& cfg.chan[channel-1]->guru<cfg.total_gurus)
				bprintf(text[NodeJoinedMultiChat]
					,cfg.sys_nodes+1,cfg.guru[cfg.chan[channel-1]->guru]->name
					,channel);
			outchar(BEL);
			for(i=0;i<preusrs;i++) {
				for(j=0;j<usrs;j++)
					if(preusr[i]==usr[j])
						break;
				if(j==usrs) {
					getnodedat(preusr[i],&node,0);
					if(node.misc&NODE_ANON)
						sprintf(str,"%.80s",text[UNKNOWN_USER]);
					else
						username(&cfg,node.useron,str);
					bprintf(text[NodeLeftMultiChat]
						,preusr[i],str,channel);
				}
			}
		else if(preusrs<usrs) {
			if(!preusrs && channel && cfg.chan[channel-1]->misc&CHAN_GURU
				&& cfg.chan[channel-1]->guru<cfg.total_gurus)
				bprintf(text[NodeLeftMultiChat]
					,cfg.sys_nodes+1,cfg.guru[cfg.chan[channel-1]->guru]->name
					,channel);
			outchar(BEL);
			for(i=0;i<usrs;i++) {
				for(j=0;j<preusrs;j++)
					if(usr[i]==preusr[j])
						break;
				if(j==preusrs) {
					getnodedat(usr[i],&node,0);
					if(node.misc&NODE_ANON)
						sprintf(str,"%.80s",text[UNKNOWN_USER]);
					else
						username(&cfg,node.useron,str);
					bprintf(text[NodeJoinedMultiChat]
		preusrs=usrs;
		for(i=0;i<usrs;i++)
			preusr[i]=usr[i];
		attr(cfg.color[clr_multichat]);
		SYNC;
		sys_status&=~SS_ABORT;
		if((ch=inkey(K_NONE,250))!=0 || wordwrap[0]) {
			if(ch=='/') {
				bputs(text[MultiChatCommandPrompt]);
				strcpy(str,"ACELWQ?*");
				if(SYSOP)
				i=getkeys(str,cfg.total_chans);
				if(i&0x80000000L) {  /* change channel */
					savch=(char)(i&~0x80000000L);
					if(savch==channel)
						continue;
					if(!chan_access(savch-1))
						continue;
					bprintf(text[WelcomeToChannelN]
						,savch,cfg.chan[savch-1]->name);

					usrs=0;
					for(i=1;i<=cfg.sys_nodes;i++) {
						if(i==cfg.node_num)
							continue;
						getnodedat(i,&node,0);
						if(node.action!=NODE_MCHT
							|| node.status!=NODE_INUSE)
							continue;
						if(node.aux && (node.aux&0xff)!=savch)
							continue;
						printnodedat(i,&node);
						if(node.aux&0x1f00) {	/* password */
							bprintf(text[PasswordProtected]
								,node.misc&NODE_ANON
								? text[UNKNOWN_USER]
								: username(&cfg,node.useron,tmp));
							if(!getstr(str,8,K_UPPER|K_ALPHA|K_LINE))
								break;
							if(strcmp(str,unpackchatpass(tmp,&node)))
								break;
							bputs(text[CorrectPassword]);
						}
deuce's avatar
deuce committed
						preusr[usrs]=(char)i;
						usr[usrs++]=(char)i;
					if(i<=cfg.sys_nodes) {	/* failed password */
						bputs(text[WrongPassword]);
					if(gurubuf) {
						free(gurubuf);
					if(cfg.chan[savch-1]->misc&CHAN_GURU
						&& cfg.chan[savch-1]->guru<cfg.total_gurus
rswindell's avatar
rswindell committed
						&& chk_ar(cfg.guru[cfg.chan[savch-1]->guru]->ar,&useron,&client
						)) {
						sprintf(str,"%s%s.dat",cfg.ctrl_dir
							,cfg.guru[cfg.chan[savch-1]->guru]->code);
						if((file=nopen(str,O_RDONLY))==-1) {
							errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
						if((gurubuf=(char *)malloc((size_t)filelength(file)+1))==NULL) {
							close(file);
							errormsg(WHERE,ERR_ALLOC,str
						read(file,gurubuf,(size_t)filelength(file));
						gurubuf[filelength(file)]=0;
					preusrs=usrs;
					if(gurubuf)
						bprintf(text[NodeInMultiChatLocally]
							,cfg.sys_nodes+1
							,cfg.guru[cfg.chan[savch-1]->guru]->name
							,savch);
					channel=savch;
					if(!usrs && cfg.chan[savch-1]->misc&CHAN_PW
						&& !noyes(text[PasswordProtectChanQ])) {
						bputs(text[PasswordPrompt]);
						if(getstr(str,8,K_UPPER|K_ALPHA|K_LINE)) {
							getnodedat(cfg.node_num,&thisnode,true);
							thisnode.aux=channel;
							packchatpass(str,&thisnode);
							getnodedat(cfg.node_num,&thisnode,true);
							thisnode.aux=channel;
						}
						getnodedat(cfg.node_num,&thisnode,true);
						thisnode.aux=channel;
					putnodedat(cfg.node_num,&thisnode);
					bputs(text[YoureOnTheAir]);
					if(cfg.chan[channel-1]->cost
						&& !(useron.exempt&FLAG('J')))
						subtract_cdt(&cfg,&useron,cfg.chan[channel-1]->cost);
				else switch(i) {	/* other command */
					case '0':	/* Global channel */
						if(!SYSOP)
							break;
                        usrs=0;
						for(i=1;i<=cfg.sys_nodes;i++) {
							if(i==cfg.node_num)
								continue;
							getnodedat(i,&node,0);
							if(node.action!=NODE_MCHT
								|| node.status!=NODE_INUSE)
								continue;
							printnodedat(i,&node);
deuce's avatar
deuce committed
							preusr[usrs]=(char)i;
							usr[usrs++]=(char)i;
						preusrs=usrs;
						if(getnodedat(cfg.node_num,&thisnode,true)==0) {
							thisnode.aux=channel=0;
							putnodedat(cfg.node_num,&thisnode);
						}
						break;
					case 'A':   /* Action commands */
						useron.chat^=CHAT_ACTION;
						bprintf("\r\nAction commands are now %s\r\n"
							,useron.chat&CHAT_ACTION
							? text[ON]:text[OFF]);
						putuserrec(&cfg,useron.number,U_CHAT,8
							,ultoa(useron.chat,str,16));
						break;
					case 'C':   /* List of action commands */
						CRLF;
						for(i=0;channel && i<cfg.total_chatacts;i++) {
							if(cfg.chatact[i]->actset
								!=cfg.chan[channel-1]->actset)
								continue;
							bprintf("%-*.*s",LEN_CHATACTCMD
								,LEN_CHATACTCMD,cfg.chatact[i]->cmd);
							if(!((i+1)%8)) {
						CRLF;
						break;
					case 'E':   /* Toggle echo */
						useron.chat^=CHAT_ECHO;
						bprintf(text[EchoIsNow]
							,useron.chat&CHAT_ECHO
							? text[ON]:text[OFF]);
						putuserrec(&cfg,useron.number,U_CHAT,8
							,ultoa(useron.chat,str,16));
						break;
					case 'L':	/* list nodes */
						CRLF;
						for(i=1;i<=cfg.sys_nodes && i<=cfg.sys_lastnode;i++) {
							getnodedat(i,&node,0);
							printnodedat(i,&node);
						CRLF;
						break;
					case 'W':   /* page node(s) */
						j=getnodetopage(0,0);
						if(!j)
							break;
						for(i=0;i<usrs;i++)
							if(usr[i]==j)
								break;
						if(i>=usrs) {
							bputs(text[UserNotFound]);

						bputs(text[NodeMsgPrompt]);
						if(!getstr(line,66,K_LINE|K_MSG))
							break;

						sprintf(buf,text[ChatLineFmt]
							,thisnode.misc&NODE_ANON
							? text[AnonUserChatHandle]
							: useron.handle
							,cfg.node_num,'*',line);
						if(useron.chat&CHAT_ECHO)
							bputs(buf);
						break;
					case 'Q':	/* quit */
						done=1;
						break;
					case '*':
							menu("chan");
						else {
							bputs(text[ChatChanLstHdr]);
							bputs(text[ChatChanLstTitles]);
							if(cfg.total_chans>=10) {
								bputs("     ");
								bputs(text[ChatChanLstTitles]);
							CRLF;
							bputs(text[ChatChanLstUnderline]);
							if(cfg.total_chans>=10) {
								bputs("     ");
								bputs(text[ChatChanLstUnderline]);
							CRLF;
							if(cfg.total_chans>=10)
								j=(cfg.total_chans/2)+(cfg.total_chans&1);
							else
								j=cfg.total_chans;
							for(i=0;i<j && !msgabort();i++) {
								bprintf(text[ChatChanLstFmt],i+1
									,cfg.chan[i]->name
									,cfg.chan[i]->cost);
								if(cfg.total_chans>=10) {
									k=(cfg.total_chans/2)
										+i+(cfg.total_chans&1);
									if(k<cfg.total_chans) {
										bputs("     ");
										bprintf(text[ChatChanLstFmt]
											,k+1
											,cfg.chan[k]->name
						break;
					case '?':	/* menu */
						menu("multchat");
				ungetkey(ch);
				j=0;
				pgraph[0]=0;
				while(j<5) {
					if(!getstr(line,66,K_WRAP|K_MSG|K_CHAT))
						break;
					if(j) {
						sprintf(str,text[ChatLineFmt]
							,thisnode.misc&NODE_ANON
							? text[AnonUserChatHandle]
							: useron.handle
							,cfg.node_num,':',nulstr);
deuce's avatar
deuce committed
						sprintf(tmp,"%*s",(int)bstrlen(str),nulstr);
					SAFECAT(pgraph,line);
					SAFECAT(pgraph,crlf);
					if(!wordwrap[0])
						break;
				if(pgraph[0]) {
					if(channel && useron.chat&CHAT_ACTION) {
						for(i=0;i<cfg.total_chatacts;i++) {
							if(cfg.chatact[i]->actset
								!=cfg.chan[channel-1]->actset)
								continue;
							sprintf(str,"%s ",cfg.chatact[i]->cmd);
							if(!strnicmp(str,pgraph,strlen(str)))
								break;
							sprintf(str,"%.*s"
								,LEN_CHATACTCMD+2,pgraph);
							str[strlen(str)-2]=0;
							if(!stricmp(cfg.chatact[i]->cmd,str))

						if(i<cfg.total_chatacts) {
							p=pgraph+strlen(str);
							n=atoi(p);
							for(j=0;j<usrs;j++) {
								getnodedat(usr[j],&node,0);
								if(usrs==1) /* no need to search */
									break;
								if(n) {
									if(usr[j]==n)
										break;
								username(&cfg,node.useron,str);
								if(!strnicmp(str,p,strlen(str)))
									break;
								getuserrec(&cfg,node.useron,U_HANDLE
									,LEN_HANDLE,str);
								if(!strnicmp(str,p,strlen(str)))
							if(!usrs
								&& cfg.chan[channel-1]->guru<cfg.total_gurus)
								strcpy(str
								,cfg.guru[cfg.chan[channel-1]->guru]->name);
							else if(j>=usrs)
								strcpy(str,"everyone");
							else if(node.misc&NODE_ANON)
								strcpy(str,text[UNKNOWN_USER]);
							else
								username(&cfg,node.useron,str);

							/* Display on same node */
							bprintf(cfg.chatact[i]->out
								,thisnode.misc&NODE_ANON
								? text[UNKNOWN_USER] : useron.alias
								,str);
							CRLF;

							if(usrs && j<usrs) {
								/* Display to dest user */
								sprintf(buf,cfg.chatact[i]->out
									,thisnode.misc&NODE_ANON
									? text[UNKNOWN_USER] : useron.alias
									,"you");
								putnmsg(&cfg,usr[j],buf);


							/* Display to all other users */
							sprintf(buf,cfg.chatact[i]->out
								,thisnode.misc&NODE_ANON
								? text[UNKNOWN_USER] : useron.alias
								,str);

							for(i=0;i<usrs;i++) {
								if(i==j)
									continue;
								getnodedat(usr[i],&node,0);
								putnmsg(&cfg,usr[i],buf);
							for(i=0;i<qusrs;i++) {
								getnodedat(qusr[i],&node,0);
								putnmsg(&cfg,qusr[i],buf);

					sprintf(buf,text[ChatLineFmt]
						,thisnode.misc&NODE_ANON
						? text[AnonUserChatHandle]
						: useron.handle
						,cfg.node_num,':',pgraph);
					if(useron.chat&CHAT_ECHO)
						bputs(buf);
					for(i=0;i<usrs;i++) {
						getnodedat(usr[i],&node,0);
						putnmsg(&cfg,usr[i],buf);
					for(i=0;i<qusrs;i++) {
						getnodedat(qusr[i],&node,0);
						putnmsg(&cfg,qusr[i],buf);
					if(!usrs && channel && gurubuf
						&& cfg.chan[channel-1]->misc&CHAN_GURU)
						guruchat(pgraph,gurubuf,cfg.chan[channel-1]->guru,guru_lastanswer);
		if(sys_status&SS_ABORT)
	lncntr=0;
/****************************************************************************/
/****************************************************************************/
bool sbbs_t::guru_page(void)
{
	char	path[MAX_PATH+1];
	char*	gurubuf;
	int 	file;
	long	i;

	if(useron.rest&FLAG('C')) {
		bputs(text[R_Chat]);
	if(!cfg.total_gurus) {
		bprintf(text[SysopIsNotAvailable],"The Guru");
rswindell's avatar
rswindell committed
	if(cfg.total_gurus==1 && chk_ar(cfg.guru[0]->ar,&useron,&client))
		i=0;
	else {
		for(i=0;i<cfg.total_gurus;i++)
			uselect(1,i,nulstr,cfg.guru[i]->name,cfg.guru[i]->ar);
		i=uselect(0,0,0,0,0);
		if(i<0)
	}
	sprintf(path,"%s%s.dat",cfg.ctrl_dir,cfg.guru[i]->code);
	if((file=nopen(path,O_RDONLY))==-1) {
		errormsg(WHERE,ERR_OPEN,path,O_RDONLY);
	if((gurubuf=(char *)malloc((size_t)filelength(file)+1))==NULL) {
		errormsg(WHERE,ERR_ALLOC,path,(size_t)filelength(file)+1);
		close(file);
	read(file,gurubuf,(size_t)filelength(file));
	gurubuf[filelength(file)]=0;
	close(file);
	localguru(gurubuf,i);
	free(gurubuf);
	return(true);
}

/****************************************************************************/
/* The chat section                                                         */
/****************************************************************************/
void sbbs_t::chatsection()
{
	char	str[256],ch,no_rip_menu;

	if(useron.rest&FLAG('C')) {
		bputs(text[R_Chat]);
	action=NODE_CHAT;
	if(useron.misc&(RIP|WIP|HTML) || !(useron.misc&EXPERT))
	ASYNC;
	bputs(text[ChatPrompt]);
	while(online) {
		no_rip_menu=0;
		ch=(char)getkeys("ACDJPQST?\r",0);
			logch(ch,0);
		switch(ch) {
			case 'S':
				useron.chat^=CHAT_SPLITP;
				putuserrec(&cfg,useron.number,U_CHAT,8
					,ultoa(useron.chat,str,16));
				bprintf("\r\nPrivate split-screen chat is now: %s\r\n"
					,useron.chat&CHAT_SPLITP ? text[ON]:text[OFF]);
				break;
			case 'A':
				CRLF;
				useron.chat^=CHAT_NOACT;
				putuserrec(&cfg,useron.number,U_CHAT,8
					,ultoa(useron.chat,str,16));
				if(getnodedat(cfg.node_num,&thisnode,true)==0) {
					thisnode.misc^=NODE_AOFF;
					printnodedat(cfg.node_num,&thisnode);
				}
				putnodedat(cfg.node_num,&thisnode);
				no_rip_menu=true;
				break;
			case 'D':
				CRLF;
				useron.chat^=CHAT_NOPAGE;
				putuserrec(&cfg,useron.number,U_CHAT,8
					,ultoa(useron.chat,str,16));
				if(getnodedat(cfg.node_num,&thisnode,true)==0) {
					thisnode.misc^=NODE_POFF;
					printnodedat(cfg.node_num,&thisnode);
				}
				putnodedat(cfg.node_num,&thisnode);
				no_rip_menu=true;
				break;
			case 'J':
				multinodechat();
				break;
			case 'P':   /* private node-to-node chat */
				privchat();
				break;
			case 'C':
				no_rip_menu=1;
				if(sysop_page())
					break;
				if(cfg.total_gurus && chk_ar(cfg.guru[0]->ar,&useron,&client) && text[ChatWithGuruInsteadQ][0]) {
					SAFEPRINTF(str,text[ChatWithGuruInsteadQ],cfg.guru[0]->name);
					if(!yesno(str))
				/* FALL-THROUGH */
				guru_page();
				no_rip_menu=1;
				break;
			case '?':
				if(useron.misc&EXPERT)
				break;
			default:	/* 'Q' or <CR> */
				lncntr=0;
//				if(gurubuf)
//					free(gurubuf);
		action=NODE_CHAT;
		if(!(useron.misc&EXPERT) || useron.misc&(WIP|HTML)
			|| (useron.misc&RIP && !no_rip_menu)) {
			menu("chat"); 
		}
		bputs(text[ChatPrompt]); 
	}
//	if(gurubuf)
//		free(gurubuf);
/****************************************************************************/
/****************************************************************************/
bool sbbs_t::sysop_page(void)
	char	str[256];
	int		i;
	if(useron.rest&FLAG('C')) {
		bputs(text[R_Chat]);
		return(false); 
	}

rswindell's avatar
rswindell committed
		|| (cfg.sys_chat_ar[0] && chk_ar(cfg.sys_chat_ar,&useron,&client))
		|| useron.exempt&FLAG('C')) {

		sprintf(str,"%s paged sysop for chat",useron.alias);
		logline("C",str);

		for(i=0;i<cfg.total_pages;i++)
rswindell's avatar
rswindell committed
			if(chk_ar(cfg.page[i]->ar,&useron,&client))
				break;
		if(i<cfg.total_pages) {
			bprintf(text[PagingGuru],cfg.sys_op);
			external(cmdstr(cfg.page[i]->cmd,nulstr,nulstr,NULL)
				,cfg.page[i]->misc&XTRN_STDIO ? EX_STDIO : 0); 
		else if(cfg.sys_misc&SM_SHRTPAGE) {
			bprintf(text[PagingGuru],cfg.sys_op);
			for(i=0;i<10 && !lkbrd(1);i++) {
				sbbs_beep(1000,200);
				mswait(200);
		else {
			sys_status^=SS_SYSPAGE;
			bprintf(text[SysopPageIsNow]
				,sys_status&SS_SYSPAGE ? text[ON] : text[OFF]);
			nosound();	
		}
		if(!(sys_status&SS_SYSPAGE))
			remove(syspage_semfile);

		return(true);
	}

	bprintf(text[SysopIsNotAvailable],cfg.sys_op);

	return(false);
}

/****************************************************************************/
/* Returns 1 if user online has access to channel "channum"                 */
/****************************************************************************/
bool sbbs_t::chan_access(uint cnum)
{

rswindell's avatar
rswindell committed
	if(!cfg.total_chans || cnum>=cfg.total_chans || !chk_ar(cfg.chan[cnum]->ar,&useron,&client)) {
		bputs(text[CantAccessThatChannel]);
	if(!(useron.exempt&FLAG('J')) && cfg.chan[cnum]->cost>useron.cdt+useron.freecdt) {
		bputs(text[NotEnoughCredits]);
/****************************************************************************/
/* Private split-screen (or interspersed) chat with node or local sysop		*/
/****************************************************************************/
void sbbs_t::privchat(bool local)
{
	char	str[128],c,*p,localbuf[5][81],remotebuf[5][81]
			,localline=0,remoteline=0,localchar=0,remotechar=0
			,*sep=text[PrivateChatSeparator]
			,*local_sep=text[SysopChatSeparator]
			;
	char	inpath[MAX_PATH+1];
	uchar	ch;
	int 	in,out,i,n,echo=1,x,y,activity,remote_activity;
    int		local_y=1,remote_y=1;

		if(useron.rest&FLAG('C')) {
			bputs(text[R_Chat]);
			return; 
		}

		n=getnodetopage(0,0);
		if(!n)
			return;
		if(n==cfg.node_num) {
			bputs(text[NoNeedToPageSelf]);
		getnodedat(n,&node,0);
		if(node.action==NODE_PCHT && node.aux!=cfg.node_num) {
			bprintf(text[NodeNAlreadyInPChat],n);
		if((node.action!=NODE_PAGE || node.aux!=cfg.node_num)
			&& node.misc&NODE_POFF && !SYSOP) {
			bprintf(text[CantPageNode],node.misc&NODE_ANON
				? text[UNKNOWN_USER] : username(&cfg,node.useron,tmp));
		if(node.action!=NODE_PAGE) {
			bprintf(text[PagingUser]
				,node.misc&NODE_ANON ? text[UNKNOWN_USER] : username(&cfg,node.useron,tmp)
				,node.misc&NODE_ANON ? 0 : node.useron);
			sprintf(str,text[NodePChatPageMsg]
				,cfg.node_num,thisnode.misc&NODE_ANON
					? text[UNKNOWN_USER] : useron.alias);
			sprintf(str,"%s paged %s on node %d to private chat"
				,useron.alias,username(&cfg,node.useron,tmp),n);
		if(getnodedat(cfg.node_num,&thisnode,true)==0) {
			thisnode.action=action=NODE_PAGE;
			thisnode.aux=n;
			putnodedat(cfg.node_num,&thisnode);
		}

		if(node.action!=NODE_PAGE || node.aux!=cfg.node_num) {
			bprintf(text[WaitingForNodeInPChat],n);
			while(online && !(sys_status&SS_ABORT)) {
				getnodedat(n,&node,0);
				if((node.action==NODE_PAGE || node.action==NODE_PCHT)
					&& node.aux==cfg.node_num) {
					bprintf(text[NodeJoinedPrivateChat]
						,n,node.misc&NODE_ANON ? text[UNKNOWN_USER]
							: username(&cfg,node.useron,tmp));
				action=NODE_PAGE;
				checkline();
				gettimeleft();
	if(getnodedat(cfg.node_num,&thisnode,true)==0) {
		thisnode.action=action=NODE_PCHT;
		thisnode.aux=n;
		thisnode.misc&=~NODE_LCHAT;
		putnodedat(cfg.node_num,&thisnode);
	}

	if(!online || sys_status&SS_ABORT)
		return;

	if(local) {
		/* If an external sysop chat event handler is installed, just run that and do nothing else */
		if(user_event(EVENT_LOCAL_CHAT))
			return;
	}

	if(((sys_status&SS_USERON && useron.chat&CHAT_SPLITP) || !(sys_status&SS_USERON))
		&& term_supports(ANSI) && rows>=24 && cols>=80)
		sys_status|=SS_SPLITP;
	/*
	if(!(useron.misc&EXPERT))
	*/

	if(!(sys_status&SS_SPLITP)) {
		if(local)
			bprintf(text[SysopIsHere],cfg.sys_op);
		else
			bputs(text[WelcomeToPrivateChat]);
	}

	sprintf(outpath,"%schat.dab",cfg.node_dir);
	if((out=sopen(outpath,O_RDWR|O_CREAT|O_BINARY,SH_DENYNO,DEFFILEMODE))==-1) {
		errormsg(WHERE,ERR_OPEN,outpath,O_RDWR|O_DENYNONE|O_CREAT);
		sprintf(inpath,"%slchat.dab",cfg.node_dir);
		sprintf(inpath,"%schat.dab",cfg.node_path[n-1]);
	if(!fexist(inpath))		/* Wait while it's created for the first time */
	if((in=sopen(inpath,O_RDWR|O_CREAT|O_BINARY,SH_DENYNO,DEFFILEMODE))==-1) {
		errormsg(WHERE,ERR_OPEN,inpath,O_RDWR|O_DENYNONE|O_CREAT);
	if((p=(char *)malloc(PCHAT_LEN))==NULL) {
		close(in);
		close(out);
		errormsg(WHERE,ERR_ALLOC,nulstr,PCHAT_LEN);
	memset(p,0,PCHAT_LEN);
	write(in,p,PCHAT_LEN);
	write(out,p,PCHAT_LEN);
	free(p);
	lseek(in,0L,SEEK_SET);
	lseek(out,0L,SEEK_SET);

	if(getnodedat(cfg.node_num,&thisnode,true)==0) {
		thisnode.misc&=~NODE_RPCHT; 		/* Clear "reset pchat flag" */
		putnodedat(cfg.node_num,&thisnode);
	}
		if(getnodedat(n,&node,true)==0) {
			node.misc|=NODE_RPCHT;				/* Set "reset pchat flag" */
			putnodedat(n,&node); 				/* on other node */
		}

											/* Wait for other node */
											/* to acknowledge and reset */
		while(online && !(sys_status&SS_ABORT)) {
			getnodedat(n,&node,0);
			if(!(node.misc&NODE_RPCHT))
				break;
			getnodedat(cfg.node_num,&thisnode,0);
			if(thisnode.misc&NODE_RPCHT)
				break;
			checkline();
			gettimeleft();
			SYNC;
	}

	action=NODE_PCHT;
	SYNC;

	if(sys_status&SS_SPLITP) {
		lncntr=0;
		CLS;
		remote_y=1;
		bprintf(local ? local_sep : sep
			,thisnode.misc&NODE_MSGW ? 'T':' '
			,sectostr(timeleft,tmp)
			,thisnode.misc&NODE_NMSG ? 'M':' ');

	while(online && (local || !(sys_status&SS_ABORT))) {
		lncntr=0;
		if(sys_status&SS_SPLITP)
			lbuflen=0;
		action=NODE_PCHT;
		activity=0;
		remote_activity=0;
		if((ch=inkey(K_GETSTR,100))!=0) {
			activity=1;
			if(echo)
				attr(cfg.color[clr_chatlocal]);
			if(ch==BS || ch==DEL) {
				if(localchar) {
					if(echo)
					localbuf[localline][localchar]=0; 
				} 
			}
			else if(ch==TAB) {
				if(echo)
					outchar(' ');
				localbuf[localline][localchar]=' ';
				localchar++;
				while(localchar<78 && localchar%8) {
					if(echo)
					localbuf[localline][localchar++]=' '; 
				} 
			}
			else if(ch==CTRL_R) {
				if(sys_status&SS_SPLITP) {
					CLS;
					attr(cfg.color[clr_chatremote]);
					remotebuf[remoteline][remotechar]=0;
					for(i=0;i<=remoteline;i++) {
						bputs(remotebuf[i]); 
						if(i!=remoteline) 
							bputs(crlf);
					}