//------------------------------------------------------------------------ // msgboard.cpp // //------------------------------------------------------------------------ // This File is part of UOX3 // Ultima Offline eXperiment III // UO Server Emulation Program // // Copyright 1999 - 2001 by Unknown real name (Dupois) // // 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., 675 Mass Ave, Cambridge, MA 02139, USA. // //------------------------------------------------------------------------ // UOX3 - Message Board // FILE - msgboard.cpp // DATE - March 16, 1999 // By - Dupois (dupois@home.com) // #include "uox3.h" ////////////////////////////////////////////////////////////////////////////// // LOCAL - GLOBAL DATA ////////////////////////////////////////////////////////////////////////////// // List of message serial numbers ACK'd by client char postAcked[MAXCLIENT][MAXPOSTS][5]; // Total number of posts sent to client int postCount[MAXCLIENT]; // Total number of ACK's received by client int postAckCount[MAXCLIENT]; // Char array for messages to client. Message body (when entering body of post) // can hold a maximum of 1975 chars (approx) unsigned char msg[MAXBUFFER]; // Buffer to be used when posting messages // |Pid|sz1|sz2|mTy|b1 |b2 |b3 |b4 |m1 |m2 |m3 |m4 | unsigned char msg2Post[MAXBUFFER] = "\x71\xFF\xFF\x05\x40\x00\x00\x19\x00\x00\x00\x00"; ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardGetPostType( int s ) // // PURPOSE: Used to retrieve the current post type in order to tell the // user what type of mode they are in. // // PARAMETERS: s Player serial number // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardGetPostType( int s ) { int type = chars[currchar[s]].postType; switch ( type ) { // LOCAL post case LOCALPOST: // Feed back to operator that post type has been changed to LOCAL sysmessage( s, "Currently posting LOCAL messages" ); break; // REGIONAL post case REGIONALPOST: // Feed back to operator that post type has been changed to REGIONAL sysmessage( s, "Currently posting REGIONAL messages" ); break; // GLOBAL POST case GLOBALPOST: // Feed back to operator that post type has been changed to GLOBAL sysmessage( s, "Currently posting GLOBAL messages" ); break; // Invalid post type default: // Feed back to operator that post type has been changed to LOCAL sysmessage( s, "Invalid post type" ); break; } return; } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardSetPostType( int s, int Type ) // // PURPOSE: Used to set the postType for the current user (Typically a GM) // There is a local array that holds each players curreny posting // type. Using the command to set the post type updates the // value in the array for that player so that they can post // different types of messages. Array can hold any of the // following: // // Type = 0 = LOCAL // 1 = REGIONAL // 2 = GLOBAL // // PARAMETERS: s Player serial number // Type Type of post // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardSetPostType( int s, int Type ) { chars[currchar[s]].postType = Type; switch ( Type ) { // LOCAL post case LOCALPOST: // Feed back to operator that post type has been changed to LOCAL sysmessage( s, "Post type set to LOCAL" ); break; // REGIONAL post case REGIONALPOST: // Feed back to operator that post type has been changed to REGIONAL sysmessage( s, "Post type set to REGIONAL" ); break; // GLOBAL POST case GLOBALPOST: // Feed back to operator that post type has been changed to GLOBAL sysmessage( s, "Post type set to GLOBAL" ); break; // Invalid post type default: // Feed back to operator that post type has been changed to LOCAL sysmessage( s, "Invalid post type" ); break; } return; } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardOpen( int s ) // // PURPOSE: Called when player dbl-clicks on a Message Board thereby // requesting a list of messages posted on the board. // // PARAMETERS: s Player serial number // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardOpen(int s) { // In Response to a doubleclick() message from a client // [SEND:5] 06 40 07 ba 3d // Message to client |Pak|sz1|sz2|mTy|sn1|sn2|sn3|sn4| b | u | l | l | e | t | i | n | | b | o | a | r | d |<------------ Unknown, don't care right now ----------------->| char msgBoardHeader[] = "\x71\x00\x26\x00\xFF\xFF\xFF\xFF\x62\x75\x6c\x6c\x65\x74\x69\x6e\x20\x62\x6f\x61\x72\x64\x00\x00\x00\x00\x00\x00\x00\x00\x40\x20\x00\xff\x00\x00\x00\x00"; // Extract the Bulletin Board serial number from client buffer and update msgBoardHeader msgBoardHeader[4] = buffer[s][1]; // From doubleclick() buffer[s] msgBoardHeader[5] = buffer[s][2]; msgBoardHeader[6] = buffer[s][3]; msgBoardHeader[7] = buffer[s][4]; // Can place up to 20 customizable chars in Message Board header to give Message Board a unique name // Might be able to do more, but why, it usually overruns the area designated for the name anyway int msgBoardSerial; msgBoardSerial = calcItemFromSer(buffer[s][1], buffer[s][2], buffer[s][3], buffer[s][4]); // If the name the item (Bulletin Board) has been defined, display it // instead of the default "Bulletin Board" title. if ( strcmp(items[msgBoardSerial].name, "#") ) strncpy( &msgBoardHeader[8], items[msgBoardSerial].name, 20); // Send Message Board header to client Network->xSend(s, msgBoardHeader, (sizeof(msgBoardHeader)-1), 0); // Send draw item message to client with required info to draw the message board // Base size plus however many messages are in the list // Example: // s1 s2 s3 s4 m# m# ?? st st xx xx yy yy b1 b2 b3 b4 c1 c2 // 40 1c 53 eb 0e b0 00 00 00 00 3a 00 3a 40 07 ba 3d 00 00 Size = 19 (min size for msg = 24 bytes) // Standard header : |Pak|sz1|sz2|sg1|sg2|<--------------- See above -------------------> | // "\x3c\x00\x18\x00\x01\x40\x1c\x53\xeb\x0e\xb0\x00\x00\x00\x00\x3a\x00\x3a\x40\x00\x00\x19\x00\x00"; // Read bbi file to determine messages on boards list // Get Message Board serial number from message buffer FILE *file = NULL; // 50 chars for prefix and 4 for the extension plus the ending NULL char fileName[256] = ""; char fileName1[55]; char fileName2[55]; char fileName3[55]; msg[0] = 0x3c; // Packet type (Items in Container) msg[1] = 0x00; // High byte of packet size msg[2] = 0x00; // Low byte of packet size msg[3] = 0x00; // High byte of number of items msg[4] = 0x00; // Low byte of number of items int offset = 5; // Offset to next msg[] value int count = 0; // Number of messages (times through while loop) int currentFile = 1; // Starting file to open and iterate through (1=GLOBAL.bbp, 2=REGIONAL.bbp, 3=LOCAL.bbp) // Determine what type of message this is in order to determine which file to open // GLOBAL Posts start at 01 00 00 00 -> 01 FF FF FF // REGIONAL Posts start at 02 00 00 00 -> 02 FF FF FF // LOCAL Posts start at 03 00 00 00 -> 03 FF FF FF // GLOBAL post file strcpy( fileName1, "global.bbi" ); // REGIONAL post file //sprintf( fileName2, "region%s.bbi", region[calcRegionFromXY(items[msgBoardSerial].x, items[msgBoardSerial].y)].name ); sprintf( fileName2, "region%d.bbi", calcRegionFromXY(items[msgBoardSerial].x, items[msgBoardSerial].y) ); // LOCAL post file sprintf( fileName3, "%02x%02x%02x%02x.bbi", buffer[s][1], buffer[s][2], buffer[s][3], buffer[s][4]); while ( currentFile <= 3 ) { // If a MSBBOARDPATH has been define in the SERVER.SCP file, then use it if (server_data.msgboardpath) strcpy( fileName, server_data.msgboardpath ); // Open the next file to process switch ( currentFile ) { case 1: // Start with the GLOBAL.bbp file first //sysmessage( s, "Opening GLOBAL.bbi messages"); strcat( fileName, fileName1 ); file = fopen( fileName, "rb" ); break; case 2: // Set fileName to REGIONAL.bbi //sysmessage( s, "Opening REGIONAL.bbi messages"); strcat( fileName, fileName2 ); file = fopen( fileName, "rb" ); break; case 3: // Set fileName to LOCAL.bbi //sysmessage( s, "Opening LOCAL.bbi messages"); strcat( fileName, fileName3 ); file = fopen( fileName, "rb" ); break; default: printf("UOX3: MsgBoardOpen() Unhandle case value: %d", currentFile); return; } // If the file doesn't exist, increment the currenFile count and move onto the next file if ( file != NULL ) { // Ignore first 4 bytes of bbi file as this is reserverd for the current max message serial number being used if ( fseek( file, 4, SEEK_SET ) ) { printf("UOX3: MsgBoardOpen() failed to seek to first message segment in bbi file\n"); return; } // Loop until we have reached the end of the file or the maximum number of posts allowed // to be displayed while ( (!feof(file)) && (count<=MAXPOSTS) ) { // Fill up the msg with data from the bbi file if ( fread( &msg[offset], sizeof(char), 19, file ) != 19 ) { if ( feof(file) ) break; } // |Off| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // |mg1|mg2|mg3|mg4|mo1|mo2|???|sg1|sg2|xx1|xx2|yy1|yy2|bn1|bn2|bn3|bn4|co1|co2| // "\x40\x1c\x53\xeb\x0e\xb0\x00\x00\x00\x00\x3a\x00\x3a\x40\x00\x00\x19\x00\x00"; // If the segment 6 is 0x00 then the message is marked for deletion so skip it if ( msg[offset+6] ) { // Set the Board SN fields the proper value for the board clicked on msg[offset+13] = buffer[s][1]; msg[offset+14] = buffer[s][2]; msg[offset+15] = buffer[s][3]; msg[offset+16] = buffer[s][4]; // Store message ID into array for later acknowledgement postAcked[s][count][0] = msg[offset+0]; postAcked[s][count][1] = msg[offset+1]; postAcked[s][count][2] = msg[offset+2]; postAcked[s][count][3] = msg[offset+3]; // Increment the offset by 19 bytes for next message index offset += 19; // Increment the message count count++; } } } // Close the current bbi file if( file ) fclose( file ); // Increment to the next file currentFile++; } // Close bbi file if( file ) fclose( file ); // Update size fields of message with new values msg[1] = (unsigned char)(offset>>8); msg[2] = (unsigned char)(offset%256); msg[3] = (unsigned char)(count>>8); msg[4] = (unsigned char)(count%256); // Set global variable that holds the count of the number of posts being sent // to this particular client postCount[s] = count; // Set the postAckCount to zero in preparation of the client ACKing the message // about to be sent postAckCount[s] = 0; // Send Draw Item message to client Network->xSend( s, msg, offset, 0 ); } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardList( int s ) // // PURPOSE: After Bulletin Board is displayed and client ACK's all posted // items this is called to send the details of the posted item to // the Bulletin Board so that it can be listed on the board. // // PARAMETERS: s Player serial number // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardList( int s ) { // READ IN bbp FILE (for list on message board) // Client sends: // Example [SEND:12] 71 00 0c 04 40 07 ba 3d 40 1c 53 eb // // Server responds with: // Sample Response format for message board message list item // |sA|au|sS| Subject |sD| Date | //[RECV:85] 71 00 55 01 40 07 ba 3d 40 1c 53 eb 00 00 00 00 01 00 33 46 6f 58 20 74 68 65 20 44 65 6d 65 6e 74 65 64 3a 20 20 34 30 30 30 67 6f 6c 64 2e 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 00 0e 44 61 79 20 33 20 40 20 32 33 3a 32 30 00 // 50 chars for prefix and 4 for the extension plus the ending NULL char fileName[256] = ""; char fileName1[55]; // for global file char fileName2[55]; // for regional file char fileName3[55]; // for local file FILE *file = NULL; int msgBytes = 0; // Number of bytes to send to client (message size) int msgOffset = 0; // Total number of bytes between messages from start of file unsigned int segmentSize = 0; // Size of a segment (Author, Subject, Date) int foundMsg = 0; // Flag when message has been found int boardSN = 0; // Bulletin Boards serial number (to determine what regions messages to display int currentFile = 1; // Starting file to open and iterate through (1=GLOBAL.bbp, 2=REGIONAL.bbp, 3=LOCAL.bbp) int w = 0; // Counter int x = 0; // Counter // Determine what type of message this is in order to determine which file to open // GLOBAL Posts start at 01 00 00 00 -> 01 FF FF FF // REGIONAL Posts start at 02 00 00 00 -> 02 FF FF FF // LOCAL Posts start at 03 00 00 00 -> 03 FF FF FF // Determine the Bulletin Boards serial number boardSN = calcItemFromSer(buffer[s][4], buffer[s][5], buffer[s][6], buffer[s][7]); // GLOBAL post file strcpy( fileName1, "global.bbp" ); // REGIONAL post file // sprintf( fileName2, "%s.bbp", region[calcRegionFromXY(items[boardSN].x, items[boardSN].y)].name ); sprintf( fileName2, "region%d.bbp", calcRegionFromXY(items[boardSN].x, items[boardSN].y) ); // LOCAL post file sprintf( fileName3, "%02x%02x%02x%02x.bbp", buffer[s][4], buffer[s][5], buffer[s][6], buffer[s][7]); // Open the bbp file for read //file = fopen( fileName3, "rb"); //if ( file == NULL ) return; // Put file not found error control here //if ( feof(file) ) return; // Put end of file error control here while ( currentFile <= 3 ) { // If a MSBBOARDPATH has been define in the SERVER.SCP file, then use it if (server_data.msgboardpath) strcpy( fileName, server_data.msgboardpath ); // Open the next file to process switch ( currentFile ) { case 1: // Start with the GLOBAL.bbp file first //sysmessage( s, "Opening GLOBAL.bbp messages"); strcat( fileName, fileName1 ); file = fopen( fileName, "rb"); break; case 2: // Set fileName to REGIONAL.bbi //sysmessage( s, "Opening REGIONAL.bbp messages"); strcat( fileName, fileName2 ); file = fopen( fileName, "rb" ); break; case 3: // Set fileName to LOCAL.bbi //sysmessage( s, "Opening LOCAL.bbp messages"); strcat( fileName, fileName3 ); file = fopen( fileName, "rb" ); break; default: printf("UOX3: MsgBoardOpen() Unhandle case value: %d", currentFile); return; } msgOffset = 0; // If the file doesn't exist, increment the currenFile count and move onto the next file if ( file != NULL ) { while ( w>8); msg[2] = (unsigned char)(msgBytes%256); // Set packet 0x71 message type to 0x01 (send post item to message board list) msg[3] = 1; // Send message to client Network->xSend( s, msg, msgBytes, 0 ); } else // If this isn't the message were looking for, jump ahead to next message { // Since we didn't find the message in this pass, get this messages size and jump ahead msgOffset += (msg[1]<<8) + msg[2]; // Jump to next message if ( fseek(file, msgOffset, SEEK_SET) ) { printf("UOX3: MsgBoardEvent() case 4 : failed to seek next message\n"); break; } } } // End of Inner while loop (redundant but safe -- for now) // If we broke out of the loop because EOF was reached then break out again if( feof( file ) ) { fclose(file); break; } }// End of Outer while loop }// End of if block // Close the current file and increment the currentFile counter if ( file ) fclose( file ); // Increment the current file counter currentFile++; }// End of While loop // If we still have 'file' open, close 'file' if ( file ) fclose( file ); } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardGetMaxMsgSN( int msgType, int autoPost=0 ) // // PURPOSE: Used during posting operation to determine the current maximum // message serial number used on the board. It then increments // the number and updates the bbi file. The new message serial // number is returned. // // PARAMETERS: msgType Type of post (LOCAL, REGIONAL, GLOBAL) // autoPost 0 = message posted by a user (default) // 1 = message posted by system // // RETURNS: int 0 = Failed to get maximum serial number // 1 = Found and updated maximum serial number ////////////////////////////////////////////////////////////////////////////// int MsgBoardGetMaxMsgSN( int msgType, int autoPost=0 ) { FILE *pFile = NULL; // 50 chars for prefix and 4 for the extension plus the ending NULL char fileName[256] = ""; char msgbbiSegment[20] = "\x00\x00\x00\x00\x0e\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; char maxMsgSN[5] = ""; int msgBoardSerial = 0; int maxSN = 0; struct tm timeOfPost; time_t now; // If a MSBBOARDPATH has been define in the SERVER.SCP file, then use it if (server_data.msgboardpath) strcpy( fileName, server_data.msgboardpath ); switch ( msgType ) { // LOCAL post case LOCALPOST: // Get Message Board serial number from message buffer sprintf( temp, "%02x%02x%02x%02x.bbi", msg2Post[4], msg2Post[5], msg2Post[6], msg2Post[7]); break; // REGIONAL post case REGIONALPOST: // set the Message Board fileName to the proper region number if ( autoPost ) { sprintf( temp, "region%d.bbi", chars[calcCharFromSer(msg2Post[4], msg2Post[5], msg2Post[6], msg2Post[7])].region ); } else { msgBoardSerial = calcItemFromSer(msg2Post[4], msg2Post[5], msg2Post[6], msg2Post[7]); sprintf( temp, "region%d.bbi", calcRegionFromXY(items[msgBoardSerial].x, items[msgBoardSerial].y) ); } break; // GLOBAL POST case GLOBALPOST: strcpy( temp, "global.bbi" ); break; // Invalid post type default: printf("UOX3: MsgBoardGetMaxMsgSN() Invalid post type, aborting post\n"); return 0; } // Append file name to path strcat( fileName, temp ); // Get the current maximum message s/n from the bbi file pFile = fopen( fileName, "rb" ); if ( pFile == NULL ) { printf("UOX3: MsgBoardGetMaxMsgSN() bbi not found. Creating file %s\n", fileName ); // Default to serial number 0 maxSN = 0; } else // bbi file exists so read in first 4 bytes to get current maximum serial number { // Get the first 4 bytes from each message index segment in the bbi file if ( fread(maxMsgSN, sizeof(char), 4, pFile) != 4 ) { printf("UOX3: MsgBoardGetMaxMsgSN() Could not get MaxSN from %s\n", fileName ); fclose( pFile ); return 0; } // Calculate the maxSN in decimal maxSN = (maxMsgSN[0]<<24) + (maxMsgSN[1]<<16) + (maxMsgSN[2]<<8) + maxMsgSN[3]; // Increment maxSN to new value maxSN++; } // Done retrieving maxMsgSN so close bbi file if ( pFile ) fclose( pFile ); // If the maxSN == 0 then the file does not exist yet so create the file if ( maxSN == 0 ) { pFile = fopen( fileName, "ab+" ); if ( pFile == NULL ) { printf("UOX3: MsgBoardGetMaxMsgSN() Error creating bbi file, aborting post\n"); return 0; } else { // GLOBAL Posts start at 01 00 00 00 -> 01 FF FF FF // REGIONAL Posts start at 02 00 00 00 -> 02 FF FF FF // LOCAL Posts start at 03 00 00 00 -> 03 FF FF FF switch ( msgType ) { // LOCAL post case LOCALPOST: // Write out the serial number as 4 bytes // Write 03 00 00 00 to bbi file (can't start at 00 00 00 00 because client crashes if this is true) if ( fwrite("\x03\x00\x00\x00", sizeof(char), 4, pFile) != 4 ) { printf("UOX3: MsgBoardGetMaxMsgSN() Error writing to bbi file, aborting post\n"); fclose( pFile ); return 0; } // Set maxSN to its startup default maxSN = 0x3000000; break; // REGIONAL post case REGIONALPOST: // Write 02 00 00 00 to bbi file (can't start at 00 00 00 00 because client crashes if this is true) if ( fwrite("\x02\x00\x00\x00", sizeof(char), 4, pFile) != 4 ) { printf("UOX3: MsgBoardGetMaxMsgSN() Error writing to bbi file, aborting post\n"); fclose( pFile ); return 0; } // Set maxSN to its startup default maxSN = 0x2000000; break; // GLOBAL POST (any other value) case GLOBALPOST: // Write 01 00 00 00 to bbi file (can't start at 00 00 00 00 because client crashes if this is true) if ( fwrite("\x01\x00\x00\x00", sizeof(char), 4, pFile) != 4 ) { printf("UOX3: MsgBoardGetMaxMsgSN() Error writing to bbi file, aborting post\n"); fclose( pFile ); return 0; } // Set maxSN to its startup default maxSN = 0x1000000; break; // Invalid post type default: printf("UOX3: MsgBoardGetMaxMsgSN() Invalid post type, aborting post\n"); fclose( pFile ); return 0; } } } else { // File must have existed already if maxSN > 0 so open the file as read/write pFile = fopen( fileName, "rb+" ); if ( pFile == NULL ) { printf("UOX3: MsgBoardGetMaxMsgSN() Failed to create bbi file, aborting post\n"); return 0; } else { // Set file pointer to BOF if ( fseek(pFile, 0, SEEK_SET) ) { printf("UOX3: MsgBoardGetMaxMsgSN() Failed to set pFile to BOF in bbi file\n"); fclose( pFile ); return 0; } else { // Convert maxSN to an char array maxMsgSN[0] = (unsigned char)(maxSN>>24); maxMsgSN[1] = (unsigned char)(maxSN>>16); maxMsgSN[2] = (unsigned char)(maxSN>>8); maxMsgSN[3] = (unsigned char)(maxSN%256); // Write out new maxSN for this post if ( fwrite( maxMsgSN, sizeof(char), 4, pFile) != 4 ) { printf("UOX3: MsgBoardGetMaxMsgSN() Error writing to bbi file, aborting post\n"); fclose( pFile ); return 0; } // Now jump to EOF to write next msgbbiSegment info if ( fseek(pFile, 0, SEEK_END) ) { printf("UOX3: MsgBoardGetMaxMsgSN() Failed to set pFile to EOF in bbi file\n"); fclose( pFile ); return 0; } } } } // Set bytes to proper values in bbi message array msgbbiSegment[0] = (unsigned char)(maxSN>>24); // Message ID 1 msgbbiSegment[1] = (unsigned char)(maxSN>>16); // Message ID 2 msgbbiSegment[2] = (unsigned char)(maxSN>>8); // Message ID 3 msgbbiSegment[3] = (unsigned char)(maxSN%256); // Message ID 4 msgbbiSegment[6] = msg2Post[3]; // 05 = user posted message, 0xFF and lower is a quest post (0xFF is escort quest) // Calculate current time and date ( for later bulletin board maintenance routine ) time( &now ); timeOfPost = *localtime( &now ); msgbbiSegment[7] = (timeOfPost.tm_yday+1)/256; msgbbiSegment[8] = (timeOfPost.tm_yday+1)%256; // If this is an autoPost then set the CHAR or ITEM serial number in order to mark it for deletion // after the quest is done. if ( autoPost ) { msgbbiSegment[13] = msg2Post[4]; // CHAR or ITEM SN1 msgbbiSegment[14] = msg2Post[5]; // CHAR or ITEM SN2 msgbbiSegment[15] = msg2Post[6]; // CHAR or ITEM SN3 msgbbiSegment[16] = msg2Post[7]; // CHAR or ITEM SN4 } // GLOBAL Posts start at 01 00 00 00 -> 01 FF FF FF // REGIONAL Posts start at 02 00 00 00 -> 02 FF FF FF // LOCAL Posts start at 03 00 00 00 -> 03 FF FF FF switch ( msgType ) { // LOCAL post case LOCALPOST: // Check to see if Maximum number of posts have been reached //if ( ((maxSN-0x03000000) >= MAXPOSTS) || (maxSN >= 0xFFFFFFFF) ) if ( maxSN >= 0x03FFFFFF ) { printf("UOX3: MsgBoardGetMaxMsgSN() Max posts reached in %s\n", fileName ); fclose( pFile ); return 0; } break; // REGIONAL post case REGIONALPOST: // Check to see if Maximum number of posts have been reached //if ( ((maxSN-0x02000000) >= MAXPOSTS) || (maxSN >= 0x02FFFFFF) ) if ( maxSN >= 0x02FFFFFF ) { printf("UOX3: MsgBoardGetMaxMsgSN() Max posts reached in %s\n", fileName ); fclose( pFile ); return 0; } break; // GLOBAL POST (any other value) case GLOBALPOST: // Check to see if Maximum number of posts have been reached //if ( ((maxSN-0x01000000) >= MAXPOSTS) || (maxSN >= 0x01FFFFFF) ) if ( maxSN >= 0x01FFFFFF ) { printf("UOX3: MsgBoardGetMaxMsgSN() Max posts reached in %s\n", fileName ); fclose( pFile ); return 0; } break; // Invalid post type default: printf("UOX3: MsgBoardGetMaxMsgSN() Invalid post type, aborting post\n"); fclose( pFile ); return 0; } // Write out bbi message array to file if ( fwrite(msgbbiSegment, sizeof(char), (sizeof(msgbbiSegment)-1), pFile) != (sizeof(msgbbiSegment)-1) ) { printf("UOX3: MsgBoardGetMaxMsgSN() Error writing to bbi file, aborting post\n"); fclose( pFile ); return 0; } // Close bbi file for the final time fclose( pFile ); // Return int value of new posts serial number return maxSN; } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardPost( int s, int msgType, int autoPost ) // // PURPOSE: Called when a user clicks on the Post button after typing in a // message or the server is creating a quest and wants to post // a message to a regional board // if message posted by a user, then autoPost = 0 // if message posted by system, then autoPost = 1 // // PARAMETERS: s Players/NPC serial number // msgType Type of post (LOCAL, REGIONAL, GLOBAL) // autoPost 0 = message posted by a user // 1 = message posted by system // // RETURNS: int 0 = Failed to post message // 1 = Post successful ////////////////////////////////////////////////////////////////////////////// int MsgBoardPost( int s, int msgType, int autoPost ) { // WRITE FILE OUT (POST MESSAGE) // 50 chars for prefix and 4 for the extension plus the ending NULL char fileName[256] = ""; FILE *pFile = NULL; int origMsgSize = 0; int newMsgSize = 0; int newMsgSN = 0; int maxMsgSN = 0; int isReply = 0; int msgBoardSerial = 0; int x, y, z, pos, offset; char msgHeader[17] = ""; char msgSubject[257] = ""; char msgBody[MAXBUFFER] = ""; char msgAuthor[52] = ""; // Maximum name size from char_st (Size + Name) char msgDate[17] = ""; // Maximum date size based on Size + "Day ### @ hh:mm" format struct tm timeOfPost; time_t now; // If this is a users post (done from the client) then copy the client buffer[s] into our buffer if ( !autoPost ) { memcpy( msg2Post, buffer[s], (buffer[s][1]*256 + buffer[s][2]) ); // Determine what type of post this is supposed to be and then set the proper file name // Also, if this is a reply to a base post, then abort posting if the reply is to a // GLOBAL or REGIONAL message. No one can reply to GLOBAL or REGIONAL messages as they // as for informational purposes only (discussions should be taken offline). There is no // reason to reply to a quest post execpt to fill up the message board. isReply = (msg2Post[8]*16777216) + (msg2Post[9]*65536) + (msg2Post[10]*256) + msg2Post[11]; // If this is a reply to anything other than a LOCAL post, abort if ( (isReply>0) && (isReply<0x03000000) ) { #ifdef DEBUG printf("UOX3: MsgBoardPost() Attempted reply to a global or regional post\n"); #endif sysmessage( s, "You can not reply to global or regional posts" ); return 0; } } // If everything passed the check above then, // Determine the new messages serial number for the type of post being done maxMsgSN = MsgBoardGetMaxMsgSN( msgType, autoPost ); // If the value returned is zero, then abort the posting if ( maxMsgSN == 0 ) { printf("UOX3: MsgBoardPost() Could not retrieve a valid message serial number\n"); sysmessage( s, "Post failed!" ); return 0; } // If a MSBBOARDPATH has been define in the SERVER.SCP file, then use it if (server_data.msgboardpath) strcpy( fileName, server_data.msgboardpath ); switch ( msgType ) { // LOCAL post case LOCALPOST: // Get Message Board serial number from message buffer sprintf( temp, "%02x%02x%02x%02x.bbp", msg2Post[4], msg2Post[5], msg2Post[6], msg2Post[7]); break; // REGIONAL post case REGIONALPOST: // set the Message Board fileName to the proper region number if ( autoPost ) { sprintf( temp, "region%d.bbp", chars[calcCharFromSer(msg2Post[4], msg2Post[5], msg2Post[6], msg2Post[7])].region ); } else { msgBoardSerial = calcItemFromSer(msg2Post[4], msg2Post[5], msg2Post[6], msg2Post[7]); sprintf( temp, "region%d.bbp", calcRegionFromXY(items[msgBoardSerial].x, items[msgBoardSerial].y) ); } break; // GLOBAL POST case GLOBALPOST: strcpy( temp, "global.bbp" ); break; // Invalid post type default: printf("UOX3: MsgBoardPost() Invalid post type, aborting post\n"); sysmessage( s, "Invalid post type!" ); return 0; } // Append file name to end of path strcat( fileName, temp ); // Open the file for appending pFile=fopen( fileName, "ab+"); // If we couldn't open the file, send an error message to client if ( pFile == NULL ) { printf("UOX3: MsgBoardPost() Unable to open bbp file, aborting post\n"); return 0; } // Calculate original size of the message that the client sent to us origMsgSize = ( (buffer[s][1]<<8) + buffer[s][2] ); // Get the messages header info (packet type, size, type, board S/N, parent msg S/N(replies only)) for ( x=0; x<12; x++ ) msgHeader[x] = msg2Post[x]; // Set new messages serial number to maxMsgSN from the bbi file msgHeader[8] = (unsigned char)(maxMsgSN>>24); msgHeader[9] = (unsigned char)(maxMsgSN>>16); msgHeader[10] = (unsigned char)(maxMsgSN>>8); msgHeader[11] = (unsigned char)(maxMsgSN%256); // Get the new messages serial number (which is its post position on the board- anything other than 00 00 00 00 // (base post) is a reply to a specific message ID ) newMsgSN = (buffer[s][8]<<24) + (buffer[s][9]<<16) + (buffer[s][10]<<8) + buffer[s][11]; // If the newMsgSN is 0 then it is a base post, other wise it is a reply to a previous post if ( newMsgSN ) { // Create the proper parent message ID segment for the new post msgHeader[12] = msg2Post[8]; msgHeader[13] = msg2Post[9]; msgHeader[14] = msg2Post[10]; msgHeader[15] = msg2Post[11]; } else { // If this isn't a reply post, the parent message SN fields should be 0x00 msgHeader[12] = 0; msgHeader[13] = 0; msgHeader[14] = 0; msgHeader[15] = 0; } newMsgSize = origMsgSize + 4; // added 4 bytes for parent msg ID // This is the position within the msg2Post[] array pos = x; // Should equal to 12 (pointing to size of Subject field) // Get the messages subject info (size, subject) y = msg2Post[pos]; // get the size of the subject for ( x=0; x<=y; x++ ) // Do while we get all bytes msgSubject[x] = msg2Post[pos+x]; // get the subject message (size and data) pos += x; // Get the messages body info (body section, body size, body data) z = msg2Post[pos]; // Total number of NULL's in Body of message offset = 0; // Check if body of post is empty (NULL) if ( z ) { // Get the FIRST Body segment size + 2 for pre segment size bytes y = msg2Post[pos+1] + 2; // Loop until number of remaining NULLS equal zero while (z) { // Store Body segment into msgBody ( continue until NULL reached ) for ( x=0; x>8); msgHeader[2] = (unsigned char)(newMsgSize%256); fwrite( msgHeader, sizeof(char), (sizeof(msgHeader)-1), pFile ); // Write out the msgAuthor fwrite( msgAuthor, sizeof(char), (msgAuthor[0]+1), pFile ); // Write out the msgSubject fwrite( msgSubject, sizeof(char), (msgSubject[0]+1), pFile ); // Write out the msgDate fwrite( msgDate, sizeof(char), (msgDate[0]+1), pFile ); // Write out the msgBody fwrite( msgBody, sizeof(char), offset, pFile ); // Close the file fclose( pFile ); // if this was a user post, then immediately update the message board with the newly created message if ( !autoPost ) { // Add item |s1|s2|s3|s4|m1|m2|??|#i|#i|x1|x2|y1|y2|b1|b2|b3|b4|c1|c2 //[RECV:20] 25 40 2b 38 1a 0e b0 00 00 00 00 00 00 00 40 07 ba 3d 00 00 char addItem[21] = "\x25\x00\x00\x00\x00\x0e\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // Insert posts serial number into addItem[] packet addItem[1] = msgHeader[8]; addItem[2] = msgHeader[9]; addItem[3] = msgHeader[10]; addItem[4] = msgHeader[11]; // Insert posts bulleting board serial number into addItem[] packet addItem[14] = msgHeader[4]; addItem[15] = msgHeader[5]; addItem[16] = msgHeader[6]; addItem[17] = msgHeader[7]; // Setup buffer to expect to receive an ACK from the client for this posting postCount[s] = 1; postAckCount[s] = 0; postAcked[s][0][0] = msgHeader[8]; postAcked[s][0][1] = msgHeader[9]; postAcked[s][0][2] = msgHeader[10]; postAcked[s][0][3] = msgHeader[11]; // Send "Add Item to Container" message to client Network->xSend( s, addItem, 20, 0); } // Return success return 1; } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardOpenPost( int s ) // // PURPOSE: Opens a posting when double-clicked in order to view the // full message. // // PARAMETERS: s Players serial number // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardOpenPost( int s ) { // READ IN bbp FILE (Client dbl-clicked on posted message on message board list) // Get Message Board serial number from message buffer // 50 chars for prefix and 4 for the extension plus the ending NULL char fileName[256] = ""; FILE *file = NULL; int msgSN = 0; int msgBoardSerial = 0; int msgBytes = 0; int dateBytes = 0; int authorBytes = 0; int subjectBytes = 0; int foundMsg = 0; int x, y, z; // Calculate the messages SN to determine which file to open // GLOBAL Posts start at 01 00 00 00 -> 01 FF FF FF // REGIONAL Posts start at 02 00 00 00 -> 02 FF FF FF // LOCAL Posts start at 03 00 00 00 -> 03 FF FF FF // If a MSBBOARDPATH has been define in the SERVER.SCP file, then use it if (server_data.msgboardpath) strcpy( fileName, server_data.msgboardpath ); msgSN = (buffer[s][8]*16777216) + (buffer[s][9]*65536) + (buffer[s][10]*256) + buffer[s][11]; // Is msgSN within the GLOBAL post range if ( (msgSN>=0x01000000) && (msgSN<=0x01FFFFFF) ) { #ifdef DEBUG sysmessage( s, "Opening GLOBAL.bbp posting"); #endif strcat( fileName, "global.bbp" ); file = fopen( fileName, "rb" ); } // Is msgSN within the REGIONAL post range else if ( (msgSN>=0x02000000) && (msgSN<=0x02FFFFFF) ) { #ifdef DEBUG sysmessage( s, "Opening REGIONAL.bbp posting"); #endif msgBoardSerial = calcItemFromSer(buffer[s][4], buffer[s][5], buffer[s][6], buffer[s][7]); sprintf( temp, "region%d.bbp", calcRegionFromXY(items[msgBoardSerial].x, items[msgBoardSerial].y) ); strcat( fileName, temp ); file = fopen( fileName, "rb" ); } // Is msgSN within the LOCAL post range else if ( (msgSN>=0x03000000) && (msgSN<=0xFFFFFFFF) ) { #ifdef DEBUG sysmessage( s, "Opening LOCAL.bbp posting"); #endif sprintf( temp, "%02x%02x%02x%02x.bbp", buffer[s][4], buffer[s][5], buffer[s][6], buffer[s][7]); strcat( fileName, temp ); file = fopen( fileName, "rb" ); } // This msgSN does not fall within a valid range else { printf("UOX3: MsgBoardOpenPost() Invalid message SN: %02x%02x%02x%02x", buffer[s][8], buffer[s][9], buffer[s][10], buffer[s][11] ); sysmessage( s, "Post not valid, please notify GM"); return; } if (file == NULL) return; // Put file not found error control here if (feof(file)) return; // Put end of file error control here // Find Message ID that has been requested while ( !foundMsg ) { // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // Read In the first 12 bytes |PacketID|Size1|Size2|MsgType|bSn1|bSn2|bSn3|bSn4|mSn1|mSn2|mSn3|mSn4|pmSN|pmSN|pmSN|pmSN| for ( x=0; x<12; x++) msg[x] = fgetc( file ); // If we have reached the EOF then stop searching if ( feof(file) ) { printf("UOX3: MsgBoardEvent() case 3: message not found \n"); break; } // Find post that was ACK'd by client if (( msg[8] == buffer[s][8] ) && ( msg[9] == buffer[s][9] ) && ( msg[10] == buffer[s][10]) && ( msg[11] == buffer[s][11])) { // Don't forget to set the flag to stop searching for the message when we find it foundMsg = 1; // Jump ahead 4 bytes in bbp file to skip // the parent message serial number section as it is not required if ( fseek(file, 4, SEEK_CUR) ) printf("UOX3: MsgBoardEvent() case 3 : failed to seek Author segment\n"); // Read in author, subject and date info to pass back to client (DO NOT SEND BODY of msg) // Count the total number of bytes in posting (not including body as it isn't sent to client) msgBytes = x; // Get size of Author segment msg[msgBytes] = fgetc( file ); if ( feof(file) ) return; msgBytes++; // Store size of Author segment authorBytes = msg[msgBytes-1]; // Fill in msg[] with Author data for ( x=0; x>8); msg[2] = (unsigned char)(msgBytes%256); msg[3] = 2; // Set packet 0x71 message type to 0x02 (send full message) } else // If this isn't the message were looking for, jump ahead to next message { // Since we didn't find the message in this pass, get this messages size and jump ahead msgBytes += (msg[1]*256) + msg[2]; // Jump to next message if ( fseek(file, msgBytes, SEEK_SET) ) printf("UOX3: MsgBoardEvent() case 3 : failed to seek next message\n"); } }// End of while loop // Close bbp file and return fclose(file); // Send message to client Network->xSend(s, msg, msgBytes, 0); } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardRemovePost( int s ) // // PURPOSE: Marks the posting for a specific message for deletion, thereby // removing it from the bulletin boards viewable list. // // PARAMETERS: s Serial number of the player removing the post // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardRemovePost( int s ) { // Sample REMOVE POST message from client // | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11| // |p#|s1|s2|mt|b1|b2|b3|b4|m1|m2|m3|m4| // Client sends 71 0 c 6 40 0 0 18 1 0 0 4 // Read bbi file to determine messages on boards list // Find the post and mark it for deletion // thereby removing it from the bulletin boards list // int s = calcSerFromChar( serial ); FILE *file = NULL; // 50 chars for prefix and 4 for the extension plus the ending NULL char fileName[256] = ""; int msgSN = 0; int msgBoardSN = 0; // Get the integer value of the message serial number msgSN = (buffer[s][8]*16777216) + (buffer[s][9]*65536) + (buffer[s][10]*256) + buffer[s][11]; // Calculate the Bulletin Boards serial number msgBoardSN = calcItemFromSer(buffer[s][4], buffer[s][5], buffer[s][6], buffer[s][7]); // Switch depending on what type of message this is: // GLOBAL = 0x01000000 -> 0x01FFFFFF // REGION = 0x02000000 -> 0x02FFFFFF // LOCAL = 0x03000000 -> 0x03FFFFFF switch ( buffer[s][8] ) { case 0x01: { // GLOBAL post file strcpy( temp, "global.bbi" ); break; } case 0x02: { // REGIONAL post file sprintf( temp, "region%d.bbi", calcRegionFromXY(items[msgBoardSN].x, items[msgBoardSN].y) ); break; } default: { // LOCAL post file sprintf( temp, "%02x%02x%02x%02x.bbi", buffer[s][4], buffer[s][5], buffer[s][6], buffer[s][7]); break; } } // If a MSBBOARDPATH has been define in the SERVER.SCP file, then use it if (server_data.msgboardpath) strcpy( fileName, server_data.msgboardpath ); // Create the full path to the file we need to open strcat( fileName, temp ); file = fopen( fileName, "rb+" ); // If the file exists continue, othewise abort with an error if ( file != NULL ) { // Ignore first 4 bytes of bbi file as this is reserverd for the current max message serial number being used if ( fseek( file, 4, SEEK_SET ) ) { printf("UOX3: MsgBoardRemovePost() failed to seek first message seg in bbi\n"); sysmessage( s, "Failed to find post to be removed." ); return; } // Loop until we have reached the end of the file while ( !feof(file) ) { // | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // |mg1|mg2|mg3|mg4|mo1|mo2|DEL|sg1|sg2|xx1|xx2|yy1|yy2|NS1|NS2|NS3|NS4|co1|co2| // "\x40\x1c\x53\xeb\x0e\xb0\x00\x00\x00\x00\x3a\x00\x3a\x40\x00\x00\x19\x00\x00"; // Fill up the msg with data from the bbi file if ( fread( msg, sizeof(char), 19, file ) != 19 ) { printf("UOX3: MsgBoardRemovePost() Could not find message to mark deleted\n"); if ( feof(file) ) break; } if ( (msg[0] == buffer[s][8]) && (msg[1] == buffer[s][9]) && (msg[2] == buffer[s][10]) && (msg[3] == buffer[s][11]) ) { // Jump back to the DEL segment in order to mark the post for deletion fseek( file, -13, SEEK_CUR ); // Write out the mark for deletion value (0x00) fputc( 0, file ); // Inform user that the post has been removed sysmessage( s, "Post removed." ); // We found the message we wanted so break out and close the file break; } } } // Close bbi file if ( file ) fclose( file ); // Put code to actually remove the post from the bulletin board here. // Posted messages use serial numbers from 0x01000000 to 0x03FFFFFF so they // will not interfere with other worldly objects that start at serial number 0x40000000 // If, however, this is a problem, then simply remove this portion of code and the // messages will not be removed on the client but will still be marked for removal // in the message board files. char cRemoveObject[6]="\x1D\x00\x00\x00\x00"; cRemoveObject[1] = buffer[s][8]; cRemoveObject[2] = buffer[s][9]; cRemoveObject[3] = buffer[s][10]; cRemoveObject[4] = buffer[s][11]; Network->xSend( s, cRemoveObject, (sizeof(cRemoveObject)-1), 0 ); // Remove code above to prevent problems with client if necessary return; } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardEvent( int s ) // // PURPOSE: Handle all of the different Bulletin Board message types. // // PARAMETERS: s Players serial number // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardEvent(int s) { // Message \x71 has numerous uses for the Bulletin Board // so we need to get the type of message from the client first. int msgType = buffer[s][3]; // If this was due to a double click event if ( buffer[s][0]==0x06 ) msgType = 0; switch (msgType) { case 0: // Server->Client: Prepare to draw message board, send title + misc info to client { // show message board code goes here (called by doubleclick()) MsgBoardOpen( s ); break; } case 1: // Server->Client: Send list of message postings (subjects only) { // message sent out by CASE 4: below break; } case 2: // Server->Client: Sending body of message to client after subject dbl-clicked { // message sent out by CASE 3: below break; } case 3: // Client->Server: Client has dbl-clicked on subject, requesting body of message { // Example [SEND:12] 71 00 0c 03 40 07 ba 3d 40 1c 53 eb MsgBoardOpenPost( s ); break; } case 4: // Client->Server: Client has ACK'ed servers download of posting serial numbers { // Check to see whether client has ACK'd all of our message ID's before proceeding postAckCount[s]++; //printf(" pstAckCont=%d postCount=%d\n", postAckCount[s], postCount[s]); if ( postAckCount[s] != postCount[s] ) return; // Server needs to handle ACK from client that contains the posting serial numbers MsgBoardList( s ); break; } case 5: // Client->Server: Client clicked on Post button (either from the main board or after pressing the Reply) { // Reply just switches to the Post item. // Check privledge level against server.scp msgpostaccess if ( (chars[currchar[s]].priv&0x01) || (server_data.msgpostaccess) ) MsgBoardPost( s, chars[currchar[s]].postType, 0 ); else sysmessage( s, "Thou art not allowed to post messages." ); break; } case 6: // Remove post from Bulletin board { // |p#|s1|s2|mt|b1|b2|b3|b4|m1|m2|m3|m4| // Client sends 71 0 c 6 40 0 0 18 1 0 0 4 if ( (chars[currchar[s]].priv&0x01) || (server_data.msgpostremove) ) MsgBoardRemovePost( s ); break; } default: { printf("UOX3: MsgBoardEvent() Unknown msgType:%x for message: %x\n", buffer[s][3], buffer[s][0]); break; } } } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardPostQuest( int serial, int questType ) // // PURPOSE: Used to read in the template for displaying a typical quest // message based on the type of quest and the serial number of // the NPC or Item. Certain parameters can be used as variables // to replace certain NPC such as %n for NPC name, %t NPC title // etc. See the MSGBOARD.SCP file for an example. // // PARAMETERS: serial NPC or Item serial number // questType Type of quest being posted (used to determine // if the item_st or char_st should be used to // for replacing paramters in the script. // // RETURNS: 0 Failed to post message // 1 Post was successfull // // NOTES: Currently only escort quests work so this function us still // in its early stages in regards to the questType parameter. ////////////////////////////////////////////////////////////////////////////// int MsgBoardPostQuest( int serial, int questType ) { char subjectEscort[] = "Escort: Needed for the day."; // Default escort message char subjectBounty[] = "Bounty: Reward for capture."; // Default bounty message char subjectItem[] = "Lost valuable item."; // Default item message char subject[50] = ""; // String that will hold the default subject int sectionEntrys[MAXENTRIES]; // List of SECTION items to store for randomizing int listCount = 0; // Number of entries under the ESCORTS section, used to randomize selection int entryToUse = 0; // Entry of the list that will be used to create random message int linesInBody = 0; // Count of number of lines in body of post int lineLength = 0; // Length of the line just read in including terminating NULL int offset = 0; // Offset to next line in buffer int numLinesOffset = 0; // Offset to the number of lines in body field // msg2Post[] Buffer initialization msg2Post[0] = 0x71; // Packet ID msg2Post[1] = 0x00; // Size of packet (High byte) msg2Post[2] = 0x00; // Size of packet (Low byte) // This is the type of quest being posted: // The value will start arbitrarily at 0xFF and count down // ESCORT = 0xFF (defined in msgboard.h) // BOUNTY = 0xFE // ITEM = 0xFD switch ( questType ) { case ESCORTQUEST: msg2Post[3] = (unsigned char)(ESCORTQUEST); break; case BOUNTYQUEST: msg2Post[3] = (unsigned char)(BOUNTYQUEST); break; case ITEMQUEST: msg2Post[3] = (unsigned char)(ITEMQUEST); break; default: printf("UOX3: MsgBoardPostQuest() undefined questType, aborting quest!\n"); return 0; } // Since quest posts can only be regional or global, can use the BullBoard SN fields as CHAR or ITEM fields msg2Post[4] = (unsigned char)(serial>>24); // Normally Bulletin Board SN1 but used for quests as CHAR or ITEM SN1 msg2Post[5] = (unsigned char)(serial>>16); // Normally Bulletin Board SN2 but used for quests as CHAR or ITEM SN2 msg2Post[6] = (unsigned char)(serial>>8); // Normally Bulletin Board SN3 but used for quests as CHAR or ITEM SN3 msg2Post[7] = (unsigned char)(serial%256); // Normally Bulletin Board SN4 but used for quests as CHAR or ITEM SN4 msg2Post[8] = 0x00; // Reply to message serial number ( 00 00 00 00 for base post ) msg2Post[9] = 0x00; // Reply to message serial number ( 00 00 00 00 for base post ) msg2Post[10] = 0x00; // Reply to message serial number ( 00 00 00 00 for base post ) msg2Post[11] = 0x00; // Reply to message serial number ( 00 00 00 00 for base post ) openscript("msgboard.scp"); switch ( questType ) { case ESCORTQUEST: { // Find the list section in order to count the number of entries in the list if (!(i_scripts[msgboard_script]->find("ESCORTS"))) { closescript(); return 0; } // Count the number of entries under the list section to determine what range to randomize within do { read2(); if ( !(strcmp("ESCORT", script1)) ) { if ( listCount >= MAXENTRIES ) { printf("UOX3: MsgBoardPostQuest() Too many entries in ESCORTS list [MAXENTRIES=%d]\n", MAXENTRIES ); break; } sectionEntrys[listCount] = str2num(script2); listCount++; } } while ( script1[0]!='}' && script1[0]!=0 ); closescript(); // If no entries are found in the list, then there must be no entries at all. if ( listCount == 0 ) { printf( "UOX3: MsgBoardPostQuest() No msgboard.scp entries found\n" ); return 0; } // Choose a random number between 1 and listCount to use as a message entryToUse = RandomNum( 1, listCount ); #ifdef DEBUG printf("UOX3: MsgBoardPostQuest() listCount=%d entryToUse=%d\n", listCount, entryToUse ); #endif // Open the script again and find the section choosen by the randomizer openscript( "msgboard.scp" ); sprintf( temp, "ESCORT %i", sectionEntrys[entryToUse-1] ); if (!(i_scripts[msgboard_script]->find(temp))) { printf( "UOX3: MsgBoardPostQuest() Couldn't find entry %s\n", temp ); closescript(); return 0; } break; } default: { printf( "UOX3: MsgBoardPostQuest() Invalid questType %d\n", questType ); closescript(); return 0; } } //////////////////////////////////////////////////////////////////////////////////// // Randomly picked a message, now get the message data and fill in up the buffer // //////////////////////////////////////////////////////////////////////////////////// char *flagPos = NULL; char flag; char tempString[64]; // Insert the default subject line depending on the type of quest selected switch ( questType ) { case ESCORTQUEST: // Copy the default subject to the generic subject string strncpy( subject, subjectEscort, sizeof(subject) ); break; case BOUNTYQUEST: // Copy the default subject to the generic subject string strncpy( subject, subjectBounty, sizeof(subject) ); break; case ITEMQUEST: // Copy the default subject to the generic subject string strncpy( subject, subjectItem, sizeof(subject) ); break; default: printf("UOX3: MsgBoardPostQuest() invalid quest type\n"); return 0; } // Set the SizeOfSubject field in the buffer and copy the subject string to the buffer msg2Post[12] = strlen(subject) + 1; strncpy( (char *)&msg2Post[13], subject, msg2Post[12] ); // Set the offset to one past linesInBody count value of the buffer // Point to the Size of the line segment offset += ( msg2Post[12] + 13 + 1 ); // Set the subject numLinesOffset = offset - 1; // Read in the random post message choosen above and fill in buffer body for posting while ( 1 ) { readscript(); // Read in 1 line of the SCP file without the trailing CR ?? // If we reached the ending curly brace, exit the loop if ( !strcmp(temp, "}") ) break; flagPos = strchr( temp, '%' ); // Loop until we don't find anymore replaceable parameters while ( flagPos ) { if ( flagPos ) { // Move the the letter indicating what text to insert flag = *(flagPos + 1); // Save the remainder of the original string temporarily strcpy( tempString, (flagPos+2) ); // Replace the flag with the requested text switch ( flag ) { // NPC Name case 'n': { strcpy( flagPos, (chars[calcCharFromSer( serial )].name) ); strcat( temp, tempString ); break; } // LOCATION in X, Y coords case 'l': { sprintf( flagPos, "%d, %d", (chars[calcCharFromSer( serial )].x), (chars[calcCharFromSer( serial )].y) ); strcat( temp, tempString ); break; } // NPC title case 't': { strcpy( flagPos, (chars[calcCharFromSer( serial )].title) ); strcat( temp, tempString ); break; } // Destination Region Name case 'r': { strcpy( flagPos, region[chars[calcCharFromSer( serial )].questDestRegion].name ); strcat( temp, tempString ); break; } // Region Name case 'R': { strcpy( flagPos, region[chars[calcCharFromSer( serial )].region].name ); strcat( temp, tempString ); break; } default: { break; } } // Look for another replaceable parameter flagPos = strchr( flagPos, '%' ); } } // Get the length of the line read into 'temp' // after being modified with any extra info due to flags (plus one for the terminating NULL) lineLength = ( strlen(temp) + 1 ); msg2Post[offset] = lineLength; offset++; // Append the string in the msg2Post buffer memcpy( &msg2Post[offset], temp, (lineLength+1) ); offset += lineLength; // Increment the total number of lines read in linesInBody++; } closescript(); msg2Post[1] = offset/256; msg2Post[2] = offset%256; msg2Post[numLinesOffset] = linesInBody; // If the message is posted to the message board successfully // RETURN 1 otherwise RETURN 0 to indicate a failure of some sort if ( MsgBoardPost( 0, REGIONALPOST, 1 ) ) return 1; // Post succeeded // Post failed return 0; } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardQuestEscortCreate( int npcIndex ) // // PURPOSE: Used to generate an escort quest based on the currently // NPC's serial number // // PARAMETERS: npcIndex NPC index value in chars[] array // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardQuestEscortCreate( int npcIndex ) { // Choose a random region as a destination for the escort quest (except for the same region as the NPC was spawned in) do { if ( escortRegions ) { // If the number of escort regions is 1, check to make sure that the only // valid escort region is not the NPC's current location - if it is Abort if ( (escortRegions==1) && (validEscortRegion[0]==chars[npcIndex].region) ) { chars[npcIndex].questDestRegion = 0; break; } chars[npcIndex].questDestRegion = validEscortRegion[RandomNum(0, (escortRegions-1))]; } else { chars[npcIndex].questDestRegion = 0; // If no escort regions have been defined in REGIONS.SCP then we can't do it!! break; } } while ( chars[npcIndex].questDestRegion == chars[npcIndex].region ); // Set quest type to escort chars[npcIndex].questType = ESCORTQUEST; // Make sure they don't move until an player accepts the quest chars[npcIndex].npcWander = 0; // Don't want our escort quest object to wander off. chars[npcIndex].npcaitype = 0; // Remove any AI from the escort (should be dumb, just follow please :) chars[npcIndex].questOrigRegion = chars[npcIndex].region; // Store this in order to remeber where the original message was posted // Set the expirey time on the NPC if no body accepts the quest if ( server_data.escortinitexpire ) chars[npcIndex].summontimer = (unsigned int)( uiCurrentTime + ( CLOCKS_PER_SEC * server_data.escortinitexpire ) ); // Make sure the questDest is valid otherwise don't post and delete the NPC if ( !chars[npcIndex].questDestRegion ) { printf("UOX3: MsgBoardQuestEscortCreate() No valid regions defined for escort quests\n"); Npcs->DeleteChar( npcIndex ); //deletechar( npcIndex ); return; } // Post the message to the message board in the same REGION as the NPC if ( !MsgBoardPostQuest(chars[npcIndex].serial, ESCORTQUEST) ) { printf( "UOX3: MsgBoardQuestEscortCreate() Failed to add quest post for %s\n", chars[npcIndex].name ); printf( "UOX3: MsgBoardQuestEscortCreate() Deleting NPC %s\n", chars[npcIndex].name ); Npcs->DeleteChar( npcIndex ); //deletechar( npcIndex ); return; } // Debugging messages #ifdef DEBUG printf("UOX3: MsgBoardQuestEscortCreate() Escort quest for:\n %s to be escorted to %s\n", chars[npcIndex].name, region[chars[npcIndex].questDestRegion].name ); #endif } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardQuestEscortArrive( int npcIndex, int pcIndex ) // // PURPOSE: Called when escorted NPC reaches its destination // // PARAMETERS: npcIndex Index number of the NPC in the chars[] array // pcIndex Index number of the player in the chars[] array // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardQuestEscortArrive( int npcIndex, int pcIndex ) { int i = npcIndex; int k = pcIndex; // Calculate payment for services rendered int servicePay = ( RandomNum(0, 20) * RandomNum(1, 30) ); // Equals a range of 0 to 600 possible gold with a 5% chance of getting 0 gold // If they have no money, well, oops! if ( servicePay == 0 ) { sprintf( temp, "Thank you %s for thy service. We have made it safely to %s. Alas, I seem to be a little short on gold. I have nothing to pay you with.", chars[currchar[k]].name, region[chars[i].questDestRegion].name ); npctalk( k, i, temp, 0 ); } else // Otherwise pay the poor sod for his time { // Less than 75 gold for a escort is pretty cheesey, so if its between 1 and 75, add a randum amount of between 75 to 100 gold if ( servicePay < 75 ) servicePay += RandomNum(75, 100); addgold( k, servicePay ); goldsfx( k, servicePay ); sprintf( temp, "Thank you %s for thy service. We have made it safely to %s. Here is thy pay as promised.", chars[currchar[k]].name, region[chars[i].questDestRegion].name ); npctalk( k, i, temp, 0 ); } // Inform the PC of what he has just been given as payment sprintf( temp, "You have just received %d gold coins from %s %s", servicePay, chars[i].name, chars[i].title ); sysmessage( k, temp ); // Take the NPC out of quest mode chars[i].npcWander = 2; // Wander freely chars[i].ftarg = -1; // Reset follow target chars[i].questType = 0; // Reset quest type chars[i].questDestRegion = 0; // Reset quest destination region // Set a timer to automatically delete the NPC chars[i].summontimer = (unsigned int)( uiCurrentTime + ( CLOCKS_PER_SEC * server_data.escortdoneexpire ) ); //removefromptr(&cownsp[chars[k].ownserial%256], k); chars[i].own1=255; chars[i].own2=255; chars[i].own3=255; chars[i].own4=255; chars[i].ownserial=-1; } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardQuestEscortDelete( int npcIndex ) // // PURPOSE: Called when escorted NPC needs to be deleted from the world // (just a wrapper in case some additional logic needs to be added) // // PARAMETERS: npcIndex Index number of the NPC in the chars[] array // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardQuestEscortDelete( int npcIndex ) { chars[npcIndex].dead=1; Npcs->DeleteChar(npcIndex); return; } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardQuestEscortRemovePost( int npcIndex ) // // PURPOSE: Marks the posting for a specific NPC for deletion, thereby // removing it from the bulletin boards viewable list. // // PARAMETERS: npcIndex Index number of the NPC in the chars[] array // // RETURNS: void ////////////////////////////////////////////////////////////////////////////// void MsgBoardQuestEscortRemovePost( int npcIndex ) { // Read bbi file to determine messages on boards list // Find the post related to this NPC's quest and mark it for deletion // thereby removing it from the bulletin boards list int s = calcSerFromChar( npcIndex ); FILE *file = NULL; // 50 chars for prefix and 4 for the extension plus the ending NULL char fileName[256] = ""; // REGIONAL post file sprintf( temp, "region%d.bbi", chars[npcIndex].questOrigRegion ); // If a MSBBOARDPATH has been define in the SERVER.SCP file, then use it if (server_data.msgboardpath) strcpy( fileName, server_data.msgboardpath ); // Set fileName to REGIONAL.bbi //sysmessage( s, "Opening REGIONAL.bbi messages"); strcat( fileName, temp ); file = fopen( fileName, "rb+" ); // If the file exists continue, othewise abort with an error if ( file != NULL ) { // Ignore first 4 bytes of bbi file as this is reserverd for the current max message serial number being used if ( fseek( file, 4, SEEK_SET ) ) { printf("UOX3: MsgBoardQuestEscortRemovePost() failed to seek first message seg in bbi\n"); return; } // Loop until we have reached the end of the file while ( !feof(file) ) { // | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 NS = NPC Serial // |mg1|mg2|mg3|mg4|mo1|mo2|DEL|sg1|sg2|xx1|xx2|yy1|yy2|NP1|NS2|NS3|NS4|co1|co2| // "\x40\x1c\x53\xeb\x0e\xb0\x00\x00\x00\x00\x3a\x00\x3a\x40\x00\x00\x19\x00\x00"; // Fill up the msg with data from the bbi file if ( fread( msg, sizeof(char), 19, file ) != 19 ) { printf("UOX3: MsgBoardQuestEscortRemovePost() Could not find message to mark deleted\n"); if ( feof(file) ) break; } if ( (msg[13] == s/16777216) && (msg[14] == s/65536 ) && (msg[15] == s/256 ) && (msg[16] == s%256 ) ) { // Jump back to the DEL segment in order to mark the post for deletion fseek( file, -13, SEEK_CUR ); // Write out the mark for deletion value (0x00) fputc( 0, file ); // We found the message we wanted so break out and close the file break; } } } // Close bbi file if ( file ) fclose( file ); return; } ////////////////////////////////////////////////////////////////////////////// // FUNCTION: MsgBoardMaintenance( void ) // // PURPOSE: Cleans out old posts which are older than the MSGRETENTION // period set in SERVER.SCP and any posts that have been marked // for deletion (such as escort quests after they have been // accepted or posts that have been "removed" through the user // interface. This is called as a cleanup routine on server // startup. Compacts and reassigns message serial numbers. // // PARAMETERS: void // // RETURNS: void // // NOTES: This function uses the _findfirst() and _findnext() functions // which are OS specific. There will definetly be an issue in // compiling this on LINUX because I have no idea what the // structure to be passed to the functions needs to be for LINUX. // This will definetly have to be #ifdef'd to handle this. // Anyone with LINUX experience please feel free to fix it up. ////////////////////////////////////////////////////////////////////////////// void MsgBoardMaintenance( void ) { return; }