/** * The standard inheritable object for player-run shop offices. * * <p><b>Description</b></p> * <p>This office is the nerve centre of the shop. Most of the data * structures are defined and maintained from here, and virtually all * of the managerial functions are forwarded here. It is this object * that passes the common variables across to other files as they call * for them. This minimises the complexity of setting up each object - as * long as they point to this object, and this object is correctly set up, * the objects will know which shop they belong to, who the employees are, * who the proprietor is etc. * </p> * <p>Before the shop will function correctly, it must be registered with * the handler - see set_very_short() * </p> * <p>Most of the functions defined in the office are not needed by * creators. Those that are needed are shown in the examples. Other * functions are not necessary, but may be useful. * </p> * <p>The source to this object is split into 15 different files. The * main file, office.c includes the other files under the office_code * directory. This is intended to ease code maintenance as the total * size of the office code (as of December 2000) is around 165k. * </p> * <p>In general, the functions contained in office.c are those intended * to be used directly by creators. The other functions are called * internally by the shop. * </p> * <p>The data itself is also saved into several files. The data is * arranged into these files to minimise the amount of disk activity * when changes are made. For example, employee data changes every time an * employee does something. The list of applicants, however, changes only * when an application status changes. They are, therefore, saved into * seperate files. Also, each save call is subject to a callout, and will * limit the amount of disk writing actually done. The only data not stored * by this file is the stock data, which is saved by each cabinet * individually. All data and logs are saved into several files within * a directory "/d/(domain)/save/player_shops/(shop_very_short)/". * In addition, some data are cleared if it hasn't been used for a while. * This will typically save up to 100k of memory for a store the size of * Tarnach's. * </p> * <p>The shop supports notice boards, posting all hirings, promotions, * bonus amounts in addition to any posts requested in the inheriting * object. In the absence of a board, all posts are mailed to each employee * and each employee will have access to an additional "memo" command in * the office. * </p> * <p>Applications are handled automatically, with managers voting whether * to accept or reject an application. On gaining positive votes from at * least 50% of the managers, an applicant is hired. If they receive more * than 50% negative votes, they are rejected. If there are insufficient * votes to settle the decision, an applicant will be hired if more managers * have voted for than against. Policy suggestions are handled in a similar * way. * </p> * <p>The shop's two main administration routines are run on a regular * basis. The first of these is run every day and is responsible for * checking that employees are still valid players (not deleted chars or * 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. * </p> * <p>The second is run every Discworld month. This review involves paying * employees directly into their nominated bank account, and awarding * bonuses based on the current value of the bonus fund. * </p> * * @example * #include "path.h" * * inherit "/std/shops/player_shop/office"; * * void setup() * { * set_light(60); * set_place("Lancre"); * set_proprietor("Tarnach Fendertwin"); * set_shop_name("Tarnach Fendertwin's Quality Consumables (Creel Springs)"); * set_very_short("TFQC-CS"); * set_channel("tarnachcs", 0); * set_shopkeeper(PATH + "shopkeeper"); * set_stock_policy("magical spell components"); * * set_shop_front(PATH+ "front"); * set_counter(PATH+ "counter"); * set_storeroom(PATH+ "storeroom"); * set_directions("east", "east", "east"); * * set_short("office of Tarnach's shop"); * set_long("This room is the office of the Creel Springs branch of " * "Tarnach Fendertwin's Quality Consumables. There is a door to the " * "managers' office in the south wall.\n"); * add_sign("The sign is a small piece of paper stuck to the wall.\n", * "This seems to be a handwritten note from Tarnach himself. " * "The handwriting, not to mention the spelling and the grammar, " * "is appalling, so it's difficult to make out much. All you " * "can understand is something about what the \"office\" is for.", * "sign", "sign" )->add_property( "there", "on one wall"); * add_exit("east", PATH+ "counter", "door"); * add_manager_exit("south", PATH + "mgr_office"); * } * * @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 */ inherit "/std/room/basic_room"; #include <player_shop.h> #include <mail.h> #include <board.h> #include <money.h> #include <move_failures.h> #include <refresh.h> #include "office.h" /* * Globals */ private nosave string _proprietor = UNSET_STR, _shop_name = UNSET_STR, _very_short = UNSET_STR, _place = UNSET_STR, _channel = UNSET_STR, _storeroom = "", _shop_front = "", _counter = "", _mgr_office = "", _store_dir = "", _counter_dir = "", _shop_dir = "", _shopkeeper = "", _stock_policy = "", _cab_name = "", _review_month = "", _savedir = "", _creator = CREATOR; private nosave object _chart = 0, _board = 0, _notice = 0; private nosave mapping _history = 0, _applicants = 0, _policies = 0, _new_policies = 0, _list = ([]), _times = ([]), _employees = ([]); /** Used to keep track of (and remove) call_outs */ private nosave int _call_save = 0, _call_hist = 0, _call_hist_clear = 0, _call_times = 0, _call_hire_list = 0, _call_mail_hirees = 0, _call_summon = 0, _call_emps = 0, _call_review = 0, _call_apps = 0, _call_apps_clear = 0, _call_pols = 0, _call_pols_clear = 0, _call_newpols = 0, _call_newpols_clear = 0; private string *_retired = ({}), *_got_bonus = ({}), _last_month = "last month", _eom = CREATOR; private mapping _baddies = ([]), _declined = ([]), _accounts = (["profit":0,"bonus":0]); 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() { do_setup++; ::create(); do_setup--; if (!do_setup) { this_object()->setup(); this_object()->reset(); } seteuid("Room"); add_property("determinate", ""); add_help_file("player_shop_office"); } /* create() */ /** * @ignore yes * Include rest of source - this is split up to keep filesizes within a * practical size limit. This should make maintaining the source less of a * nightmare. Only public functions will be found in this file. */ #include "office_code/admin.c" // misc admin functions #include "office_code/applications.c" // application-related functions #include "office_code/baddies.c" // banning-related functions #include "office_code/cabinets.c" // cabinet-related functions #include "office_code/emp_cmd.c" // employee commands #include "office_code/lists.c" // listing functions #include "office_code/logging.c" // log-related functions #include "office_code/masked.c" // masked functions #include "office_code/memo.c" // memo functions #include "office_code/personnel.c" // personnel-related functions #include "office_code/policies.c" // policy-related functions #include "office_code/review.c" // daily & monthly review functions #include "office_code/save.c" // saving-related functions #include "office_code/stock.c" // stock-related functions /****************************************************************************** ****************************************************************************** * START OF PUBLIC FUNCTIONS * ****************************************************************************** ******************************************************************************/ /** * Set the exit to the managers' office. * This method also modifies the exit to allow only managers to enter 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() */ /** * Query 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() { load_applicants(); clear_applicants(); return copy(_applicants + ([])); } /** * 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 as above. */ mapping get_baddies() { return copy(_baddies + ([])); } /** * Query the list of employees. * This method returns the list of employees, sorted alphabetically. * @see get_supervisors() * @see get_managers() * @see get_retired() * @return the sorted array of employees. */ string *get_employees() { string *employees = m_indices(_employees); /* Don't include people with the supervisor bit set */ foreach (string word in employees) if (_employees[word][EMP_POINTS] & SUPERVISOR) employees -= ({word}); return copy(sort_array(employees, 1)); } /* get_employees() */ /** * Query the list of managers. * This method returns the list of managers, sorted alphabetically. * @see get_employees() * @see get_supervisors() * @see get_retired() * @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 policy suggestions. * These are the policies that have been proposed but not yet implemented. * @see get_policies() * @see query_policy() * @return a mapping of the suggested shop policies. */ mapping get_new_policies() { load_new_policies(); clear_new_policies(); return copy(_new_policies); } /** * Query the policies. * These are the policies currently in effect. * @see get_new_policies() * @see query_policy() * @return a mapping of the shop policies. */ mapping get_policies(int type) { load_policies(); clear_policies(); if (type) return copy(filter(_policies, (: _policies[$1][1] :))); else return copy(filter(_policies, (: !_policies[$1][1] :))); } /* get_policies() */ /** * Query the list of retired managers. * This method returns the list of retired managers, sorted alphabetically. * @see get_employees() * @see get_supervisors() * @see get_managers() * @return the sorted array of retired managers. */ string *get_retired() { return copy(sort_array(_retired, 1)); } /** * Query the list of supervisors. * This method returns the list of supervisors, sorted alphabetically. * @see get_employees() * @see get_managers() * @see get_retired() * @return the sorted array of supervisors. */ string *get_supervisors() { string *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 (string word in supervisors) if (_employees[word][EMP_POINTS] & MANAGER) supervisors -= ({word}); return copy(sort_array(supervisors, 1)); } /* get_supervisors() */ /** * 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; foreach (string word in m_indices(_employees)) if (_employees[word][EMP_POINTS] & CLOCKED_IN) { if (_employees[word][EMP_POINTS] & NPC) continue; if (!find_player(word) || !interactive(find_player(word))) { reset_employee(word, CLOCKED_IN); shop_log(GENERAL, word, "was clocked out", UNPAID); } else any++; } return any; } /* num_employees_in() */ /** * 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) { load_applicants(); clear_applicants(); 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() */ /** * Query the value of the bonus account. * This is the current value of the bonus account. Will need converting to * local currency if it is to be displayed. * @see query_profit() * @return the value of the bonus account. */ int query_bonus() { return copy(_accounts["bonus"]); } /** * Query the channel used by the shop. * @see set_channel() * @return the channel in use by this shop */ string query_channel() { return copy(_channel); } /** * Query the path to the shop counter. * @see set_counter() * @return the path to the counter. */ string query_counter() { return copy(_counter); } /** * Query the maintainer of this shop's files. * @see set_creator() * @return the person responsible for this shop. */ void query_creator(string creator) { return copy(_creator); } /** * 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() */ /** * Determine if this person is an employee of the shop. * @see query_supervisor() * @see query_manager() * @see query_retired() * @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() */ /** * 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 + ([])); } /** * Query the list of items sold by the shop. * Generates an array of the keys to the list mapping. * @see query_list_mapping() * @see query_list_string() * @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. * @see query_list_array() * @see query_list_string() * @return the list of items in mapping form. */ mapping query_list_mapping() { return copy(_list) + ([]); } /** * Query the list of items sold by the shop. * Generates a list of all items bought & sold by this shop, and outputs a * multiple short string for use in displays. * @see query_list_array() * @see query_list_mapping() * @return the list of items. */ string query_list_string() { if (!m_sizeof(_list)) return "absolutely nothing at the moment"; return query_multiple_short(m_indices(_list)); } /* query_list_string() */ /** * Determine if this person is a manager of this shop. * @see query_employee() * @see query_supervisor() * @see query_retired() * @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() */ /** * Query the maximum number of employees. * @return the maximum number of employees allowed at this shop. */ int query_maxemp() { return copy(_max_emp); } /** * Query the path to the managers' office. * @return the path to the managers' office. */ string query_mgr_office() { return copy(_mgr_office); } /** * Query the number of storeroom cabinets. * @return the number of cabinets in the storeroom. */ int query_num_cabinets() { return copy(_num_cabinets); } /** * Query the base pay rate. * This is the base pay rate set by the managers; the amount each employee * will receive for a single transaction. * @return the value of the base pay rate */ int query_pay() { return copy(_pay_val); } /** * Query the location of this shop. * @see set_place() * @return the location of the shop */ string query_place() { return copy(_place); } /** * Determine if a shop policy exists. * @see get_policies() * @see get_new_policies() * @param policy The name of the policy to query. * @return 2 if it is already policy, 1 if it is a proposal, else 0 */ int query_policy(string policy) { load_policies(); clear_policies(); if (m_sizeof(_policies) && _policies[policy]) return 2; load_new_policies(); clear_new_policies(); if (m_sizeof(_new_policies) && _new_policies[policy]) return 1; return 0; } /* query_policy() */ /** * Query the value of the profit account. * This is the current value of the profit account. Will need converting to * local currency if it is to be displayed. * @see query_bonus() * @return the value of the profit account */ int query_profit() { return copy(_accounts["profit"]); } /** * Query the name of the proprietor. * @see set_proprietor() * @return the name of the proprietor */ string query_proprietor() { return copy(_proprietor); } /** * Determine if this person is a retired manager of this shop. * @see query_employee() * @see query_supervisor() * @see query_manager() * @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() */ /** @ignore yes */ string query_savedir() { return copy(_savedir); } /** * Query the path to the shop front. * @see set_shop_front() * @return the path to the shop front */ string query_shop_front() { return copy(_shop_front); } /** * Query the full name of the shop. * @see set_shop_name() * @return the full name of the shop */ string query_shop_name() { return copy(_shop_name); } /** * Query the shopkeeper name. * @see set_shopkeeper() * @return the name of the shopkeeper */ string query_shopkeeper() { return copy(_shopkeeper); } /** * Query number of items in stock. * Determines the number of a specific item currently held by this shop. * @param items the item to query * @return the number of 'items' in stock */ int query_stock(string items) { return _storeroom->query_num_items(items, 0); } /** * Query the path to the storeroom. * @see set_storeroom() * @return the path to the storeroom */ string query_storeroom() { return copy(_storeroom); } /** * Determine if this person is a supervisor of this shop. * @see query_employee() * @see query_manager() * @see query_retired() * @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() */ /** * Save the employee data file. * This method uses a call_out to help minimise the amount of disk activity * during normal operations of the shop. The very short name of the shop * must have been set previously. * @see save_me() * @see set_very_short() */ protected void save_emps() { if (_very_short == UNSET_STR) return; remove_call_out(_call_emps); _call_emps = call_out((: do_save_emps() :), SAVE_DELAY); } /* save_emps() */ /** * Save the shop data file. * This method uses a call_out to help minimise the amount of disk activity * during normal operations of the shop. The very short name of the shop * must have been set previously. * @see save_emps() * @see set_very_short() */ protected void save_me() { if (_very_short == UNSET_STR) return; remove_call_out(_call_save); _call_save = call_out((: do_save() :), SAVE_DELAY); } /* save_me() */ /** * 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( "tarnachcs", 0 ); * @see query_channel() * @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() */ /** * Set the path to the counter. * This is the full path and filename of the counter object to be used by this * shop. * @example set_counter( PATH + "counter" ); * @see query_counter() * @param path The full path and filename to the shop's counter. */ protected void set_counter(string path) { _counter = path; } /** * 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> * @example set_creator( "ringo" ); * @see query_creator() * @param creator The person responsible for this shop. */ protected void set_creator(string creator) { _creator = creator; } /** * 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", "south", "up" 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() */ /** * 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 /obj/handlers/money_handler * @example set_place( "Lancre" ); * @see query_location() * @param place the location of this shop. */ protected void set_place(string place) { _place = place; } /** * Set the name of the proprietor. * A fictional name; the owner of this establishment. All administration * board posts & mudmails will be sent by this name. * @example set_proprietor( "Tarnach Fendertwin" ); * @see query_proprietor() * @param name the name of the proprietor. */ protected void set_proprietor(string name) { _proprietor = name; } /** * Set the path to the customer area. * @example set_shop_front( PATH + "shopfront" ); * @see query_shop_front() * @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 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 (Creel Springs)" ); * @see query_shop_name() * @param name the name of the shop. */ protected void set_shop_name(string name) { _shop_name = name; } /** * Set the npc shopkeeper object. * @example set_shopkeeper( PATH + "shopkeeper" ); * @see /std/shops/player_shop/shopkeeper.c * @param path the full path to the shopkeeper. */ protected void set_shopkeeper(string path) { _shopkeeper = path; path=_shopkeeper->query_name(); if (!_employees || !_employees[path]) { _employees += ([path:EMP_MAP]); _employees[path][EMP_POINTS] = EMPLOYEE + NPC; } } /** * Set the path to the storeroom. * @example set_storeroom( PATH + "storeroom" ); * @see query_storeroom() * @param path the full path and filename to the storeroom of the shop. */ protected void set_storeroom(string path) { _storeroom = path; } /** * Set the stock's main policy. * This is a general description of the items that this store deals in. * @example set_stock_policy( "magical spell components" ); * @param desc the main stock description. */ protected void set_stock_policy(string desc) { _stock_policy = desc; } /** * 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. This function also restores all saved data. * You must register this name by calling add_shop() in the handler. * @example set_very_short( "TFQC-CS" ); * @see shop_very_short() * @see /obj/handlers/player_shop * @param name the very short name. */ protected void set_very_short(string name) { if (PLAYER_SHOP->query_shop(name) != file_name(this_object())) return; _very_short = name; _savedir = sprintf("/save/player_housing/%s/player_shops/%s/", lower_case(geteuid(this_object())), name); if (file_size(_savedir+ "shop_data.o") > 0) unguarded((: restore_object, _savedir+ "shop_data" :)); if (file_size(_savedir+ "employees") > 0) _employees = restore_variable(unguarded((: read_file, _savedir+ "employees" :))); if (file_size(_savedir+ "times") > 0) _times = restore_variable(unguarded((: read_file, _savedir+ "times" :))); if (file_size(_savedir+ "list") > 0) _list = restore_variable(unguarded((: read_file, _savedir+ "list" :))); } /* set_very_short() */ /** * Query the very short name of the shop. * @see set_very_short() * @return the very short name of the shop. */ string shop_very_short() { return copy(_very_short); }