/** * The standard inheritable object for player-run shop offices. * This is the main object for player-run shops. The majority of * player-shop functionality is handled from within this object. * See the header file for a complete description of the shop's * workings. * @see /include/player_shop.h * @see /std/shops/player_shop/mgr_office.c * @see /std/shops/player_shop/counter.c * @see /std/shops/player_shop/storeroom.c * @see /std/shops/player_shop/shop_front.c * @see /std/shops/player_shop/shopkeeper.c * @author Ringo * @started 1st August 1999 */ #include <player_shop.h> #include <mail.h> #include <board.h> #include <money.h> #include <move_failures.h> #include <refresh.h> inherit ROOM_OBJ; int do_claim( string ); int do_clock( string ); int do_bank( mixed * ); int do_logs( mixed *, string ); int do_chart( mixed *, string ); int do_promote( string ); void demote( string, string ); void save_hist(); void save_times(); void add_policy( string ); void remove_policy( string ); int do_policy_vote( string, string, string ); private void adjust_profit( string emp, int amount ); private nosave string _proprietor = "???", _shop_name = "???", _very_short = "???", _place = "???", _channel = "???", _storeroom = "", _shop_front = "", _counter = "", _mgr_office = "", _store_dir = "", _counter_dir = "", _shop_dir = "", _shopkeeper = "", _log_file = "", _stock_policy = "", _save_dir = SAVE_DIR, _creator = CREATOR; private nosave object _chart = 0, _board = 0, _notice = 0; private nosave mapping _history = ([]), _list = ([]), _times = ([]), _employees = ([]); private nosave int _call_save = 0, _call_hist = 0, _call_times = 0, _call_hire_list = 0, _call_mail_hirees = 0, _call_summon = 0, _npc_time = 0, _call_emps = 0; private string *_retired = ({}), *_got_bonus = ({}), _last_month = "last month"; private mapping _applicants = ([]), _baddies = ([]), _declined = ([]), _accounts = (["profit":0,"bonus":0]), _new_policies = ([]), _policies = ([]); private int _max_emp = MAX_EMP, _bonus_val = 0, _bonus = 0, _pay_val = 4, _num_cabinets = MIN_CABINETS, _net_takings = 0; private mixed *_register; /** @ignore yes */ void create() { string ob_file; object this_ob; this_ob = this_object(); ob_file = file_name( this_ob ); #ifdef DEBUG debug_printf( "Filename: %s\n", ob_file ); #endif seteuid( (string)master()->creator_file( ob_file ) ); do_setup++; ::create(); do_setup--; if ( !do_setup ) { this_ob->setup(); this_ob->reset(); } add_property( "determinate", "" ); _chart = clone_object( SHOP_CHART ); _chart->set_office( ob_file ); add_hidden_object( _chart ); _notice = clone_object( SHOP_NOTICE ); _notice->set_office( ob_file ); add_hidden_object( _notice ); #ifndef DEBUG REFRESH->register_refresh( ob_file ); REFRESH->register_delete( ob_file ); #endif } /* create() */ /** * Set the creator of this shop. * This person will receive all applications, complaints, suggestions * etc in the absence of any managers. Default is set by CREATOR in * <player_shop.h> * @param creator The person responsible for this shop. */ void set_creator( string creator ) { _creator = creator; } /** * Query the maintainer of this shop's files. * @return The person responsible for this shop. */ void query_creator( string creator ) { return copy( _creator ); } /** * Determine if this person is an employee of the shop. * @param player The player to query. * @return The employee's points, or FALSE if not an employee. */ int query_employee( string player ) { if ( _employees[player] ) { return copy(_employees[player][EMP_POINTS]); } return FALSE; } /* query_employee() */ /** * Determine if this person is a supervisor of this shop. * @param player The player to query. * @return TRUE or FALSE */ int query_supervisor( string player ) { if ( _employees[player] ) { return ( _employees[player][EMP_POINTS] & SUPERVISOR ); } return FALSE; } /* query_supervisor() */ /** * Determine if this person is a manager of this shop. * @param player The player to query. * @return TRUE or FALSE */ int query_manager( string player ) { if ( _employees[player] ) { return ( _employees[player][EMP_POINTS] & MANAGER ); } return FALSE; } /* query_manager() */ /** * Determine if this person is a retired manager of this shop. * @param player The player to query. * @return TRUE or FALSE */ int query_retired( string player ) { return ( member_array( player, _retired ) == -1 )?FALSE:TRUE; } /* query_retired() */ /** * Determine if this person has applied for a job. * @param player The player to query. * @return APPLIED if applied, HIRED if voted in, AWAITING if awaiting * a vacancy. Otherwise, will return FALSE. */ int query_applicant( string player ) { if ( !sizeof( _applicants ) ) { return FALSE; } if ( _applicants[player] ) { return copy(_applicants[player][APP_TYPE]); } return FALSE; } /* query_applicant() */ /** * Determine if this person is banned from the shop. * @param player The player to query. * @return The time of the ban if banned, FALSE if not banned. */ int query_baddie( string player ) { if ( !m_sizeof( _baddies ) ) { return FALSE; } if ( _baddies[player] ) { return copy(_baddies[player][BAD_TIME]); } return FALSE; } /* query_baddie() */ /** @ignore yes */ void init() { string word; object tp; ::init(); tp = this_player(); word = tp->query_name(); /* * Only employees & creators should have access to shop commands. */ if( !creatorp(tp) && !_employees[word] && ( member_array( word, _retired ) == -1 ) ) { return; } add_command("claim", "{badge|handbook|bonus}", (: do_claim($4[0]) :) ); add_command("resign", ""); add_command("list", ""); add_command("office", ""); /* * If there is no board set up for this shop, all comms done by mail. */ if( !_board ) add_command( "memo", "" ); /* * Retired managers have no need for following commands. */ if( member_array( word, _retired ) != -1 ) return; add_command("clock", "{in|out}", (: do_clock($4[0]) :) ); add_command("bank", ({ "", "<number>" }), (: do_bank($4) :) ); if( !tp->query_property( "no score" ) ) add_command("promotion", "{on|off}", (: do_promote($4[0]) :) ); /* * Only supervisors & managers have access to following commands. */ if( !creatorp(tp) && !( _employees[word][EMP_POINTS] & SUPERVISOR ) ) return; add_command("chart", ({ "add <string'item'>", "remove <string'item'>", "max <string'item'> <number>", "buy <string'item'> <number>", "sell <string'item'> <number>", "assign <string'item'> [to cabinet] <number>", "unassign <string'item'> [from cabinet] <number>" }), (: do_chart($4, $5) :) ); add_command("check", "cabinets"); if( !creatorp(tp) && !( _employees[word][EMP_POINTS] & MANAGER ) ) add_command("logs", ({ "", "<number>", "[chart]" }), (: do_logs($4,$5) :) ); else add_command("logs", ({ "", "<number>", "{personnel|accounts|chart}" }), (: do_logs($4,$5) :) ); } /* init() */ /** * @ignore yes * Saves the employee data. */ void do_save_emps() { #ifdef DEBUG debug_printf( "Saving employee data to %s.\n", _save_dir + _very_short+ ".employees" ); #endif unguarded( (: write_file, _save_dir + _very_short+ ".employees", save_variable( _employees ), 1 :) ); } /* do_save_emps() */ /** * Save the data file. * This method uses a call_out to help minimise the amount of disk activity * during normal operations of the shop. */ protected void save_emps() { if( _very_short == "???" ) return; remove_call_out( _call_emps ); _call_emps = call_out( "do_save_emps", SAVE_DELAY ); } /* save_emps() */ /** * @ignore yes * Save this object immediately. */ void do_save() { #ifdef DEBUG debug_printf( "Saving shop data to %s.\n", _save_dir + _very_short ); #endif unguarded( (: save_object, _save_dir + _very_short :) ); } /* do_save() */ /** * Save the data file. * This method uses a call_out to help minimise the amount of disk activity * during normal operations of the shop. */ protected void save_me() { if( _very_short == "???" ) return; remove_call_out( _call_save ); _call_save = call_out( "do_save", SAVE_DELAY ); } /* save_me() */ /** * @ignore yes * Saves the employment last-action times data. */ void save_list() { if ( _very_short == "???" ) { return; } #ifdef DEBUG debug_printf( "Saving chart list data to %s.\n", _save_dir+ _very_short+ ".list" ); #endif unguarded( (: write_file, _save_dir + _very_short+ ".list", save_variable( _list ), 1 :) ); } /* do_save_list() */ /** * @ignore yes * Make sure all data is saved before desting this object. */ void dest_me() { if( _very_short != "???" ) { remove_call_out( _call_save ); remove_call_out( _call_hist ); remove_call_out( _call_times ); remove_call_out( _call_emps ); save_hist(); save_times(); do_save_emps(); do_save(); } if( _chart ) _chart->dest_me(); if( _board ) _board->dest_me(); if( _notice ) _notice->dest_me(); ::dest_me(); } /* dest_me() */ /** * Query number of 'items' in stock. */ int query_stock( string items ) { return _storeroom->query_num_items( items, 0 ); } /* query_stock() */ /** * @ignore yes * Saves the employment last-action times data. */ void save_times() { if( _very_short == "???" ) return; #ifdef DEBUG debug_printf( "Saving times data to %s.\n", _save_dir + _very_short+ ".times" ); #endif unguarded( (: write_file, _save_dir + _very_short+ ".times", save_variable( _times ), 1 :) ); } /* save_times() */ /** * @ignore yes * Sets the last action time of an employee. * This time is the last time an employee did something worth * recording and is used to determine if they are inactive. * @param employee The employee. */ private void set_emp_time( string employee ) { if ( !_employees[employee] ) { return; } _employees[employee][EMP_TIME] = time(); // Set last-action time _employees[employee][EMP_INACTIVE] = 0; // Reset inactivity flag if ( !sizeof( _times ) ) { _times = ([ employee:0 ]); } else if ( !_times[employee] ) { _times += ([ employee:0 ]); } _times[employee] = time(); // Update employee record remove_call_out( _call_times ); _call_times = call_out( "save_times", PERS_DELAY ); save_emps(); } /* set_emp_time() */ /** * @ignore yes * This function adds an entry to the logs & pays employees. * Logs record a full day's (3 DW days) activity, apart from the * chart, accounts & personnel logs which use log_file.<br> * @param logtype The type of log entry - see <player_shop.h> * @param word The employee making the entry. * @param words The log entry text. */ void shop_log( int logtype, string word, string words ) { string date, file, month, colour, summary, item, sign; sscanf( mudtime(time()), "%*s %*s %*s %s", month ); if ( file_size( _log_file +"general.log" ) > 0 ) { date = ctime( time() )[ 0 .. 9 ]; /* * Are we on a new day yet? If so, do the daily review. */ if ( date != unguarded( (: read_file, _log_file +"general.log", 1, 1 :) )[ 0 .. 9 ] ) { _storeroom->force_load(); call_out( "update_averages", 60 ); file = _log_file +"general.log-"+ time(); unguarded( (: rename, _log_file +"general.log", file :) ); call_out( "review_employees", 5 ); summary = sprintf( "%sFor the day ending %s:%s\n", "%^BOLD%^", mudtime(time()), "%^RESET%^" ); foreach ( item in m_indices( _list ) ) { summary += sprintf( " %s - Bought %d, Sold %d\n", capitalize(item), _list[item][CHART_BOUGHT], _list[item][CHART_SOLD] ); _list[item][CHART_AVESALE] = ( _list[item][CHART_AVESALE] + _list[item][CHART_SOLD] + random(2) ) / 2; _list[item][CHART_SOLD] = 0; _list[item][CHART_BOUGHT] = 0; } save_list(); sign = ""; if ( _net_takings < 0 ) { _net_takings = -_net_takings; sign = "-"; } summary += sprintf( "%sThe net takings of the shop were %s%s.%s\n\n", "%^BOLD%^", sign, MONEY_HAND->money_value_string( _net_takings, _place ), "%^RESET%^" ); _net_takings = 0; save_me(); unguarded( (: write_file, _log_file +"general.log", date +"\n" + summary :) ); /* * Remove old logs. */ foreach ( file in get_dir( _log_file +"general.log-*" ) ) { sscanf( file, "%*s-%s", date ); if ( time() - to_int( date ) > (60*60*24*LOG_DAYS) ) { unguarded( (: rm, _log_file + "general.log-"+ date :) ); } } } } /* * Are we in a new month yet? If so do the monthly review. */ if ( month != _last_month ) { if ( member_array( month, ({ "Offle", "February", "March", "April", "May", "June", "Grune", "August", "Spune", "Sektober", "Ember", "December", "Ick" }) ) != -1 ) { call_out( "monthly_review", 60, month); } } /* * Only pay employees if clocked in & this action is at least * 1 minute after the previous paid action. */ if ( _employees[word] && !(_employees[word][EMP_POINTS] & NPC) ) { if ( _employees[word][EMP_POINTS] & CLOCKED_IN && _employees[word][EMP_TIME] < ( time() - 60 ) ) { if ( _employees[word][EMP_POINTS] & MANAGER ) { _employees[word][EMP_PAY] += ( _pay_val * 2 ); } else if ( _employees[word][EMP_POINTS] & SUPERVISOR ) { _employees[word][EMP_PAY] += to_int( _pay_val * 1.5 ); _employees[word][EMP_POINTS] += 32; } else { _employees[word][EMP_PAY] += _pay_val; _employees[word][EMP_POINTS] += 32; } set_emp_time( word ); } } /* * Write entry to the appropriate log. */ switch ( logtype ) { case PURCHASE : colour = "%^GREEN%^"; break; case SALE : colour = "%^RED%^"; break; case GENERAL : colour = "%^CYAN%^"; break; case PERSONNEL : log_file( _log_file+ "personnel.log", "%s: %s - %s\n", mudtime(time()), capitalize( word ), words ); return; break; case ACCOUNTS : log_file( _log_file+ "accounts.log", "%s: %s - %s\n", mudtime(time()), capitalize( word ), words ); return; break; case CHARTLOG : log_file( _log_file+ "chart.log", "%s: %s - %s\n", mudtime(time()), capitalize( word ), words ); return; break; default : colour = "%^RESET%^"; break; } unguarded( (: write_file, _log_file +"general.log", colour + mudtime(time())+ ": "+ capitalize( word ) +"%^RESET%^ - "+ words +"\n" :) ); } /* shop_log() */ /** * @ignore yes * Writes a message to the shop's board if it exists. If not, will * send a mail to each employee. This message is "written" by the shop's * proprietor. * @param subject Take a guess ;-) * @param post See subject... */ private void add_board_message( string subject, string post ) { string employee, *employees; if ( _board ) { #ifdef DEBUG debug_printf( "Posting message %s to board %s.\n", subject, _channel ); #endif BOARD_HAND->add_message( _channel, _proprietor, subject, post + "--\n"+ _proprietor+ " (proprietor)" ); } else { employees = _retired; foreach( employee in m_indices( _employees ) ) { if ( !( _employees[employee][EMP_POINTS] & NPC ) ) { employees += ({ employee }); } } if ( !sizeof( employees ) ) { #ifdef DEBUG debug_printf( "No employees to send mail to.\n" ); #endif return; } employees += ({ CREATOR }); #ifdef DEBUG debug_printf( "Sending mail %s to all employees.\n", subject ); #endif AUTO_MAILER->auto_mail( implode( employees, "," ), _proprietor, subject, "", post ); } } /* add_board_message() */ /** * @ignore yes * Send a memo to each employee. Only used if there is no board. */ int do_memo() { this_player()->do_edit( 0, "end_memo" ); add_succeeded_mess( "" ); return 1; } /* do_memo() */ /** @ignore yes */ void end_memo( string text ) { string *employees; if ( !text ) { tell_object( this_player(), "Aborted.\n" ); #ifdef DEBUG debug_printf( "No message text. Aborting.\n" ); #endif return; } employees = _retired; if ( creatorp(TP) ) { employees += ({ TP->query_name() }); } foreach( string employee in m_indices( _employees ) ) { if ( !( _employees[employee][EMP_POINTS] & NPC ) ) { employees += ({ employee }); } } tell_object( this_player(), "Do you want to keep a copy of the memo? " ); input_to( "send_memo", 0, text, employees ); } /* end_memo() */ /** @ignore yes */ void send_memo( string ans, string text, string *employees ) { ans = lower_case( ans ); if ( strlen(ans) < 1 || ( ans[0] != 'y' && ans[0] != 'n' ) ) { tell_object( TP, "Do you want to keep a copy of the memo? (Yes or No)? " ); input_to( "send_memo", 0, text, employees ); return; } if ( !sizeof( employees ) ) { #ifdef DEBUG debug_printf( "No employees to send mail to.\n" ); #endif tell_object( TP, "There is no-one to send a memo to!\n" ); return; } if(ans[0] == 'n') employees -= ({ TP->query_name() }); #ifdef DEBUG debug_printf( "Sending employee memo to %s.\n", implode( employees, "," ) ); #endif tell_object( TP, "Sending your memo.\n" ); AUTO_MAILER->auto_mail( implode( employees, "," ), TP->query_name(), _very_short+ " employee memo", "", text, 0, 0 ); } /* send_memo() */ /** * Return the employee data. * This includes only active employees - retired managers are not included. * The data is formatted as:<br> * ([ employee:({ points, time, bank, pay, inactive, nobonus, nopromote }) ])<br> * @return The employee mapping, formatted as above. */ mapping query_employees() { return copy(_employees + ([ ])); } /** * @ignore yes * Displays the list of commands available to each employee. */ int do_office() { int employee; object tp; string tp_name, board_mess = ""; if( !_board ) board_mess = " memo - send a memo to the other employees\n"; add_succeeded_mess( "" ); tp = this_player(); tp_name = tp->query_name(); if( member_array( tp_name, _retired ) != -1 ) { tell_object( tp, "As a retired manager, you can use the following " "commands:\n" " claim - claim a badge or handbook\n" " list - list the other employees of the shop\n" " logs - review the shop's logs\n" " resign - terminate your association with Tarnach's\n"+ board_mess+ "You may also enter the managers' office for more commands.\n" ); return 1; } employee = ( creatorp(tp) ? SUPERVISOR + MANAGER : _employees[tp_name][EMP_POINTS] ); tell_object( tp, "As an employee, you can use the following commands:\n" " clock - start or finish your working day\n"+ " claim - claim a badge, handbook, or this month's bonus\n" " bank - set to which bank account you are paid\n" " list - list the other employees of the shop\n" " resign - end your employment at the shop\n"+ board_mess ); if( employee & SUPERVISOR ) { tell_object( tp, "As a supervisor, you can also use:\n" " chart - change the information on the sales chart\n" " check - check cabinet assignments\n" " logs - review the shop's logs\n" ); } if( employee & MANAGER ) { tell_object( tp, "As a manager you can enter the managers' " "office and\nuse the commands listed there.\n" ); } return 1; } /* do_office() */ /** * @ignore yes * Adds a new employee. * Also prevent the employee from receiving a bonus until they have * worked a full month by setting EMP_NOBONUS to 1. */ void add_employee( string player ) { if( !_employees[player] ) { _employees += ([ player: ({ 1, 0, 0, 0, 0, 1, 0 }) ]); set_emp_time( player ); _got_bonus += ({ player }); save_me(); } } /* add_employee() */ /** * @ignore yes * Remove this person from the retired managers array. * Used when a retired manager resigns. */ private void remove_retired( string retired ) { retired = lower_case( retired ); if( member_array( retired, _retired ) == -1 ) return; _retired -= ({ retired }); save_me(); } /* remove_retired() */ /** * @ignore yes * Remove this person from the applicants mapping. * Used when an applicant is hired, declined, or cancels their * application. */ private void remove_applicant( string applicant ) { applicant = lower_case( applicant ); if( query_applicant( applicant ) ) { map_delete( _applicants, applicant ); save_me(); } } /* remove_applicant() */ /** * @ignore yes * Remove this person from the employees mapping. * Used when an employee is fired, resigns, or retires. */ private void remove_employee( string employee ) { employee = lower_case( employee ); if( _employees[employee] ) { map_delete( _employees, employee ); save_emps(); } remove_applicant( employee ); remove_retired( employee ); /* Check to see if anyone should be hired */ remove_call_out( _call_hire_list ); _call_hire_list = call_out( "check_hire_list", 5 ); } /* remove_employee() */ /** * @ignore yes * Used to check access to the managers' office. */ int check_manager( string action ) { object tp = this_player(); string tp_name = tp->query_name(); if( creatorp(tp) || (_employees[tp_name][EMP_POINTS] & MANAGER) || ( member_array( tp_name, _retired ) != -1 ) ) return 1; return notify_fail( "You are not a manager here!\n" ); } /* check_manager() */ /** * Query the list of retired managers. * This method returns the list of retired managers, sorted * alphabetically. * @return The sorted array of retired managers. */ string *get_retired() { return copy( sort_array( _retired, 1 ) ); } /* get_retired() */ /** * Query the list of managers. * This method returns the list of managers, sorted alphabetically. * @return The sorted array of managers. */ string *get_managers() { return copy( sort_array( keys( filter( _employees, (: _employees[$1][EMP_POINTS] & MANAGER :) ) ), 1 ) ); } /* get_managers() */ /** * Query the list of supervisors. * This method returns the list of supervisors, sorted * alphabetically. * @return The sorted array of supervisors. */ string *get_supervisors() { string word, *supervisors; supervisors = keys( filter( _employees, (: _employees[$1][EMP_POINTS] & SUPERVISOR :) ) ); /* Managers also have the supervisor bit set, so don't * include them in this list. */ foreach( word in supervisors ) { if( _employees[word][EMP_POINTS] & MANAGER ) supervisors -= ({ word }); } return copy( sort_array( supervisors, 1 ) ); } /* get_supervisors() */ /** * Query the list of employees. * This method returns the list of employees, sorted alphabetically. * @return The sorted array of employees. */ string *get_employees() { string word, *employees; employees = keys( _employees ); /* Don't include people with the supervisor bit set */ foreach( word in employees ) { if( _employees[word][EMP_POINTS] & SUPERVISOR ) employees -= ({ word }); } return copy( sort_array( employees, 1 ) ); } /* get_employees() */ /** * @ignore yes * Used by the shop front to add this person to the applicants mapping. */ void add_applicant( string player ) { player = lower_case( player ); if ( !query_applicant( player ) ) { if ( !sizeof( _applicants ) ) { _applicants = ([ player:({ APPLIED,0,({}),({}), ({}), }) ]); } else { _applicants += ([ player:({ APPLIED,0,({}),({}), ({}), }) ]); _applicants[player][APP_TIME] = time(); } save_me(); } } /* add_applicant() */ /** * Return the list of applicants. * This method returns the list of applicants as a mapping formatted * as follows:<br> * ([ "name": type, time, ({ for }), ({ against }), ({ abstain }), ]) * @return The applicants mapping formatted as above. */ mapping get_applicants() { return copy(_applicants + ([ ])); } /** * Determine if a player was declined for a job. * This method is used to determine if there is a declined * application registered for a player. * @param player The player to query. * @return FALSE, or the time at which the applicant was declined. */ int query_declined( string player ) { if ( !sizeof( _declined ) ) { return FALSE; } if ( _declined[player] ) { return copy(_declined[player]); } return FALSE; } /* query_declined() */ /** * @ignore yes * Add this person to the list of declined applicants. Used by * the shop front to determine if this person can apply again * yet. */ private void add_declined( string applicant ) { applicant = lower_case( applicant ); if ( !query_declined( applicant ) ) { if ( !sizeof( _declined ) ) { _declined = ([applicant:0]); } else { _declined += ([applicant:0]); } _declined[applicant] = time(); save_me(); } } /* add_declined() */ /** * @ignore yes * Remove this person from the list of declined applicants, * allowing them to re-apply. */ private void remove_declined( string declined ) { declined = lower_case( declined ); if ( query_declined( declined ) ) { map_delete( _declined, declined ); save_me(); } } /* remove_declined() */ /** * @ignore yes * Saves the employment history data. */ void save_hist() { if ( _very_short == "???" ) { return; } #ifdef DEBUG debug_printf( "Saving history data to %s.\n", _save_dir + _very_short+ ".history" ); #endif unguarded( (: write_file, _save_dir + _very_short+ ".history", save_variable( _history ), 1 :) ); } /* save_hist() */ /** * @ignore yes * Adds an entry to the employee's history. * This method is intended to log relevant personnel issues such as * applications, hirings, promotions etc. The normal day-to-day * stuff is logged in the shop's logs themselves. * @param shop The shop very short name. * @param employee The employee. * @param note The note to log. */ void employee_log( string employee, string note ) { if ( !sizeof(_history) ) { _history = ([ employee:({ ({}),({}), }) ]); } else if ( !_history[employee] ) { _history += ([ employee:({ ({}),({}), }) ]); } _history[employee][0] += ({ time() }); _history[employee][1] += ({ note }); remove_call_out( _call_hist ); _call_hist = call_out( "save_hist", PERS_DELAY ); if ( !sizeof( _times ) ) { _times = ([ employee:0 ]); } else if ( !_times[employee] ) { _times += ([ employee:0 ]); } _times[employee] = time(); // Update employee record remove_call_out( _call_times ); _call_times = call_out( "save_times", PERS_DELAY ); } /* employee_log() */ /** * @ignore yes * Used when applicant has sufficient supporting votes to be * accepted. */ private void hire( string word ) { int gender; string obj, poss, subj; remove_applicant( word ); /* Do not hire if not a user, already an employee, or banned */ if ( !"/secure/login"->test_user( word ) || _employees[word] || query_baddie( word ) ) { return; } add_employee( word ); employee_log( word, "Hired" ); shop_log( PERSONNEL, _proprietor, "hired "+ capitalize( word ) ); AUTO_MAILER->auto_mail( word, _proprietor, _shop_name, "", "Congratulations! You've been hired to work at "+ _shop_name+ ". You'll find that you can now move through the counter " "to the back areas of the shop. The first things you should " "do are \"claim\" a new badge and staff handbook.\n" ); gender = "/secure/login"->test_gender(word); obj = ({ "it", "him", "her" })[gender]; poss = ({ "its", "his", "her" })[gender]; subj = ({ "it", "he", "she" })[gender]; add_board_message( "New employee", capitalize( word )+ " has today been employed to work for the shop. Please make " + obj + " feel welcome, and assist " + obj + " while " + subj + " gets " "started in " + poss + " new position.\n" ); /* Update the other accepted applicants */ remove_call_out( _call_mail_hirees ); _call_mail_hirees = call_out( "mail_hirees", 5 ); } /* hire() */ /** * @ignore yes * Check to see if we should hire any more accepted applicants. */ void check_hire_list() { int count, x; string word; string *hirees; hirees = ({}); /* Applicant has not confirmed employment after being accepted * so remove them. */ foreach ( word in m_indices( filter( _applicants, (: _applicants[$1][APP_TYPE] == HIRED :) ) ) ) { if ( time() - _applicants[word][APP_TIME] > HIRE_TIMEOUT ) { AUTO_MAILER->auto_mail( word, _proprietor, _shop_name, "", "Since you have not returned to confirm " "your employment with us, we have assumed that you are no " "longer interested, and removed your name from our files. " "If you wish to re-apply at any time in the future, please " "return to the shop to do so.\n" ); employee_log( word, "Lapsed their application" ); remove_applicant( word ); } } foreach ( word in m_indices( filter( _applicants, (: _applicants[$1][APP_TYPE] == APPLIED :) ) ) ) { if ( time() - _applicants[word][APP_TIME] > HIRE_TIMEOUT ) { if ( sizeof( _applicants[word][APP_FOR] ) < sizeof( _applicants[word][APP_AGAINST] ) ) { /* Applicant has more 'no' votes than 'yes' votes so * decline them. */ remove_applicant( word ); AUTO_MAILER->auto_mail( word, _proprietor, _shop_name, "", "Thank you for your recent application for employment " "with us. Unfortunately, I have to inform you that you " "have been unsuccessful at this time. Please feel free " "to re-apply again in the future, when your application " "will be re-considered.\n" ); employee_log( word, "Application was declined" ); add_declined( word ); } else { /* Applicant has at least as many 'yes' votes as 'no' votes * so accept them. */ AUTO_MAILER->auto_mail( word, _proprietor, _shop_name, "", "Congratulations! You've been accepted to work at "+ _shop_name+ ". Please return to the shop within the next " "seven days to \"confirm\" that you wish to accept " "the position.\n" ); _applicants[word][APP_TYPE] = HIRED; _applicants[word][APP_TIME] = time(); employee_log( word, "Application was accepted" ); } } } /* Number of places available */ count = _max_emp - sizeof( _employees ); if ( count < 0 ) { return; } TCRE("shiannar", count+""); hirees = sort_array( keys( filter( _applicants, (: _applicants[$1][APP_TYPE] == AWAITING :) ) ), (: _applicants[$1][APP_TIME] - _applicants[$2][APP_TIME] :) ); /* See if we can hire all people awaiting a vacancy */ if ( count > sizeof( hirees ) ) { count = sizeof( hirees ); } /* Hire as many people as we can */ for ( x = 0; x < count; x++ ) { hire( hirees[x] ); } save_me(); } /* check_hire_list() */ /** * @ignore yes * Send a mail to each applicant awaiting a vacancy to let them know * their position in the queue. */ void mail_hirees() { int x; string *hirees; /* Make sure we are mailing the correct person with the correct position * by sorting the list by time. */ hirees = sort_array( keys( filter( _applicants, (: _applicants[$1][APP_TYPE] == AWAITING :) ) ), (: _applicants[$1][APP_TIME] - _applicants[$2][APP_TIME] :) ); for( x = 0; x < sizeof( hirees ); x++ ) { AUTO_MAILER->auto_mail( hirees[x], _proprietor, _shop_name, "", "I am writing to inform you that you have now moved to position "+ query_num( x + 1 )+ " in the employment waiting list. If you " "are not near the top of the list, please be patient.\nThank you.\n"); } } /* mail_hirees() */ /** * @ignore yes * Employee bits are set upon promotion, or when clocking in. */ private void set_employee( string word, int bit ) { if( !_employees[word] ) return; if( bit < EMPLOYEE || bit > CLOCKED_IN ) { #ifdef DEBUG debug_printf("Trying to set an employee bit < %d || > %d\n", EMPLOYEE, CLOCKED_IN ); #endif return; } _employees[word][EMP_POINTS] |= bit; save_emps(); } /* set_employee() */ /** * @ignore yes * Employee bits are reset on clocking out. */ void reset_employee( string word, int bit ) { if( !_employees[word] ) return; if( bit < EMPLOYEE || bit > CLOCKED_IN ) { #ifdef DEBUG debug_printf("Trying to reset an employee bit < %d || > %d\n", EMPLOYEE, CLOCKED_IN ); #endif return; } _employees[word][EMP_POINTS] -= _employees[word][EMP_POINTS] & bit; save_emps(); } /* reset_employee() */ /** * Query the list of items sold by the shop. * @return The list of items, formatted nicely in a multiple_short string. */ string query_list_string() { if( !sizeof( _list ) ) return "absolutely nothing at the moment"; return query_multiple_short( keys( _list ) ); } /* query_list_string() */ /** * Query the list of items sold by the shop. * @return The list of items in array form. */ string *query_list_array() { return copy(m_indices( _list ) + ({ })); } /** * Query the list of items sold by the shop. * @return The list of items in mapping form. */ mapping query_list_mapping() { return copy( _list ) + ([]); } /** * @ignore yes * Used by the shop front when an applicant confirms their employment * after being accepted. */ private void confirm_employment( string applicant ) { applicant = lower_case( applicant ); _applicants[applicant][APP_TYPE] = AWAITING; _applicants[applicant][APP_TIME] = time(); tell_object( find_player( applicant ), "You have now been added to " "our waiting list. You will be notified of your position in the " "list as it changes. You may \"cancel\" your application at " "any time. You are currently at position " + sizeof( m_indices( filter( _applicants, (: _applicants[$1][APP_TYPE] == AWAITING :) ) ) )+ " in the waiting list.\n" ); save_me(); } /* confirm_employment() */ /** * @ignore yes * Used when employees are fired by managers, or automatically. */ private void fire_them( string word, string them, string reason ) { if ( !_employees[them] ) { return; } BANK->adjust_account( them, BANKS[_employees[them][EMP_BANK]][1], _employees[them][EMP_PAY] ); shop_log( ACCOUNTS, _proprietor, "paid "+ MONEY_HAND->money_value_string( _employees[them][EMP_PAY], _place )+ " to "+ capitalize( them ) ); shop_log( PERSONNEL, word, "fired "+ capitalize( them ) + " for "+ reason ); AUTO_MAILER->auto_mail( them, word, _shop_name, "", "Unfortunately, I have to inform you that you have today " "been fired for " + reason + ". You have been paid the sum of "+ MONEY_HAND->money_value_string( _employees[them][EMP_PAY], _place )+ " for the work you have carried out to this date.\nIf you feel you " "have been unfairly dismissed, please refer to a manager.\n" ); employee_log( them, "Fired by "+ capitalize( word )+ " for "+ reason ); remove_employee( them ); } /* fire_them() */ /** * @ignore yes * Remove this person from the list of people banned from the shop. */ private void remove_baddie( string word ) { if ( !query_baddie( word ) ) { return; } map_delete( _baddies, word ); save_me(); } /* remove_baddie() */ /** * @ignore yes * Called once a day to conduct the shop's daily review. * This method is the daily maintenance function for the shop. It checks that * employees are still valid players, and not creators. It conducts automatic * promotions, and handles demotions for inactive employees. It also updates * the lists of declined applicants and banned people and removes that status * if applicable. Finally, it calls the check_hire_list function to see if * we can hire any new employees. */ void review_employees() { int prom, prom_number, managers, time; string word, *promos, promopost, *emps; emps = _retired; emps += m_indices( _employees ); foreach ( word in emps ) { if ( !"/secure/login"->test_user( word ) ) { /* Make sure we are not firing the npc shopkeeper */ if ( _employees[word][EMP_POINTS] & NPC ) { continue; } fire_them( _proprietor, word, "not existing" ); } if ( creatorp(word) ) { fire_them( _proprietor, word, "being a creator" ); } } promos = ({ }); prom = FALSE; time = time(); /* Check for inactive managers */ foreach ( word in get_managers() ) { if ( ( time - _employees[ word ][EMP_TIME] ) > (60*60*24*MGR_DEMOTE) ) { demote( _proprietor, word ); } else if ( time - _employees[word][EMP_TIME] > (60*60*24*MGR_WARN) && !_employees[word][EMP_INACTIVE] ) { AUTO_MAILER->auto_mail( word, _proprietor, "Poor attendance", "", "It has come to my attention that you have now been " "inactive for over " + MGR_WARN+ " days. As you are a manager, " "you are required to meet certain levels of attendance. " "You are now in serious danger of being demoted without " "further warning.\n---\n" + _proprietor+ " (proprietor)\n" ); _employees[word][EMP_INACTIVE] = TRUE; employee_log( word, "Warned about inactivity" ); shop_log( PERSONNEL, _proprietor, "warned "+ capitalize( word ) + " about inactivity"); } } /* Check supervisors for inactivity or promotion. Sorted by points so * people promoted in order. */ foreach( word in sort_array( get_supervisors(), (: _employees[$1][EMP_POINTS] - _employees[$2][EMP_POINTS] :) ) ) { if ( time - _employees[word][EMP_TIME] > (60*60*24*SPR_DEMOTE) ) { demote( _proprietor, word ); } else if ( time - _employees[word][EMP_TIME] > (60*60*24*SPR_WARN) && !_employees[word][EMP_INACTIVE] ) { AUTO_MAILER->auto_mail( word, _proprietor, "Poor attendance", "", "It has come to my attention that you have now been " "inactive for over "+ SPR_WARN+ " days. As you are a supervisor, " "you are required to meet certain levels of attendance. " "You are now in serious danger of being demoted without " "further warning.\n---\n" + _proprietor+ " (proprietor)\n" ); _employees[word][EMP_INACTIVE] = TRUE; employee_log( word, "Warned about inactivity" ); shop_log( PERSONNEL, _proprietor, "warned "+ capitalize( word ) + " about inactivity"); } else { /* See if any managerial vacancies. If so, and this person has * sufficient points & is not being ignored for promotion, * promote them. */ prom_number = ( _max_emp * PERCENT_M ) / 100; if ( ( _employees[word][EMP_POINTS] > 32 * MANAGER_POINTS ) && sizeof( get_managers() ) < prom_number && !_employees[word][EMP_NOPROMOTE] ) { set_employee( word, MANAGER ); shop_log( PERSONNEL, _proprietor, "promoted "+ capitalize( word )+ " to manager" ); employee_log( word, "Promoted to manager" ); AUTO_MAILER->auto_mail( word, _proprietor, "Promotion!", "", "Congratulations! You've been promoted to manager " "of "+ _shop_name+ ". You'll find that you can now enter " "the managers' office. Please remember to use the \"memo\" " "facility from there to discuss any major admin points with " "other managers. This includes hiring, firing, and so on.\n" ); promos += ({ word }); prom = TRUE; } } } /* Check employees for inactivity or promotion. Sorted by points so * people promoted in order. */ foreach( word in sort_array( get_employees(), (: _employees[$1][EMP_POINTS] - _employees[$2][EMP_POINTS] :) ) ) { if ( _employees[word][EMP_POINTS] & NPC ) { continue; } if ( time - _employees[word][EMP_TIME] > (60*60*24*EMP_FIRE) ) { fire_them( _proprietor, word, "serious inactivity" ); continue; } if ( time - _employees[word][EMP_TIME] > (60*60*24*EMP_WARN) ) { if ( !_employees[word][EMP_INACTIVE] ) { AUTO_MAILER->auto_mail( word, _proprietor, "Inactivity", "", "It has come to my attention that you have now been " "inactive for over "+ EMP_WARN+ " days. Unless this " "situation is resolved, the management may have no option " "but to terminate your employment.\n---\n"+ _proprietor+ " (proprietor)\n" ); _employees[word][EMP_INACTIVE] = TRUE; shop_log( PERSONNEL, _proprietor, "warned "+ capitalize( word )+ " about inactivity" ); employee_log( word, "Warned about inactivity" ); } } else { /* See if any supervisory vacancies. If so, and this person has * sufficient points & is not being ignored for promotion, * promote them. */ prom_number = ( _max_emp * PERCENT_S ) / 100; if ( ( _employees[word][EMP_POINTS] > 32 * SUPER_POINTS ) && sizeof( get_supervisors() ) < prom_number && !_employees[word][EMP_NOPROMOTE] ) { if( _employees[word][EMP_POINTS] & CLOCKED_IN ) { _employees[word][EMP_POINTS] = (SUPER_POINTS * 32) + EMPLOYEE+ SUPERVISOR + CLOCKED_IN; } else { _employees[word][EMP_POINTS] = (SUPER_POINTS * 32) + EMPLOYEE+ SUPERVISOR; } shop_log( PERSONNEL, _proprietor, "promoted "+ capitalize( word )+ " to supervisor" ); employee_log( word, "Promoted to supervisor" ); AUTO_MAILER->auto_mail( word, _proprietor, "Promotion!", "", "Congratulations! You've been promoted to supervisor " "of "+ _shop_name+ ". You will now be able to use your " "newly acquired supervisor commands.\n" ); promos += ({ word }); prom = TRUE; } } } /* Post about promotions */ if ( prom ) { promopost = "The following employees have been promoted:\n\n"; foreach ( word in promos ) { promopost += sprintf( " %s has been promoted to %s\n", capitalize( word ), (_employees[word][EMP_POINTS] & MANAGER)? "manager":"supervisor"); } promopost += "\nCongratulations!\n"; add_board_message( "Promotions", promopost ); } /* Check the list of banned people */ foreach ( word in m_indices( _baddies ) ) { if ( time - _baddies[word][BAD_TIME] > (60*60*24*BAN_LENGTH) ) { remove_baddie( word ); } } /* Check the list of declined applicants */ foreach ( word in m_indices( _declined ) ) { if ( time - _declined[word] > (60*60*24*DECLINE_LENGTH) ) { remove_declined( word ); } } /* See if anyone can be hired */ remove_call_out( _call_hire_list ); _call_hire_list = call_out( "check_hire_list", 5 ); save_emps(); /* Update policies */ managers = sizeof( get_managers() ) + sizeof( get_retired() ); foreach ( word in m_indices( _new_policies ) ) { if ( time - _new_policies[word][0] > (60*60*24*14) ) { if ( sizeof( _new_policies[word][3] ) > managers / 2 ) { add_policy( word ); } else { remove_policy( word ); } } } /* Update the player history data */ foreach ( word in m_indices( _history ) ) { if ( !"/secure/login"->test_user( word ) || creatorp( word ) || !_times[word] || _times[word] < (time - HIST_TIMEOUT) ) { map_delete( _times, word ); map_delete( _history, word ); } } save_hist(); save_times(); } /* review_employees() */ /** * Calculate total employee wage packet for this month. * @return The current value of the months' wages. */ int calc_pay() { string word, *emps; int amount = 0; emps = m_indices( _employees ); foreach ( word in emps ) { if ( _employees[word][EMP_POINTS] & NPC ) { continue; } amount += _employees[word][EMP_PAY]; } return amount; } /* calc_pay() */ /** * Calculate the months' projected bonuses. * These values are based upon the current staff levels, as well as * the amount of money in the bonus account. The result is written * directly to this_player(). */ void calc_bonus() { int bonus_val, bonus_divisor = 0; string word; foreach ( word in m_indices( _employees ) ) { if ( _employees[word][EMP_POINTS] & NPC || _employees[word][EMP_NOBONUS] ) { continue; } if ( _employees[word][EMP_POINTS] & MANAGER ) { bonus_divisor += 4; } else if ( _employees[word][EMP_POINTS] & SUPERVISOR ) { bonus_divisor += 3; } else { bonus_divisor += 2; } } if ( !bonus_divisor ) { bonus_val = _accounts["bonus"]; } else { bonus_val = ( _accounts["bonus"] * 2 ) / bonus_divisor; } word = "Based on the bonus fund of "+ MONEY_HAND->money_value_string( _accounts["bonus"], _place )+ ", the following bonuses are anticipated:\n" "\n Managers - "+ MONEY_HAND->money_value_string( bonus_val * 2, _place )+ "\n Supervisors - "+ MONEY_HAND->money_value_string( to_int( bonus_val * 1.5 ), _place )+ "\n Employees - "+ MONEY_HAND->money_value_string( bonus_val, _place )+ "\n"; tell_object( this_player(), word ); } /* calc_bonus() */ /** * @ignore yes * Called once a month to conduct the shop's monthly review. * This method is the monthly maintenance function for the shop. This review * involves paying employees direct into their nominated bank account, and * awarding bonuses based on the current value of the bonus fund. If there is * not enough money in the profit account to pay the correct amount, the employees * are paid on a pro-rata basis. */ void monthly_review(string month) { int amount, bonus_divisor, cabinet_cost, pay; float pay_multiplier; string *emps, post, word; // MONTH THINGIE IS BUGGERED HERE. amount = bonus_divisor = 0; pay_multiplier = 1.0; emps = keys( _employees ); amount = calc_pay(); cabinet_cost = ( _num_cabinets - MIN_CABINETS ) * CABINET_COST; adjust_profit( _proprietor, -cabinet_cost ); shop_log( ACCOUNTS, _proprietor, "paid "+ MONEY_H->money_value_string( cabinet_cost, _place )+" for the rent " "of " + ( _num_cabinets - MIN_CABINETS ) + " cabinets" ); if( amount > _accounts["profit"] ) { pay_multiplier = _accounts["profit"] / amount; } foreach ( word in emps ) { if( _employees[word][EMP_POINTS] & NPC ) { continue; } if( !_employees[word][EMP_PAY] ) { continue; } pay = to_int(_employees[word][EMP_PAY] * pay_multiplier); BANK->adjust_account( word, BANKS[_employees[word][EMP_BANK]][1], pay ); shop_log( ACCOUNTS, _proprietor, "paid "+ MONEY_HAND-> money_value_string( pay, _place )+ " to "+ capitalize( word ) ); AUTO_MAILER->auto_mail( word, _proprietor, "Pay advice for "+ _last_month, "", "For your work during "+ _last_month+ ", you have been paid a total of "+ MONEY_HAND->money_value_string( pay, _place )+ ". Keep up the good work.\n--\n"+ _proprietor+ " (proprietor)\n" ); _employees[word][EMP_PAY] = 0; _accounts["profit"] -= pay; } _bonus += _accounts["bonus"]; _accounts["bonus"] = 0; foreach ( word in m_indices( _employees ) ) { if ( _employees[word][EMP_POINTS] & NPC || _employees[word][EMP_NOBONUS] ) { continue; } if ( _employees[word][EMP_POINTS] & MANAGER ) { bonus_divisor += 4; } else if ( _employees[word][EMP_POINTS] & SUPERVISOR ) { bonus_divisor += 3; } else { bonus_divisor += 2; } } if ( !bonus_divisor ) { _bonus_val = _bonus; } else { _bonus_val = ( _bonus * 2 ) / bonus_divisor; } _got_bonus = ({ }); foreach ( word in m_indices( _employees ) ) { if ( _employees[word][EMP_POINTS] & NPC ) { continue; } if ( _employees[word][EMP_NOBONUS] ) { _employees[word][EMP_NOBONUS]--; _got_bonus += ({ word }); } } post = "Based on the bonus fund of "+ MONEY_HAND->money_value_string( _bonus, _place )+ " for "+ month+ ", the following bonuses have been awarded:\n" "\n Managers - "+ MONEY_HAND->money_value_string( _bonus_val * 2, _place )+ "\n Supervisors - "+ MONEY_HAND->money_value_string( to_int( _bonus_val * 1.5 ), _place )+ "\n Employees - "+ MONEY_HAND->money_value_string( _bonus_val, _place )+ "\n\nKeep up the good work.\n"; add_board_message( "Bonuses for "+ month, post ); shop_log( ACCOUNTS, _proprietor, "paid out "+ MONEY_HAND->money_value_string( _bonus, _place )+ " in bonuses for "+ month ); _last_month = month; save_me(); save_emps(); } /* monthly_review() */ /** * Query the people banned from the shop. * This method returns a mapping of the people banned from this shop, along * with the time that they were banned, the person banning them, and the * reason they were banned. The format for this mapping is:<br> * ([ person:({ reason, banner, time }) ]) * @return The mapping, formatted above. */ mapping get_baddies() { return copy(_baddies + ([ ])); } /** * @ignore yes * Ban this person from the shop. * This person will be banned for a specific period, and will not be allowed * back in the shop within this period. People can be banned by managers, or * automatically for attacking employees. */ private void add_baddie( string word, string reason, string banner ) { object ob; word = lower_case( word ); if ( query_baddie( word ) ) { /* * Added this bit after discovering someone had been banned 75 * times for killing Gretta @ Tarnach's- all at the same time. */ if ( _baddies[word][BAD_TIME] == time() || reason == _baddies[word][BAD_REASON] ) { return; } remove_baddie( word ); } if ( !sizeof( _baddies ) ) { _baddies = ([ word:({ "", "", 0, }) ]); } else { _baddies += ([ word:({ "", "", 0, }) ]); } _baddies[word][BAD_REASON] = reason; _baddies[word][BAD_BANNER] = banner; _baddies[word][BAD_TIME] = time(); save_me(); shop_log( GENERAL, capitalize(banner), "banned "+ capitalize( word )+ " for "+ reason ); employee_log( word, sprintf( "Banned by %s for %s.", capitalize(banner), reason ) ); fire_them( _proprietor, word, reason ); remove_applicant( word ); if ( !( ob = find_player( word ) ) ) { return; } /* If this person is in the shop front, move them outside. */ if ( environment( ob ) == find_object( _shop_front ) ) { tell_room( _shop_front, ob->query_short()+ " drifts out of the door, " "seemingly against "+ ob->query_possessive()+ " will.\n", ({ ob }) ); tell_object( ob , "You feel yourself pushed out of the shop " "by a mysterious force.\n" ); tell_room( _shop_front->query_outside(), ob->query_short()+ " drifts through the door of "+ _shop_name + ", seemingly against "+ ob->query_possessive()+ " will.\n", ({ ob }) ); ob->move( _shop_front->query_outside() ); } } /* add_baddie() */ /** * Query the number of employees currently clocked in. * This function will also clock out any employees that are no longer on DW. * @return The number of employees clocked in. */ int num_employees_in() { int any = 0; string word; object ob; foreach ( word in m_indices( _employees ) ) { if ( _employees[word][EMP_POINTS] & CLOCKED_IN ) { if ( _employees[word][EMP_POINTS] & NPC ) { continue; } if ( !( ob = find_player( word ) ) || !interactive( ob ) ) { reset_employee( word, CLOCKED_IN ); shop_log( GENERAL, word, "was clocked out" ); } else { any++; } } } return any; } /* num_employees_in() */ /** * @ignore yes * Called by the npc shopkeeper to limit loading patterns. */ void set_npc_time() { _npc_time = time(); } /** * @ignore yes * Loads the npc shopkeeper and calls him/her/it to work. Will only reload * the npc once every NPC_DELAY, unless clock_out is non-zero in which case the * npc will be loaded regardless - this parameter indicates that the npc is * being recalled due to an employee clocking out. */ void summon_shopkeeper( int clock_out ) { /* Already loaded */ if ( find_object( _shopkeeper ) ) { return; } /* Don't appear if any employees in */ if ( num_employees_in() ) { return; } /* Start shift */ if ( ( time() - _npc_time > NPC_RELOAD ) || clock_out ) { load_object(_shopkeeper)->start_shift(); } } /* summon_shopkeeper() */ /** * @ignore yes * A nicely formatted employee list for the employee badge. */ string badge_list() { string word, temp_result, *results, tp_name; object ob, tp; results = ({}); tp = this_player(); tp_name = tp->query_name(); foreach( word in sort_array( _retired, 1 ) ) { if ( ( ob = find_player( word ) ) && !ob->query_invis() ) { results += ({ "%^CYAN%^"+ ob->query_cap_name() +"%^RESET%^ (R)" }); } } foreach ( word in get_managers() ) { temp_result = ""; if ( ( ob = find_player( word ) ) && !ob->query_invis() ) { if( ( creatorp(tp) || ( _employees[tp_name][EMP_POINTS] & SUPERVISOR ) ) && ( _employees[word][EMP_POINTS] & CLOCKED_IN ) ) { temp_result = "%^YELLOW%^"; } results += ({ temp_result+ ob->query_cap_name()+ "%^RESET%^ (m)" }); } } foreach( word in get_supervisors() ) { temp_result = ""; if( ( ob = find_player( word ) ) && !ob->query_invis() ) { if( ( creatorp(tp) || ( _employees[tp_name][EMP_POINTS] & SUPERVISOR ) ) && ( _employees[word][EMP_POINTS] & CLOCKED_IN ) ) { temp_result = "%^YELLOW%^"; } results += ({ temp_result+ ob->query_cap_name()+ "%^RESET%^ (s)" }); } } foreach( word in get_employees() ) { temp_result = ""; if( ( ob = find_player( word ) ) && !ob->query_invis() ) { if( ( creatorp(tp) || ( _employees[tp_name][EMP_POINTS] & SUPERVISOR ) ) && ( _employees[word][EMP_POINTS] & CLOCKED_IN ) ) { temp_result = "%^YELLOW%^"; } results += ({ temp_result+ ob->query_cap_name()+ "%^RESET%^" }); } } if( !sizeof( results ) ) { /* Will only occur if no employees logged on, and a creator is using * badge. */ return "Just you, I'm afraid.\n"; } return query_multiple_short( results ) + "\n"; } /* badge_list() */ /** * @ignore yes * The list of employees clocked in used by the extra_look in the shop front. */ string employees_clocked_in() { string word; object *words, ob; words = ({ }); if( !num_employees_in() ) return "No employees"; foreach( word in keys( _employees ) ) { if( ( ob = find_player( word ) ) && !ob->query_invis() ) { if( _employees[word][EMP_POINTS] & CLOCKED_IN ) words += ({ ob->query_cap_name() }); } } return query_multiple_short( words ); } /* employees_clocked_in() */ /** * Checks to see if the specified employee is clocked in. * @param ob the object to check */ int employee_clocked_in(object ob) { if(!query_employee(ob->query_name())) return 0; return (_employees[ob->query_name()][EMP_POINTS] & CLOCKED_IN); } /* employee_clocked_in() */ /** * @ignore yes * Used by the managers' office whenever a manager casts a vote * on an applicant. */ private void add_vote( string applicant, int vote ) { int managers; string voter; voter = this_player()->query_name(); managers = sizeof( get_managers() ); if ( vote == VABSTAIN ) { _applicants[applicant][APP_ABSTAIN] += ({ voter }); } else if ( vote == VFOR ) { _applicants[applicant][APP_FOR] += ({ voter }); } else { _applicants[applicant][APP_AGAINST] += ({ voter }); } shop_log( GENERAL, voter, "voted on "+ capitalize( applicant ) ); save_me(); managers -= sizeof( _applicants[applicant][APP_ABSTAIN] ); if ( sizeof( _applicants[applicant][APP_FOR] ) >= managers / 2 ) { /* This applicant has the vote of at least 50% * of managers so hire them. */ AUTO_MAILER->auto_mail( applicant, _proprietor, _shop_name, "", "Congratulations! You've been accepted to work at "+ _shop_name+ ". Please return to the shop within the next " "seven days to \"confirm\" that you wish to accept " "the position.\n" ); _applicants[applicant][APP_TYPE] = HIRED; _applicants[applicant][APP_TIME] = time(); employee_log( applicant, "Application was accepted" ); } else if ( sizeof( _applicants[applicant][APP_AGAINST] ) > managers / 2 ) { /* This applicant has been voted against by over 50% of the * managers so decline them. */ remove_applicant( applicant ); AUTO_MAILER->auto_mail( applicant, _proprietor, _shop_name, "", "Thank you for your recent application for employment " "with us. Unfortunately, I have to inform you that you " "have been unsuccessful at this time. Please feel free " "to re-apply again in the future, when your application " "will be re-considered.\n" ); employee_log( applicant, "Application was declined" ); add_declined( applicant ); } } /* add_vote() */ /** * @ignore yes * Used by the managers' office to determine whether a manager has * already voted on this applicant. */ int check_vote( string applicant, string voter ) { if ( member_array( voter, _applicants[applicant][APP_FOR] ) != -1 || member_array( voter, _applicants[applicant][APP_AGAINST] ) != -1 || member_array( voter, _applicants[applicant][APP_ABSTAIN] ) != -1 ) { return TRUE; } return FALSE; } /* check_vote() */ /** * @ignore yes * Updates the chart with the average stock of an item. */ void update_averages() { string words; foreach ( words in m_indices( _list ) ) { _list[words][CHART_AVE] = ( _list[words][CHART_AVE] + query_stock( words ) + random(2) ) / 2; } save_list(); } /* update_averages() */ /** * @ignore yes * Query if the shop sells this item. * @param item The item to query. * @return TRUE or FALSE */ int query_on_list( string item ) { return (!_list[item])?FALSE:TRUE; } /** * @ignore yes * Add an item for the shop to deal in. */ private void put_on_list( string word ) { if ( !_list[word] ) { _list[word] = ({ 5, 0, 20, 25, ({}), 0, 0, 0 }); save_list(); } } /* put_on_list() */ /** * @ignore yes * Stop the shop dealing in this item. */ private void take_off_list( string word ) { if ( !_list[word] ) { return; } map_delete( _list, word ); save_list(); } /* take_off_list() */ /** * @ignore yes * Query the maximum number of this item allowed in stock. * @param item The item to query. * @return The maximum stock. */ int query_max( string item ) { return ( !_list[item] ) ? 0 : copy(_list[item][CHART_MAX]); } /* query_max() */ /** * @ignore yes * Set the maximum number of this item allowed in stock. */ private void set_max( string word, int number ) { if ( !_list[word] ) { return; } _list[word][CHART_MAX] = number; save_list(); } /* set_max() */ /** * @ignore yes * Query the average number of this item in stock. * @param item The item to query. * @return The average stock. */ int query_ave( string item ) { return ( !_list[item] ) ? 0 : copy(_list[item][CHART_AVE]); } /* query_ave() */ /** * @ignore yes * Query the buying price of this item. * This is the actual value, in the smallest unit of local currency * (eg. pence, farthings etc), rather than the absolute value. * @param item The item to query. * @return The buy price. */ int query_buy( string item ) { return ( !_list[item] ) ? 0 : copy(_list[item][CHART_BUY]); } /* query_buy() */ /** * @ignore yes * Set the buy price of this item. */ private void set_buy( string word, int number ) { if ( !_list[ word ] ) { return; } _list[word][CHART_BUY] = number; save_list(); } /* set_buy() */ /** * @ignore yes * Query the sell price of this item. * This is the actual value, in the smallest unit of local currency * (eg. pence, farthings etc), rather than the absolute value. * @param item The item to query. * @return The sell price. */ int query_sell( string item ) { return ( !_list[item] ) ? 0 : copy(_list[item][CHART_SELL]); } /* query_sell() */ /** * @ignore yes * Set the sell price of this item. */ private void set_sell( string word, int number ) { if ( !_list[word] ) { return; } _list[word][CHART_SELL] = number; save_list(); } /* set_sell() */ /** * @ignore yes * Query whether this cabinet is currently assigned to any items. * @param cabinet The cabinet number ( between 1 and number of cabinets ) * @return TRUE or FALSE */ int query_cabinet_used( int cabinet ) { string word; foreach( word in m_indices(_list) ) { if ( member_array( cabinet, _list[word][CHART_CAB] ) != -1 ) { return TRUE; } } return FALSE; } /* query_cabinet_used() */ /** * @ignore yes * Query which cabinets are assigned to this item. * @param item The item to query. * @return An array of cabinet numbers assigned to the item. */ int *query_cabinet( string item ) { return ( !_list[item] ) ? ({}) : copy(_list[item][CHART_CAB]); } /* query_cabinet() */ /** * @ignore yes * Assign an item to use a cabinet. */ private void add_chart_cabinet( string word, int number ) { if ( !_list[word] ) { return; } _list[word][CHART_CAB] += ({ number }); _list[word][CHART_CAB] = sort_array( _list[word][CHART_CAB], 1 ); save_list(); } /* add_chart_cabinet() */ /** * @ignore yes * Stop an item using a cabinet. */ private void remove_chart_cabinet( string word, int number ) { if ( !_list[word] ) { return; } _list[word][CHART_CAB] -= ({ number }); save_list(); } /* remove_chart_cabinet() */ /** * @ignore yes * Used by the employee list to show managers & creators the last * time an employee was active. Employees will be highlighted yellow * if they are currently on an inactivity warning, and red if they are * within 7 days of being fired/demoted */ private string query_worked( string emp ) { string blurb; /* Clocked in */ if ( _employees[emp][EMP_POINTS] & CLOCKED_IN ) { return " is currently clocked in"; } /* NPC */ if ( _employees[emp][EMP_POINTS] & NPC ) { return " has gone home for tea"; } /* On leave */ if ( _employees[emp][EMP_TIME] > time() ) { return " - %^CYAN%^on leave until "+ ctime( _employees[emp][EMP_TIME] ) + "%^RESET%^"; } blurb = " - last action "; if ( _employees[emp][EMP_POINTS] & MANAGER ) { if ( ( time() - _employees[emp][EMP_TIME] ) > ((60*60*24*MGR_DEMOTE)-7) ) { blurb += "%^RED%^"; } else if ( ( time() - _employees[emp][EMP_TIME] ) > (60*60*24*MGR_WARN) ) { blurb += "%^RED%^"; } } else if ( _employees[emp][EMP_POINTS] & SUPERVISOR ) { if ( ( time() - _employees[emp][EMP_TIME] ) > ((60*60*24*SPR_DEMOTE)-7) ) { blurb += "%^RED%^"; } else if ( ( time() - _employees[emp][EMP_TIME] ) > (60*60*24*SPR_WARN) ) { blurb += "%^YELLOW%^"; } } else if ( ( time() - _employees[emp][EMP_TIME] ) > ((60*60*24*EMP_FIRE)-7) ) { blurb += "%^RED%^"; } else if ( ( time() - _employees[emp][EMP_TIME] ) > (60*60*24*EMP_WARN) ) { blurb += "%^YELLOW%^"; } blurb += ctime( _employees[emp][EMP_TIME] ) + "%^RESET%^"; return( blurb ); } /* query_worked() */ /** * @ignore yes * This function is used by employees to clock in and out of work. * Clocking out may also summon the npc shopkeeper. */ int do_clock( string clock ) { string word; object tp; tp=this_player(); if ( creatorp(tp) ) { tell_object( tp, "Creators cannot clock "+clock+"!\n" ); return 1; } word = tp->query_name(); switch( clock ) { case "in" : if ( _employees[word][EMP_POINTS] & CLOCKED_IN ) { tell_object( tp, "You are already clocked in!\n" ); return 1; } /* Stop the shopkeeper appearing if on their way */ remove_call_out( _call_summon ); /* Is this person a cre alt? If so, prevent promotion. */ if ( tp->query_property( "no score" ) ) { _employees[word][EMP_NOPROMOTE] = TRUE; } set_employee( word, CLOCKED_IN ); break; case "out" : if ( !_employees[word][EMP_POINTS] & CLOCKED_IN ) { tell_object( tp, "You are already clocked out!\n" ); return 1; } /* Call npc shopkeeper to work */ remove_call_out( _call_summon ); _call_summon = call_out( "summon_shopkeeper", 60, 1 ); reset_employee( word, CLOCKED_IN ); break; } add_succeeded_mess( "$N $V "+ clock+ ".\n" ); shop_log( GENERAL, word, "clocked "+ clock ); return 1; } /* do_clock() */ /** * @ignore yes * Displays an employee's percentage towards promotion, if applicable. * Displays all employees' scores to creators. * Displays last-action (leave) times to managers. */ string list_stuff( string word ) { string results = "", tp; /* Nothing to display for retired managers */ if ( member_array( word, _retired ) != -1 ) { return "\n"; } tp = this_player()->query_name(); /* Npc shopkeeper */ if ( _employees[word][EMP_POINTS] & NPC ) { return query_worked( word ) + "\n"; } /* Creator viewing - display all scores */ if ( creatorp(this_player()) ) { results = " ["+ ( _employees[word][EMP_POINTS] / 32 ) +"]"; } else if ( ( word == tp ) && !(_employees[word][EMP_POINTS] & MANAGER) ) { if ( _employees[word][EMP_POINTS] & SUPERVISOR ) { results = " ["+ ( ( 100 * ( _employees[word][EMP_POINTS] / 32 - SUPER_POINTS )) / ( MANAGER_POINTS - SUPER_POINTS )) +"%]"; } else { results = " ["+ ( ( 100 * ( _employees[word][EMP_POINTS] / 32 ) ) / SUPER_POINTS ) +"%]"; } } /* Last-action times displayed to managers & creators */ if ( creatorp(this_player()) || (_employees[tp][EMP_POINTS] & MANAGER) ) { results += query_worked( word ); } return results + "\n"; } /* list_stuff() */ /** * @ignore yes * Employee wishes to terminate their employment with the shop. */ int do_resign() { string word; word = this_player()->query_name(); add_succeeded_mess( "$N $V.\n" ); remove_employee( word ); shop_log( PERSONNEL, word, "resigned" ); employee_log( word, "Resigned" ); return 1; } /* do_resign() */ /** * @ignore yes * The list of employees in the shop. * This list is displayed sorted primarily by status (Retired, manager, * supervisor, riff-raff) and then alphabetically within those groups. * It also highlights those employees currently logged on (as long as they * are visible). */ int do_list() { string word, results; int emps_in; object ob; results = " Employees of "+ _shop_name + "\n" " As at " + ctime( time() ) + "\n\n"; /* Retired managers */ foreach ( word in sort_array( _retired, 1 ) ) { if ( (ob = find_player( word )) && !ob->query_invis() ) { results += "%^GREEN%^"; } results += " "+ capitalize( word )+ "%^RESET%^ (retired manager)\n"; } /* Managers */ foreach ( word in get_managers() ) { if ( (ob = find_player( word )) && !ob->query_invis() ) { emps_in ++; results += "%^GREEN%^"; } results += " "+ capitalize( word )+ "%^RESET%^ (manager)"+ list_stuff( word ); } /* Supervisors */ foreach ( word in get_supervisors() ) { if ( (ob = find_player( word )) && !ob->query_invis() ) { emps_in ++; results += "%^GREEN%^"; } results += " "+ capitalize( word )+ "%^RESET%^ (supervisor)"+ list_stuff( word ); } /* Riff-raff */ foreach ( word in get_employees() ) { if ( (ob = find_player( word )) && !ob->query_invis() ) { emps_in ++; results += "%^GREEN%^"; } results += " "+ capitalize( word )+ "%^RESET%^"+ list_stuff( word ); } results += "\n"; /* If viewed by manager or creator, display number of employees logged * on and number clocked in. */ if( creatorp(TP) || _employees[TP->query_name()][EMP_POINTS] & MANAGER ) { if ( emps_in == 1 ) { results += " There is one employee "; } else { results += " There are "+ query_num(emps_in,0)+ " employees "; } results += "out of "+ query_num( sizeof( _employees ) )+ " on Discworld at the moment, with "+ query_num( num_employees_in(), 0 ) + " currently clocked in.\n\n"; } tell_object( this_player(), "$P$Employees$P$"+ results ); add_succeeded_mess( "" ); return 1; } /* do_list() */ /** * @ignore yes * Claim a badge, handbook, or bonus. */ int do_claim( string item ) { object thing, tp; int give_bonus; string tp_name; tp = this_player(); switch( item ) { case "badge" : thing = clone_object( BADGE ); thing->set_props( file_name( this_object() ), tp->query_name() ); break; case "handbook" : thing = clone_object( STAFF_BOOK ); thing->set_read_mess("\n "+ _shop_name+ "\n\n" " Staff Handbook\n\n This handbook belongs to: "+ tp->query_short()+ "\n\n", "common", 100 ); thing->set_office( file_name( this_object() ) ); break; case "bonus" : tp_name = tp->query_name(); /* Already claimed, new employee, or had bonus suspended. */ if ( member_array( tp_name, _got_bonus ) != -1 ) { tell_object( tp, "You are not entitled to a " "bonus this month!\n" ); return 1; } /* Value depends on status */ if ( _employees[tp_name][EMP_POINTS] & MANAGER ) { give_bonus = _bonus_val * 2; } if ( _employees[tp_name][EMP_POINTS] & SUPERVISOR ) { give_bonus = to_int( _bonus_val * 1.5 ); } else { give_bonus = _bonus_val; } /* Nothing to give */ if ( !give_bonus ) { tell_object( tp, "There is no money in the bonus " "fund this month.\n" ); return 1; } /* Prevent them from claiming again this month */ _got_bonus += ({ tp_name }); _bonus -= give_bonus; /* Update value of unclaimed bonus */ if ( _bonus < 0 ) { _bonus = 0; } thing = MONEY_HAND->make_new_amount( give_bonus, _place ); shop_log( GENERAL, tp_name, "claimed "+ MONEY_HAND->money_value_string( give_bonus, _place ) ); if ( thing->move( tp ) != MOVE_OK ) { thing->move( environment( tp ) ); tell_object( tp, "You drop "+ thing->query_short()+ " as you're carrying too much.\n" ); } save_me(); add_succeeded_mess( "$N $V "+ MONEY_HAND-> money_value_string( give_bonus, _place )+ ".\n" ); return 1; break; } if ( thing->move( tp ) != MOVE_OK ) { thing->move( this_object() ); tell_object( tp, "You drop your "+ item+ " as you're carrying too much.\n" ); } add_succeeded_mess( "$N $V a new "+ item+ ".\n" ); return 1; } /* do_claim() */ /** * @ignore yes * This employee has requested to be passed over for promotion. */ int do_promote( string on ) { object tp; add_succeeded_mess( "" ); tp = this_player(); switch ( on ) { case "on" : _employees[tp->query_name()][EMP_NOPROMOTE] = TRUE; tell_object( tp, "You have now requested to be " "passed over for promotion.\n" ); break; case "off" : if ( tp->query_property( "no score" ) ) { tell_object( tp, "Creator alts cannot be promoted.\n" ); return 1; } _employees[tp->query_name()][EMP_NOPROMOTE] = FALSE; tell_object( tp, "You have now requested to be " "considered for promotion.\n" ); break; } save_emps(); return 1; } /* do_promote() */ /** * @ignore yes * Display or update the employee's bank details. */ int do_bank( mixed *args ) { string word, message; int x; object tp; tp = this_player(); if ( creatorp(tp) ) { tell_object( tp, "Why do you need to be paid?\n" ); return 1; } word = tp->query_name(); add_succeeded_mess( "" ); if ( !sizeof( args ) ) { message = "You are currently being paid into your account at "+ BANKS[_employees[word][EMP_BANK]][0]+ ". To change which bank " "you would like your pay to be deposited at, use the command " "\"bank <number>\" where <number> is one of the following:\n\n"; for ( x = 0; x < sizeof(BANKS); x++ ) { message += sprintf( " %d: %s\n", x+1, BANKS[x][0] ); } tell_object( tp, message + "\n" ); return 1; } if ( args[0] < 1 || args[0] > sizeof(BANKS) ) { tell_object( tp, "There are "+ sizeof(BANKS)+ " banks to choose from.\n" ); return 1; } _employees[word][EMP_BANK] = args[0] - 1; save_emps(); tell_object( tp, "You change your bank details.\n" ); return 1; } /* do_bank() */ /** * @ignore yes * Maintain the list of stock dealt in by this shop. */ int do_chart( mixed *args, string pattern ) { int number; string word, item; word = this_player()->query_name(); item = args[0]; if ( sizeof(args) > 1 ) { number = args[1]; } if ( pattern == "add <string'item'>" ) { if ( query_on_list( item ) ) { tell_object( this_player(), item + " are already on the sales list.\n" ); return 1; } } else if ( !query_on_list( item ) ) { tell_object( this_player(), item +" are not on the sales list.\n" ); return 1; } switch ( pattern ) { case "add <string'item'>" : /* Start dealing in this item */ add_succeeded_mess( "$N put$s "+ item +" on the sales list.\n" ); put_on_list( item ); shop_log( CHARTLOG, word, "put "+ item +" on the sales list" ); break; case "remove <string'item'>" : /* Stop dealing in this item */ add_succeeded_mess( "$N take$s "+item +" off the sales list.\n" ); take_off_list( item ); shop_log( CHARTLOG, word, "took "+item +" off the sales list" ); break; case "max <string'item'> <number>" : /* Set maximum stock */ add_succeeded_mess( "$N set$s the maximum stock of "+ item +" to "+ number +".\n" ); set_max( item, number ); shop_log( CHARTLOG, word, "set max of "+ item +" to "+ number ); break; case "buy <string'item'> <number>" : /* Set buy price */ add_succeeded_mess( "$N set$s the buying price of "+ item +" to "+ number +".\n" ); set_buy( item, number ); shop_log( CHARTLOG, word, "set buy of "+ item +" to "+ number ); break; case "sell <string'item'> <number>" : /* Set sell price */ add_succeeded_mess( "$N set$s the selling price of "+ item +" to "+ number +".\n" ); set_sell( item, number ); shop_log( CHARTLOG, word, "set sell of "+ item +" to "+ number ); break; case "assign <string'item'> [to cabinet] <number>" : /* Assign item to cabinets */ if ( member_array( number, _list[item][CHART_CAB] ) != -1 ) { tell_object( this_player(), item+ " are already assigned to cabinet "+ number+ ".\n" ); return 1; } if ( number < 1 || number > _num_cabinets ) { tell_object( this_player(), "That cabinet does not exist.\n" ); return 1; } add_chart_cabinet( item, number ); add_succeeded_mess( "$N assign$s "+ item +" to use cabinet "+ number +".\n" ); shop_log( CHARTLOG, word, "assigned "+ item + " to cabinet "+ number ); break; case "unassign <string'item'> [from cabinet] <number>" : /* Stop item using cabinets */ if ( member_array( number, _list[item][CHART_CAB] ) == -1 ) { tell_object( this_player(), item+ " are not assigned to cabinet "+ number+ ".\n" ); return 1; } remove_chart_cabinet( item, number ); add_succeeded_mess( "$N stop$s "+ item +" using cabinet "+ number +".\n" ); shop_log( CHARTLOG, word, "unassigned "+ item + " from cabinet "+ number ); break; } return 1; } /* do_chart() */ /** * @ignore yes * Check cabinet assignments */ int do_check() { string result, item, word,*items; int x; result = "Current cabinet assignments are:\n"; for( x = 0; x < _num_cabinets; x++ ) { result += sprintf( "Cabinet %2d: ", x+1 ); items = ({}); foreach( item in m_indices(_list) ) { if ( member_array( x+1, _list[item][CHART_CAB] ) != -1 ) { word = sprintf( "%s (%d)", item, _list[item][CHART_MAX] ); items += ({ word }); } } if ( sizeof( items ) ) { result += query_multiple_short( items ); } result += "\n"; } result += "\n"; tell_object( this_player(), "$P$Cabinet assignments$P$"+ result ); return 1; } /* do_check() */ /** * @ignore yes * Display the shop's logs. */ int do_logs( mixed *args, string pattern ) { int i; string start, end, file, *files, words; add_succeeded_mess(""); switch ( pattern ) { case "" : /* Display available logs */ files = get_dir( _log_file +"general.log*" ); words = "Available logs:\n\n"; for ( i = 0; i < sizeof( files ); i++ ) { if ( !i ) { words += " 1: current log\n"; continue; } sscanf( unguarded( (: read_file, _log_file + files[i], 2, 1 :) ), "%*s, %s: %*s", start ); sscanf( unguarded( (: read_file, _log_file + files[i], file_length( _log_file + files[i] ), 1 :) ), "%*s, %s: %*s", end ); if ( start == end ) { words += sprintf( "%2d: %s\n", i + 1, start ); continue; } words += sprintf( "%2d: %s to %s\n", i + 1, start, end ); } words += "\nUse \"logs <number>\" to read one of them, or logs " "{personnel|accounts|chart} to view those logs.\n"; tell_object( this_player(),"$P$Logs$P$"+ words ); break; case "<number>" : /* Display specific log */ files = get_dir( _log_file +"general.log*" ); if ( ( args[0] < 1 ) || ( args[0] > sizeof( files ) ) ) { tell_object( this_player(), "There are "+ sizeof( files ) + " logs. Use \"logs\".\n" ); return 1; } sscanf( unguarded( (: read_file, _log_file + files[args[0] - 1] :) ), "%*s\n%s", words ); tell_object( this_player(),"$P$Log "+ args[0]+ "$P$"+ words ); break; case "[chart]" : /* Display chart log */ file = _log_file + "chart.log"; if ( file_size( file ) > 0 ) { sscanf( unguarded( (: read_file, file :) ), "%s", words ); tell_object( this_player(),"$P$"+ capitalize(args[0])+ " log$P$"+ words ); } else { tell_object( this_player(), "The chart log is empty.\n" ); return 1; } break; case "{personnel|accounts|chart}" : /* Display special log */ file = _log_file + args[0] + ".log"; if ( file_size( file ) > 0 ) { sscanf( unguarded( (: read_file, file :) ), "%s", words ); tell_object( this_player(),"$P$"+ capitalize(args[0])+ " log$P$"+ words ); } else { tell_object( this_player(), "The "+ args[0]+ " log is empty.\n" ); return 1; } break; } return 1; } /* do_logs() */ /** * @ignore yes * Query the direction to another part of the shop. * This function is used by the npc shopkeeper to navigate * around the shop. */ string directions_to( string place ) { if ( place == _counter ) { return copy(_counter_dir); } if ( place == _storeroom ) { return copy(_store_dir); } if ( place == _shop_front ) { return copy(_shop_dir); } return "here"; } /* directions_to() */ /** * Set the location of the shop. * This is used by the money handling functions to determine which * currency to use and therefore should be one of the locations returned * by the query_all_places() function in /handlers/money_handler * @example set_place( "Ankh-Morpork" ); * @param place The location of this shop. */ protected void set_place( string place ) { _place = place; } /** * Set the npc shopkeeper object. * @example set_shopkeeper( CHARS + "gretta_shuffle" ); * @param path The full path to the shopkeeper. */ protected void set_shopkeeper( string path ) { _shopkeeper = path; } /* set_shopkeeper() */ /** * Set the path to the customer area. * @example set_shop_front( PATH + "consumables" ); * @param path The full path and filename to the customer area of the shop. */ protected void set_shop_front( string path ) { _shop_front = path; } /** * Set the path to the counter. * @example set_counter( PATH + "counter" ); * @param path The full path and filename to the shop's counter. */ protected void set_counter( string path ) { _counter = path; } /** * Set the path to save the log files to. * @example set_log_files( PATH + "logs/" ); * @param path The full path to the log files. */ protected void set_log_files( string path ) { _log_file = path; } /** * Set the path to the storeroom. * @example set_storeroom( PATH + "tarnach's_storeroom" ); * @param path The full path and filename to the storeroom of the shop. */ protected void set_storeroom( string path ) { _storeroom = path; } /** * Query the name of the proprietor. */ string query_proprietor() { return copy(_proprietor); } /** * Query the channel used by the shop. */ string query_channel() { return copy(_channel); } /** * Query the value of the profit account. */ int query_profit() { return copy(_accounts["profit"]); } /** * Query the value of the bonus account. */ int query_bonus() { return copy(_accounts["bonus"]); } /** * Query the maximum number of employees. */ int query_maxemp() { return copy(_max_emp); } /** * Query the base pay rate. */ int query_pay() { return copy(_pay_val); } /** * Query the location of this shop. */ string query_place() { return copy(_place); } /** * Query the path to the shop front. */ string query_shop_front() { return copy(_shop_front); } /** * Query the path to the counter. */ string query_counter() { return copy(_counter); } /** * Query the path to the managers' office. */ string query_mgr_office() { return copy(_mgr_office); } /** * Query the path to the storeroom. */ string query_storeroom() { return copy(_storeroom); } /** * Query the full name of the shop. */ string query_shop_name() { return copy(_shop_name); } /** * Set the exit to the managers' office. * @example add_manager_exit( "east", PATH + "tarnach's_man_office" ); * @param dir The direction of the exit. * @param path The full path and filename to the managers' office of the shop. */ protected void add_manager_exit( string dir, string path ) { add_exit( dir, path, "door" ); modify_exit( dir, ({ "function", ({ this_object(), "check_manager" }), "door short", "office door", "door long", "There is a sign on the door which reads: \""+ _proprietor+ "- Private\".\n" }) ); _mgr_office = path; } /* set_mgr_office() */ /** * Set the full name of the shop. * This is used throughout the shop, and passed to the shop front as the * short description for the shop. * @example set_shop_name( "Tarnach Fendertwin's Quality Consumables" ); * @param name The name of the shop. */ protected void set_shop_name( string name ) { _shop_name = name; } /** * Set the very short name of the shop. * This is used in many places including save-file names, setting up * player-titles, and mail headers. It should be no more than around * 4 or 5 chars in length. * @example set_very_short( "TFQC" ); * @param name The short name. */ protected void set_very_short( string name ) { _very_short = name; if ( file_size( _save_dir + name + ".o" ) > 0 ) { unguarded( (: restore_object, _save_dir + name :) ); } if ( file_size( _save_dir + name+ ".employees" ) > 0 ) { _employees = restore_variable( unguarded( (: read_file, _save_dir+ name+ ".employees" :) ) ); } if ( file_size( _save_dir + name+ ".history" ) > 0 ) { _history = restore_variable( unguarded( (: read_file, _save_dir+ name+ ".history" :) ) ); } if ( file_size( _save_dir + name+ ".times" ) > 0 ) { _times = restore_variable( unguarded( (: read_file, _save_dir+ name+ ".times" :) ) ); } TCRE("shiannar", "PSHOP:"+file_name(TO)+": Restoring files."); } /* set_very_short() */ /** * Returns the very short name of the shop. */ string shop_very_short() { return copy(_very_short); } /** * Set the name of the proprietor. * @example set_proprietor( "Tarnach Fendertwin" ); * @param name The name of the proprietor. */ protected void set_proprietor( string name ) { _proprietor = name; } /** * Set the channel used by the shop. * This sets the channel used by the employees' badges, and also the * name of the board. If a board has been set-up for this shop, this * function will also add the board into the room. * @example set_channel( "Tarnach's", 1 ); * @param name The name of the channel. * @param board Non-zero if a board exists for this shop. */ protected void set_channel( string name, int board ) { _channel = lower_case( name ); if ( board ) { _board = clone_object( "/obj/misc/board" ); _board->set_datafile( name ); _board->move( this_object() ); } } /* set_channel() */ /** * @ignore yes * Adjust the value of the profit account. */ private void adjust_profit( string emp, int amount ) { string sign = ""; _accounts["profit"] += amount; if ( _accounts["profit"] < 0 ) { _accounts["profit"] = 0; } if ( amount < 0 ) { sign = "-"; amount = -amount; } shop_log( ACCOUNTS, emp, sprintf( "adjusted the profit account by %s%s", sign, MONEY_HAND->money_value_string( amount, _place ) ) ); save_me(); } /* adjust_profit() */ /** * @ignore yes * Adjust the value of the bonus account. */ private void adjust_bonus( string emp, int amount ) { string sign = ""; _accounts["bonus"] += amount; if ( _accounts["bonus"] < 0 ) { _accounts["bonus"] = 0; } if ( amount < 0 ) { sign = "-"; amount = -amount; } shop_log( ACCOUNTS, emp, sprintf( "adjusted the bonus account by %s%s", sign, MONEY_HAND->money_value_string( amount, _place ) ) ); save_me(); } /* adjust_profit() */ /** * Set the directions to other parts of the shop. * This function is used by the npc shopkeeper to navigate * around the shop, using the exits at the given directions. * These directions should be the standard "north", "southeast" etc. * @example set_directions( "southeast", "southeast", "southeast" ); * @param store The direction to the storeroom. * @param counter The direction to the counter. * @param shop The direction to the shop front. */ protected void set_directions( string store, string counter, string shop ) { _store_dir = store; _counter_dir = counter; _shop_dir = shop; } /* set_directions() */ /** * @ignore yes * Demote a manager or supervisor. */ private void demote( string demoter, string demotee ) { int points; points = _employees[demotee][EMP_POINTS] & CLOCKED_IN; if( _employees[demotee][EMP_POINTS] & MANAGER ) { points += (SUPER_POINTS * 32) + EMPLOYEE + SUPERVISOR; } else { points += EMPLOYEE; } _employees[demotee][EMP_POINTS] = points; save_emps(); AUTO_MAILER->auto_mail( demotee, _proprietor, "Demotion", "", "This is to advise you that you have today been demoted.\n" "This demotion will now stay on your employment record.\n" ); employee_log( demotee, "Demoted by "+ demoter ); shop_log( PERSONNEL, demoter, "demoted "+ capitalize( demotee ) ); } /* demote() */ /** * @ignore yes * View an employee's history. * This method displays a formatted display of the employee's history * with a particular shop, and is viewable by managers of that shop. * @param employee The employee. */ void view_record( string employee ) { string text; int i; if ( !sizeof(_history) || !_history[employee] ) { tell_object( this_player(), "There is no history for that person.\n" ); return; } text = "Employment history of "+ capitalize( employee )+ ":\n\n"; for( i = 0; i < sizeof( _history[employee][0] ); i++ ) { text += ctime(_history[employee][0][i])+ ": "+ _history[employee][1][i]+ "\n"; } tell_object( this_player(), "$P$"+ capitalize(employee)+ "'s history$P$"+ text ); } /* view_record() */ /** * @ignore yes * Saving the contents of the shop's register. */ void save_register( mixed *money ) { _register = money; save_me(); } /* save_register() */ /** * @ignore yes * Loading the contents of the shop's register. */ mixed *restore_register() { return copy(({_register})); } /* restore_register() */ /** * Query the number of storeroom cabinets. * @return The number of cabinets in the storeroom. */ int query_num_cabinets() { return copy(_num_cabinets); } /** * @ignore yes * Managers' office. * Retire from management. */ int do_retire() { string manager; manager = this_player()->query_name(); if ( !(_employees[manager][EMP_POINTS] & MANAGER) ) { return 0; } remove_employee( manager ); _retired += ({ manager }); shop_log( PERSONNEL, manager, "retired from management" ); employee_log( manager, "Retired from management" ); save_me(); add_succeeded_mess( "$N retire$s.\n" ); return 1; } /* do_retire() */ /** * @ignore yes * Managers' office. * Transfer money between accounts. */ int do_transfer( mixed *args ) { int value, best; object money; string tp; if ( args[2] == args[3] || !args[0] ) { tell_object( this_player(), "The point being?\n" ); return 1; } tp = this_player()->query_name(); money = clone_object( MONEY_OBJECT ); money->set_money_array( MONEY_HAND->query_values_in( _place ) ); if ( ( best = money->find_best_fit( args[1] ) ) == -1 ) { tell_object( this_player(), "That currency is not used by " "the shop.\n" ); return 1; } value = args[0] * ( money->query_money_array() )[ best + 1 ]; money->dest_me(); switch( args[2] ) { case "register" : if ( _counter->query_register() < value ) { tell_object( this_player(), "There isn't that much available.\n" ); return 1; } _counter->adjust_register( tp, value ); if ( args[3] == "profit" ) { adjust_profit( tp, value ); } else { adjust_bonus( tp, value ); } break; case "bonus" : if ( _accounts["bonus"] < value ) { tell_object( this_player(), "There isn't that much available.\n" ); return 1; } adjust_bonus( tp, -value ); if ( args[3] == "profit" ) { adjust_profit( tp, value ); } else { money = MONEY_HAND->make_new_amount( value, _place ); _counter->add_money_ob( money ); shop_log( ACCOUNTS, tp, "adjusted the register by "+ MONEY_HAND->money_value_string( value, _place ) ); } break; case "profit" : if ( _accounts["profit"] < value ) { tell_object( this_player(), "There isn't that much available.\n" ); return 1; } adjust_profit( tp, -value ); if ( args[3] == "bonus" ) { adjust_bonus( tp, value ); } else { money = MONEY_HAND->make_new_amount( value, _place ); _counter->add_money_ob( money ); shop_log( ACCOUNTS, tp, "adjusted the register by "+ MONEY_HAND->money_value_string( value, _place ) ); } break; } tell_object( this_player(), "Ok.\n" ); return 1; } /* do_transfer() */ /** * @ignore yes * Make sure cabinet's dested before removing file. */ void rm_cab( string cab_name ) { #ifdef DEBUG debug_printf( "Removing cabinet file %s.\n", _save_dir+ _very_short+ "_" + cab_name+ ".o" ); #endif unguarded( (: rm, _save_dir + _very_short+ "_"+ cab_name+ ".o" :) ); } /* rm_cab() */ /** * @ignore yes * Managers' office. * Renting store cabinets. */ int do_rent() { if ( !( _num_cabinets < MAX_CABINETS ) ) { tell_object( this_player(), "The shop already contains the " "maximum number of cabinets.\n" ); return 1; } if ( CABINET_COST > _accounts["profit"] ) { tell_object( this_player(), "Cabinets currently cost "+ MONEY_HAND->money_value_string( CABINET_COST, _place ) + ". There is not enough money in the profit account.\n" ); return 1; } tell_object( this_player(), "Are you sure you wish to rent a cabinet for "+ MONEY_HAND->money_value_string( CABINET_COST, _place ) + " per month? " ); input_to( "confirm_cabinet", 0, 1 ); return 1; } /* do_rent() */ /** * @ignore yes * Managers' office. * Removing storeroom cabinets. */ int do_remove() { int stock; if ( !( (stock = _num_cabinets ) > MIN_CABINETS ) ) { tell_object( this_player(), "The shop already contains the " "minimum number of cabinets.\n" ); return 1; } if ( sizeof( _storeroom->query_stock(stock) ) ) { tell_object( this_player(), "Cabinet "+ stock+ " still contains stock.\n" ); return 1; } if ( query_cabinet_used(stock) ) { tell_object( this_player(), "Cabinet "+ stock+ " is still assigned to hold stock.\n" ); return 1; } tell_object( this_player(), "Are you sure you wish to remove a cabinet? " ); input_to( "confirm_cabinet", 0, 0 ); return 1; } /* do_remove() */ /** @ignore yes */ void confirm_cabinet( string confirm, int rent ) { string cab_name; confirm = lower_case( confirm ); if ( strlen( confirm ) < 1 || ( confirm[0] != 'y' && confirm[0] != 'n' ) ) { tell_object( this_player(), sprintf("Please enter 'yes' or 'no'.\n" "Are you sure you want to %s a cabinet? ", (rent)?"rent":"remove" ) ); input_to( "confirm_cabinet", 0, rent ); return ; } if ( confirm[0] == 'n' ) { tell_object( this_player(), "Ok.\n" ); return; } if ( rent ) { if ( _storeroom->add_cabinet() ) { _num_cabinets++; adjust_profit( this_player()->query_name(), -CABINET_COST ); shop_log( ACCOUNTS, this_player()->query_name(), "rented cabinet for "+ MONEY_HAND->money_value_string( CABINET_COST, _place ) ); save_me(); } } else { if ( ( cab_name = _storeroom->remove_cabinet() ) != "" ) { _num_cabinets--; shop_log( ACCOUNTS, this_player()->query_name(), "removed cabinet" ); if ( file_size( _save_dir + _very_short+ "_"+ cab_name + ".o" ) > 0 ) { call_out( "rm_cab", 5, cab_name ); } save_me(); } } tell_object( this_player(), "Cabinet "+ (rent)?"rent":"remove" + "ed.\n" ); } /* confirm_cabinet() */ /** * @ignore yes * Managers' office. * Set maximum employees & base pay rate. */ int do_set( mixed *args, string pattern ) { int value, best; object money; switch( pattern ) { case "maximum <number> [employees]" : if ( args[0] < MIN_EMP || args[0] > MAX_EMP ) { tell_object( this_player(), "Must be between "+ MIN_EMP+ " and "+ MAX_EMP+ ".\n" ); return 1; } shop_log( PERSONNEL, this_player()->query_name(), "set maximum employees to "+ args[0] ); _max_emp = args[0]; break; case "pay <number'amount'> <string'type'>" : money = clone_object( MONEY_OBJECT ); money->set_money_array( MONEY_HAND->query_values_in( _place ) ); if ( ( best = money->find_best_fit( args[1] ) ) == -1 ) { tell_object( this_player(), "That currency is not used by the shop.\n" ); return 1; } value = args[0] * ( money->query_money_array() )[best + 1]; money->dest_me(); if ( value < 1 ) { tell_object( this_player(), "You must pay your employees something.\n" ); return 1; } shop_log( PERSONNEL, this_player()->query_name(), "set pay to "+ MONEY_HAND->money_value_string( value, _place ) ); _pay_val = value; add_board_message( "Pay", sprintf( "The base pay rate has today " "been set to:\n\n Managers: %s\n Supervisors: %s\n " "Employees: %s\n", MONEY_HAND->money_value_string(value * 2, _place), MONEY_HAND->money_value_string(to_int(value * 1.5), _place), MONEY_HAND->money_value_string(value, _place) ) ); break; } tell_object( this_player(), "Ok.\n" ); save_me(); return 1; } /* do_set() */ /** * @ignore yes * Managers' office. * Commend employees. Adds 5% of their promotion target. */ int do_commend( string emp ) { string commender; if ( !_employees[emp] ) { tell_object( this_player(), capitalize( emp )+ " is not an active employee!\n" ); return 1; } if ( _employees[emp][EMP_POINTS] & MANAGER ) { tell_object( this_player(), "You can't commend a manager.\n" ); return 1; } if ( _employees[emp][EMP_POINTS] & NPC ) { tell_object( this_player(), "Don't be silly! " "You can't commend $C$"+ emp +".\n" ); return 1; } commender = this_player()->query_cap_name(); AUTO_MAILER->auto_mail( emp, lower_case(commender), "Commendation", "", "This is to advise you that you have today received a " "commendation for outstanding service.\nThis will now stay on " "your employment record.\n" ); employee_log( emp, "Received a commendation from "+ commender ); shop_log( PERSONNEL, commender, "commended "+ capitalize( emp ) ); _employees[emp][EMP_POINTS] += (_employees[emp][EMP_POINTS] & SUPERVISOR)? to_int(MANAGER_POINTS * 0.05 * 32) : to_int(SUPER_POINTS * 0.05 * 32); save_emps(); tell_object( this_player(), "You commend "+ capitalize( emp )+ ".\n" ); return 1; } /* do_commend() */ /** * @ignore yes * Managers' office. * Warn employees. Removes 5% of their promotion target. */ int do_warn( mixed *args ) { string warner; object tp; int points; tp = this_player(); args[0] = lower_case( args[0] ); if ( !_employees[args[0]] ) { tell_object( tp, capitalize( args[0] )+ " is not an active employee!\n" ); return 1; } if ( _employees[args[0]][EMP_POINTS] & MANAGER ) { if ( !creatorp(tp) ) { tell_object( tp, "You don't have the authority to " "warn $C$"+ args[0] +".\n" ); return 1; } } if ( _employees[args[0]][EMP_POINTS] & NPC ) { tell_object( tp, "Don't be silly! You can't warn $C$"+ args[0] +".\n" ); return 1; } warner = tp->query_cap_name(); AUTO_MAILER->auto_mail( args[0], _proprietor, "Official warning", "", "This is to advise you that you have today received a formal " "warning for " + args[1] + ".\nThis warning will now stay on " "your employment record.\n" ); employee_log( args[0], "Received a warning from "+ warner+ " for "+ args[1] ); shop_log( PERSONNEL, warner, "warned "+ capitalize( args[0] ) + " for "+ args[1] ); points = _employees[args[0]][EMP_POINTS] & CLOCKED_IN; if ( _employees[args[0]][EMP_POINTS] & SUPERVISOR ) { _employees[args[0]][EMP_POINTS] -= to_int(MANAGER_POINTS * 0.05 * 32); } else { _employees[args[0]][EMP_POINTS] -= to_int(SUPER_POINTS * 0.05 * 32) + EMPLOYEE; if ( _employees[args[0]][EMP_POINTS] < 1 ) { _employees[args[0]][EMP_POINTS] = EMPLOYEE + points; } } save_emps(); tell_object( tp, "You warn "+ capitalize( args[0] )+ " for "+ args[1]+ ".\n" ); return 1; } /* do_warn() */ /** * @ignore yes * Managers' office. * Demote supervisors or managers. */ int do_demote( string emp ) { object tp; tp = this_player(); emp = lower_case( emp ); if ( !_employees[emp] ) { tell_object( tp, capitalize( emp )+ " is not an active employee!\n" ); return 1; } if ( _employees[emp][EMP_POINTS] & MANAGER ) { if ( !creatorp(tp) ) { tell_object( tp, "You don't have the authority to " "demote $C$"+ emp +".\n" ); return 1; } } if ( !( _employees[emp][EMP_POINTS] & SUPERVISOR ) ) { tell_object( tp, "Don't be silly! You can't demote $C$"+ emp +".\n" ); return 1; } demote( tp->query_cap_name(), emp ); tell_object( tp, "You demote "+ capitalize( emp )+ ".\n" ); return 1; } /* do_demote() */ /** * @ignore yes * Managers' office. * Suspend employee's bonus for x months. */ int do_suspend( mixed *args ) { string suspender; object tp; args[0] = lower_case( args[0] ); tp = this_player(); if ( !_employees[args[0]] ) { tell_object( tp, capitalize( args[0] )+ " is not an active employee!\n" ); return 1; } if ( (_employees[args[0]][EMP_POINTS] & MANAGER) && ( !creatorp(tp) ) ) { tell_object( tp, "You don't have the authority to " "suspend $C$"+ args[0] +"'s bonus.\n" ); return 1; } if ( _employees[args[0]][EMP_POINTS] & NPC ) { tell_object( tp, "Don't be silly! " "You can't suspend $C$"+ args[0] +"'s bonus.\n" ); return 1; } suspender = tp->query_cap_name(); _employees[args[0]][EMP_NOBONUS] = args[1]; save_emps(); AUTO_MAILER->auto_mail( args[0], _proprietor, "Suspended bonus", "", sprintf( "This is to advise you that you have had your bonus " "entitlement suspended for %d month%s.\nThis suspension will " "now stay on your employment record.\n", args[1], (args[1] == 1)?"":"s" ) ); employee_log( args[0], sprintf( "Bonus suspended for %d month%s by %s", args[1], (args[1] == 1)?"":"s", suspender ) ); shop_log( PERSONNEL, suspender, sprintf( "suspended %s's bonus for %d month%s", args[0], args[1], (args[1] == 1)?"":"s" ) ); tell_object( tp, "You suspend "+ capitalize( args[0] )+ "'s bonus for "+ args[1]+ " months.\n" ); return 1; } /* do_suspend() */ /** * @ignore yes * Managers' office. * Place an employee on leave. */ int do_leave( mixed *args ) { object tp; tp = this_player(); args[0] = lower_case( args[0] ); if ( !_employees[args[0]] ) { tell_object( tp, capitalize( args[0] )+ " is not an employee!\n" ); return 1; } if ( args[1] > MAX_LEAVE ) { tell_object( tp, "You cannot place an employee on leave " "for more than "+ MAX_LEAVE+ " days at a time.\n" ); return 1; } _employees[args[0]][EMP_TIME] = time() + ( args[1] * 86400 ); save_emps(); add_succeeded_mess( capitalize( args[0] )+ " is on leave until "+ ctime( time() + ( args[1] * 86400 ) )+ ".\n" ); shop_log( PERSONNEL, tp->query_name(), "placed "+ capitalize( args[0] )+ " on leave for " + args[1]+ " days" ); tell_object( tp, "You place "+ capitalize( args[0] )+ " on leave for " + args[1]+ " days" ); return 1; } /* do_leave() */ /** * @ignore yes * Managers' office. * Ban a person from the shop. */ int do_ban( mixed *args ) { if ( !"/secure/login"->test_user( lower_case( args[0] ) ) ) { tell_object( this_player(), args[0]+ " is not a player.\n" ); return 1; } add_baddie( args[0], args[1], this_player()->query_name() ); tell_object( this_player(), "You ban "+ capitalize( args[0] )+ " for "+ args[1]+ ".\n" ); return 1; } /* do_ban() */ /** * @ignore yes * Managers' office. * Remove the ban on a person. */ int do_unban( string person ) { if ( !query_baddie( person ) ) { tell_object( this_player(), person+ " is not currently banned.\n" ); return 1; } remove_baddie( person ); shop_log( GENERAL, this_player()->query_name(), "removed the ban on "+ capitalize( person ) ); tell_object( this_player(), "You remove the ban on "+ capitalize( person )+ ".\n" ); return 1; } /* do_unban() */ /** * @ignore yes * Managers' office. * Fire an employee. */ int do_fire( mixed args ) { args[0] = lower_case( args[0] ); if( !_employees[args[0]] ) { tell_object( TP, capitalize( args[0] )+" doesn't work at the shop!\n"); return 1; } if( ( member_array( args[0], _retired ) != -1 || query_manager( args[0] ) ) && !creatorp(TP) ) { tell_object( TP, "You don't have the authority to fire $C$"+ args[0] +".\n"); return 1; } if( _employees[args[0]][EMP_POINTS] & NPC ) { tell_object( TP, "Don't be silly! You can't fire $C$"+ args[0] +".\n"); return 1; } fire_them( this_player()->query_name(), args[0], args[1] ); tell_object( this_player(), "You fire "+ capitalize( args[0] )+ " for "+ args[1]+ ".\n" ); return 1; } /* do_fire() */ /** * @ignore yes * Managers' office. * Vote on an applicant. */ int do_vote( mixed *args, string pattern ) { string tp; int query_app; tp = this_player()->query_name(); if ( pattern == "<word'applicant'> {y|n|a}" ) { args[0] = lower_case( args[0] ); if ( !( query_app = query_applicant(args[0]) ) ) { tell_object( this_player(), capitalize( args[0] )+ " hasn't applied!\n" ); return 1; } if ( query_app != APPLIED ) { tell_object( this_player(), capitalize( args[0] )+ " has already been hired!\n" ); return 1; } if ( check_vote( args[0], tp ) ) { tell_object( this_player(), "You have already voted for this person!\n" ); return 1; } if ( args[1] == "y" ) { add_vote( args[0], VFOR ); tell_object( this_player(), "You vote for " + capitalize( args[0] ) + " to work for the shop.\n" ); } else if ( args[1] == "a" ) { add_vote( args[0], VABSTAIN ); tell_object( this_player(), "You abstain on " + capitalize( args[0] ) + ".\n" ); } else { add_vote( args[0], VAGAINST ); tell_object( this_player(), "You vote against " + capitalize( args[0] ) + " working for the shop.\n" ); } } else { do_policy_vote( tp, args[0], args[1] ); } return 1; } /* do_vote() */ /** * Someone has entered the room. * This function will automatically fire an employee if they have * teleported to this room. To avoid this happening, or to modify, * mask this function. */ void event_enter( object ob, string message, object from ) { string room; if ( !from || creatorp(ob) || from == find_object( "/room/void") || !ob->query_player() ) { return; } room = file_name( from ); if ( room == _counter || room == _shop_front || room == _storeroom || room == _mgr_office || from == this_object() ) { return; } else { #ifdef DEBUG debug_printf( "%s arrived from %O.\n", ob->query_name(), from ); #endif fire_them( _proprietor, ob->query_name(), "for teleporting into the shop" ); } } /* event_enter() */ /** * Someone has died. * This function will automatically fire an employee if they have * killed someone whilst on duty. It will also make a note of anyone * who has killed an on-duty employee (including the npc shopkeeper). To * avoid this happening, or to modify, mask this function. */ void event_death( object killed, object *others, object killer, string rmess, string kmess ) { object baddie; /* Only look for players & the shopkeeper * We don't want to fire/ban people for killing a cabbage... */ if( !killer || ( !interactive(killed) && !( query_employee( killed->query_name() ) & NPC ) ) ) { return; } if( ( query_employee( killed->query_name() ) & NPC ) || ( query_employee( killed->query_name() ) ) ) { add_baddie( killer->query_name(), "the vicious assault on "+ killed->query_name(), _proprietor ); foreach( baddie in others ) { if( interactive(baddie) ) { add_baddie( baddie->query_name(), "the vicious assault on "+ killed->query_name(), _proprietor ); } } return; } if( query_employee( killer->query_name() ) & CLOCKED_IN ) { add_baddie( killer->query_name(), "the vicious assault on "+ killed->query_name(), _proprietor ); } if( sizeof( others ) ) { foreach( baddie in others ) { if( interactive(baddie) ) return; if( query_employee( baddie->query_name() )& CLOCKED_IN ) { add_baddie( baddie->query_name(), "the vicious assault " "on "+ killed->query_name(), _proprietor ); } } } } /* event_death() */ /** * @ignore yes * Shop front. * Cancel application. */ int do_cancel() { object tp; tp = this_player(); remove_applicant( tp->query_name() ); employee_log( tp->query_name(), "Cancelled application" ); tell_object( tp, "You cancel your application.\n" ); return 1; } /* do_cancel() */ /** * @ignore yes * Shop front. * Confirm employment. */ int do_confirm() { if(!query_applicant(TP->query_name())) return notify_fail("You are not currently an applicant here.\n"); confirm_employment( TP->query_name() ); employee_log( TP->query_name(), "Confirmed employment" ); tell_object( TP, "You confirm your employment.\n" ); return 1; } /* do_confirm() */ /** * @ignore yes * Keep track of net change in register. */ void adjust_takings( int amt ) { _net_takings += amt; save_me(); } /* adjust_takings() */ /** * @ignore yes * Keep track of items bought. */ void adjust_bought( string item, int amt ) { _list[item][CHART_BOUGHT] += amt; save_list(); } /* adjust_bought() */ /** * @ignore yes * Keep track of items sold. */ void adjust_sold( string item, int amt ) { _list[item][CHART_SOLD] += amt; save_list(); } /* adjust_sold() */ /** * @ignore yes * Add standard stuff */ void set_long( string long_desc ) { long_desc += "Employees may \"clock\" in and out of work here " "and examine the items chart. There is also a policy notice " "located next to the chart.\n"; ::set_long( long_desc ); } /* set_long() */ /** * @ignore yes * Update policies in force. */ private void add_policy( string policy ) { if ( !_policies[policy] ) { _policies += ([ policy:"" ]); _policies[policy] = _new_policies[policy][2]; AUTO_MAILER->auto_mail( _new_policies[policy][1], _proprietor, "Policy proposition - "+ policy, "", "Your proposition has been accepted by majority vote and is " "now shop policy.\n" ); shop_log( GENERAL, "Shop", "accepted the "+ policy + " policy.\n" ); } else { map_delete( _policies, policy ); AUTO_MAILER->auto_mail( _new_policies[policy][1], _proprietor, "Policy proposition - "+ policy, "", "Your proposition to remove the above policy has been accepted " "by majority vote.\n" ); shop_log( GENERAL, "Shop", "removed the "+ policy + " policy.\n" ); } map_delete( _new_policies, policy ); save_me(); } /* add_policy() */ /** * @ignore yes * Policy update has been unsuccessful. */ private void remove_policy( string policy ) { AUTO_MAILER->auto_mail( _new_policies[policy][1], "Tarnach Fendertwin", "Policy proposition - "+ policy, "", "Your proposition has been rejected by majority vote.\n" ); shop_log( GENERAL, "Shop", "rejected the "+ policy + " policy.\n" ); map_delete( _new_policies, policy ); save_me(); } /* remove_policy() */ /** * @ignore yes * Add a vote */ private int do_policy_vote( string mgr, string policy, string decision ) { int managers; if ( !m_sizeof(_new_policies) || !_new_policies[policy] ) { tell_object( this_player(), "There is no such policy pending!\n" ); return 1; } if ( member_array( mgr, _new_policies[policy][3] ) != -1 || member_array( mgr, _new_policies[policy][4] ) != -1 ) { tell_object( this_player(), "You have already voted on this policy!\n" ); return 1; } if ( decision == "y" ) { _new_policies[policy][3] += ({ mgr }); tell_object( this_player(), "You vote for the " + policy+ " policy.\n" ); } else { _new_policies[policy][4] += ({ mgr }); tell_object( this_player(), "You vote against the " + policy+ " policy.\n" ); } shop_log( GENERAL, mgr, "voted on the "+ policy + " policy.\n" ); save_me(); managers = sizeof( get_managers() ) + sizeof( get_retired() ); if ( sizeof( _new_policies[policy][3] ) > managers / 2 ) { add_policy( policy ); } else if ( sizeof( _new_policies[policy][4] ) >= managers / 2 ) { remove_policy( policy ); } return 1; } /* do_policy_vote() */ /** * Determine if a shop policy exists. * @param policy The name of the policy to query. * @return 2 if it is already policy, 1 if it is being voted upon, else 0 */ int query_policy( string policy ) { if ( m_sizeof( _policies ) && _policies[policy] ) return 2; if ( m_sizeof( _new_policies ) && _new_policies[policy] ) return 1; return 0; } /* query_policy() */ /** * @ignore yes * Add a new policy suggestion. */ void add_policy_suggest( string name, string text, string mgr ) { if ( !sizeof( _new_policies ) ) { _new_policies = ([ name:({0,"","",({}),({}) }) ]); } else { _new_policies += ([ name:({0,"","",({}),({}) }) ]); } if ( sizeof( _policies ) && _policies[name] ) { text = _policies[name][2]; } _new_policies[name][0] = time(); _new_policies[name][1] = mgr; _new_policies[name][2] = text; do_policy_vote( mgr, name, "y" ); save_me(); } /* add_policy_suggest() */ /** * Query the policy suggestions. * @return A mapping of the suggested shop policies. */ mapping get_new_policies() { return copy( _new_policies ); } /** * Query the policies. * @return A mapping of the shop policies. */ mapping get_policies() { return copy( _policies ); } /** * Set the stock's main policy. * @param desc The main stock description, eg. "magical spell components" */ void set_stock_policy( string desc ) { _stock_policy = desc; } /** * @ignore yes * Query the shop's main policy - used for the notice. */ string get_stock_policy() { return copy( _stock_policy ); } /** * @ignore yes * Called when someone is refreshed or deleted * Will delete the employee in case of deletion or full refresh. In case * of partial refresh, will reset employee to new employee status. */ void refresh_function( mixed employee, int flag ) { string emp_name; switch ( flag ) { case PLAYER_DELETED : emp_name = employee; break; case TOTAL_REFRESH : case PARTIAL_REFRESH : emp_name = employee->query_name(); break; } if ( query_applicant( emp_name ) ) { remove_employee( emp_name ); switch ( flag ) { case PLAYER_DELETED : shop_log( PERSONNEL, capitalize(emp_name), "deleted character" ); employee_log( emp_name, "deleted character" ); break; case TOTAL_REFRESH : case PARTIAL_REFRESH : tell_object( employee, "Your application at "+ _shop_name+ " has been removed.\n" ); shop_log( PERSONNEL, capitalize(emp_name), "refreshed character" ); employee_log( emp_name, "refreshed character" ); break; } } if ( member_array( emp_name, _retired ) != -1 ) { remove_employee( emp_name ); switch ( flag ) { case PLAYER_DELETED : shop_log( PERSONNEL, capitalize(emp_name), "deleted character" ); employee_log( emp_name, "deleted character" ); break; case TOTAL_REFRESH : case PARTIAL_REFRESH : tell_object( employee, "Your employment at "+ _shop_name+ " has been terminated.\n" ); shop_log( PERSONNEL, capitalize(emp_name), "refreshed character" ); employee_log( emp_name, "refreshed character" ); break; } } if ( !_employees[emp_name] ) { return; } switch ( flag ) { case PLAYER_DELETED : shop_log( PERSONNEL, capitalize(emp_name), "deleted character" ); employee_log( emp_name, "deleted character" ); remove_employee( emp_name ); break; case TOTAL_REFRESH : tell_object( employee, "Your employment at "+ _shop_name+ " has been terminated.\n" ); shop_log( PERSONNEL, capitalize(emp_name), "refreshed totally" ); employee_log( emp_name, "refreshed totally" ); remove_employee( emp_name ); break; case PARTIAL_REFRESH : tell_object( employee, "Your employment level at "+ _shop_name+ " has been reset.\n" ); shop_log( PERSONNEL, capitalize(emp_name), "refreshed character" ); employee_log( emp_name, "refreshed character" ); _employees[emp_name] = ({1,0,0,0,0,1,0,}); set_emp_time( emp_name ); _got_bonus += ({ emp_name }); save_me(); break; } } /* refresh_function() */ /** * Overrides the default save dir. * @param dir the new dir */ void set_save_dir(string dir) { _save_dir = dir; } /* set_save_dir() */