/* * File: /std/user.c * The user body is from the TMI-2 lib. Part of the bodies project and * implemented by Watcher@TMI-2 and Mobydick@TMI-2. The code contained * in this object is heavily based on code found in the original user.c * * The relevant code headers follow: * Original Authors: Sulam and Buddha @ The Mud Institute * Many other people have added to this as well. * This file has a long revision history dating back to the hideous * mudlib.n and is now probably not at all the same. * This file is part of the TMI distribution mudlib. * Please keep this header with any code within. * * 94-11-09 Leto added Beek's error handler code * 94-11-28 Leto added Beek's move_or code * 94-11-30 Leto added receive_snoop * 95-01-11 Blue modified receive_snoop slightly. */ #define DEBUG #include <config.h> #include <login.h> #include <commands.h> #include <daemons.h> #include <net/daemons.h> #include <mudlib.h> #include <flock.h> #include <move.h> #include <money.h> #include <priv.h> #include <driver/origin.h> #include <uid.h> #include <switches.h> #include <messages.h> /* * Some files are inherited by including user.h. */ #include <user.h> #include <user2.h> #include <logs.h> #include <body.h> #include <login_macros.h> /* * This is only needed for consistency_check. */ #include <channels.h> #include <domains.h> /* * For receive_snoop */ #include <ansi.h> inherit LIVING ; /* * This is probably only a local hack. */ #ifndef CAP_NAME_MASTER_ONLY # undef CAP_NAME # undef LINK_CAP_NAME # define CAP_NAME capitalize(query("name")) # define LINK_CAP_NAME capitalize(link_data("name")) #endif #define HALL "/d/Fooland/hall" /* * prototypes for local functions */ static varargs void complete_setup (string str); static void die(); static int create_ghost(); int coins_carried(); void init_setup(); void destroy_autoload_obj(); varargs void execute_attack(int hit_mod, int dam_mod); void call_user_dump(string type); /* * Setup basic and command catch systems */ void basic_commands() { add_action("quit", "quit"); } static void init_commands() { string path; add_action( "cmd_hook", "", 1 ); path = query("PATH"); if (!path) { if ( wizardp(this_object()) ) path = NEW_WIZ_PATH; else path = USER_CMDS; set("PATH", path, MASTER_ONLY); } } /* * Setup standard user command hook system. This system interfaces * with the cmd bin system, the environment's exits, and feeling entries. */ nomask static int cmd_hook(string cmd) { string file; string verb; int foo; mapping before, after; verb = query_verb(); if (environment() && environment()->valid_exit(verb)) { verb = "go"; cmd = query_verb(); } file = (string)CMD_D->find_cmd(verb, explode(query("PATH"), ":")); if (file && file != "") { #ifdef PROFILING before = rusage(); #endif foo = (int)call_other(file, "cmd_" + verb, cmd); #ifdef PROFILING after = rusage(); "/adm/daemons/profile"->log_cmd(verb,before,after); #endif return foo; } if (environment() && environment()->query("quiet")) return 0; #ifdef PROFILING before = rusage(); #endif foo = (int)EMOTE_D->parse(verb, cmd); if (foo) { #ifdef PROFILING after = rusage(); "/adm/daemons/profile"->log_cmd(verb,before,after); #endif return foo; } #ifdef PROFILING before = rusage(); #endif #ifndef INTERMUD if (verb == "gwiz" || verb == "interwiz") { printf("Sorry, %s does not support intermud.\n",capitalize(mud_name())); return 1; } #endif /* INTERMUD */ foo = (int) CHANNELS_D -> parse_channel( verb, cmd ); #ifdef PROFILING if ( foo ) { after = rsuage(); "/adm/daemons/profile" -> log_cmd( verb, before, after ); } #endif return foo; } /* * This function protects the object from being shadowed for * certain secure functions. */ nomask int query_prevent_shadow(object ob) { mixed *protect; int loop; protect = ({ "catch_tell", "receive_message", "do_alias", "do_xverb", "do_substitution", "process_input", "tsh", "do_nickname", "receive_snoop" }); for (loop = sizeof(protect); loop--; ) if (function_exists(protect[loop], ob)) return 1; return 0; } /* * Move the player to another room. Give the appropriate * message to on-lookers. * The optional message describes the message to give when the player * leaves. */ varargs int move_player(mixed dest, string message, string dir) { object prev; int res; prev = environment( this_object() ); if ( res = move(dest) != MOVE_OK ) { message(MSG_INFO, "You remain where you are.\n", this_player()); return res; } if (environment() && wizardp(this_object()) && query("display_path")) message(MSG_LOOK, sprintf("[%s]\n", file_name(environment())), this_object()); if (message == "SNEAK") { set_temp("force_to_look", 1); command("look"); set_temp("force_to_look", 0); return 0; } if (!query("invisible")) { if (message == 0 || message == "") { if (dir && dir != "") { message(MSG_MOVEMENT, (string)query_mout(dir) + "\n", prev, this_object()); message(MSG_MOVEMENT, (string)query_min() + "\n", environment(), this_object()); } else { message(MSG_MOVEMENT, (string)query_mmout() + "\n", prev, this_object()); message(MSG_MOVEMENT, (string)query_mmin() + "\n", environment(), this_object()); } } else { message(MSG_MOVEMENT, message + "\n", prev, this_object()); message(MSG_MOVEMENT, (string)query_min() + "\n", environment(), this_object()); } } set_temp ("force_to_look", 1); command("look"); set_temp("force_to_look", 0); /* * Follow/track mudlib support */ if (!query("no_follow") && environment() != prev && prev) all_inventory(prev)->follow_other(this_object(), environment(), dir); return 0; } private int clean_up_attackers() { mixed *attackers_tmp; int i; attackers_tmp = ({ }); for (i = sizeof(attackers); i--; ) { /* * If he's dead, then forget about him entirely. */ if (attackers[i] == 0 || !living(attackers[i])) continue; /* * If he's not here, then forget about him. */ if (environment(attackers[i]) != environment(this_player())) continue; /* * If he's a ghost, we've done enough to him already :) */ if ((int)attackers[i]->query_ghost() == 1) continue; /* * If we get this far, then we still want to be attacking him */ attackers_tmp += ({ attackers[i] }); } /* * Copy the tmp list over to the attackers list. */ attackers = attackers_tmp; if (sizeof(attackers_tmp) == 0) any_attack = 0; return any_attack; } /* * Continue_attack is called from heart_beat.. * here is where we can try to see if we're dead or in combat. */ void continue_attack() { /* * Check if this object has just died. if so, do the death stuff. */ if (query("hit_points") < 0) { die(); return; } /* * If there's no one to attack, then we are finished. */ if (!any_attack) return; /* * Call the clean_up_attackers function to see who's left. If it * returns 0, then there's no one left. */ if (clean_up_attackers() == 0) { /* * No attackers in the room */ message(MSG_COMBAT, "The combat is over.\n", this_player()); return; } /* * Check to see if the player is doing something that prevents * him from making an attack. */ if (query("stop_attack")>0) { message(MSG_COMBAT, "You are too busy to make an attack!\n", this_player()); return; } /* * Check to see if we're under wimpy, and if so, run away. */ if (query("hit_points")*100/query("max_hp") < this_player()->query("wimpy")) { run_away(); return; } execute_attack(); } static int loop, noise; /* * This is the big, ugly CPU hogging function where the combat actually * takes place. */ varargs void execute_attack (int hit_mod, int dam_mod) { int att_sk, def_sk, str, dex, ac, wc, hit_chance; string name, verb1, verb2, vname, damstr, damstr2, wepstr, *verbs; mixed *contents; object *prots; object weapon; int old_inv, s; string victim, posgender; int *damrange; /* * Check to see if primary target is dead, if so move to the next attack * in the attackers queue. If the attackers queue is empty, stop attack call. */ if (attackers[0]->query("hit_points") < 0) { attackers -= ({ attackers[0] }); if (!attackers || !sizeof(attackers)) return; } /* * Is the target being protected? If so, find the alternate target. */ prots = attackers[0]->query("protectors"); if (prots && sizeof(prots) > 0) { /* * Get rid of all ineligible protectors: dead or not present. */ prots = filter_array(prots,"valid_protect", this_object()); /* * If there are eligible protectors, then move the protector to the top * of the list, adding him if needed. */ if (s = sizeof(prots)) { /* * Ok, I tried fixing it, but it is rather complex. In any case, * I do not think it is doing what it is supposed to do. * Someone mixed strings and objects all the way through... Leto */ // victim = prots[random(s)]; weapon = prots[random(s)]; /* * re-using a variable to save memory - sorry bout that :( */ // weapon = find_player(victim); //Leto ac = member_array(weapon,attackers); if (ac>-1) { attackers[ac]=attackers[0]; attackers[0]=weapon; } else { attackers = ({ weapon }) + attackers; weapon -> kill_ob(this_object()); } } } /* * hit_mod and dam_mod are modifiers that can be passed to the attack. * The heartbeat doesn't add them but you can make special attacks by * calling execute_attack directly. Be careful if you do so, you'll want * to also call kill_ob to make sure a fight starts... */ // if (!hit_mod) hit_mod=0; // if (!dam_mod) dam_mod=0; /* * Collect the various statistics needed to get the hit chance and damage. */ str = query("stat/strength"); dex = attackers[0]->query("stat/dexterity"); ac = attackers[0]->query("armor_class"); weapon = query("weapon1"); /* * If they don't have a weapon, they get their intrinsic combat skills. */ if (!weapon) { /* * These are the numbers/strings for a unarmed user attack. They're * hard-coded but you could equally well set them as properties in the * user object. */ wc = 2; dmin = 1; dmax = 3; verb1 = "swing at"; verb2 = "swings at"; wepstr = "fists"; weapontype = "Blunt weapons"; } else { /* * If we get here, then the player has a weapon, and we query the * weapon for its attack properties. * Does he have a second weapon? If so, use it on 20% of attacks. */ if (query("weapon2") && random(5)==0) { weapon = query("weapon2"); } wc = query("attack_strength"); damrange = weapon->query("damage"); dmin = damrange[0]; dmax = damrange[1]; verbs = allocate(2); verbs = weapon->get_verb(); verb1 = verbs[0]; verb2 = verbs[1]; wepstr = (string)weapon->query("name"); weapontype = capitalize(weapon->query("type")+" weapons"); } name = query("cap_name"); vname = attackers[0]->query("cap_name"); posgender = possessive(query("gender")); /* * Check the attacker's attack skill and the defenders skill(s). * * If a player, check his weapons skills. Otherwise, use the attack skill * for a monster. */ att_sk = query_skill(weapontype); /* * Ditto for the defender. */ if (!(int)attackers[0]->query_monster()==1) { if (attackers[0]->query("armor/shield")) { def_sk = attackers[0]->query_skill("Shield defense"); } else { def_sk = attackers[0]->query_skill("Parrying defense"); } } else { def_sk = attackers[0]->query_skill("defense"); } /* * This is the combat formula. * If you are using drunkenness, and want it to affect combat, then * call query("drunk"), which goes 1-25, and subtract it from the * hit chance. */ hit_chance = 30 + str + att_sk + 3*wc - dex - def_sk - 3*ac + hit_mod; /* * Attacking invisible creatures is really rather difficult. */ if ((int)attackers[0]->query("invisible")>0) hit_chance /= 5; /* * The hit chance is constrained to be between 2 and 98 percent. */ if (hit_chance<2) hit_chance = 2; if (hit_chance>98) hit_chance = 98; /* * Improve the skills of the attacker and defender. * * The probability of the skill improving depends on the hit chance. If the * hit chance is 0 or 100, the skill does not improve. If the hit chance is * 50%, then the skill improves automatically. The closer the hit chance is * to 50%, the more likely the skill is to improve. This rewards players for * taking on monsters roughly equal in skill to themselves. */ skill_improve_prob = hit_chance * (100-hit_chance) / 25; if (random(100)<skill_improve_prob) { improve_skill(weapontype,1); } if (random(100)<skill_improve_prob) { if (!attackers[0]->query_monster()) { if (attackers[0]->query("armor/shield")) { attackers[0]->improve_skill("Shield defense",1); } else { attackers[0]->improve_skill("Parrying defense",1); } } } /* * Get a list of all in the room who are listening to the battle. */ contents = all_inventory(environment(this_object())); contents = filter_array(contents, "filter_listening", this_object()); /* * This is the damage formula. * We have to calculate this first because we don't want to print messages * of the form "You hit for zero damage." If the damage is less than zero, * we print a "You miss" message regardless of the hit_chance roll. */ damage = random(dmax-dmin+1)+dmin+str/8-1+att_sk/10-def_sk/5+dam_mod; /* * Before we print any messages, we need to make ourselves visible. * Otherwise the person we're attacking doesn't get the message. We * become invisible again after the messages are printed. * This is klugey but real real easy. */ old_inv = query("invisible"); set ("invisible", 0); /* * If positive damage, and the hit lands, then we do damage and print * the appropriate damage messages. */ if (damage>0 && random(100)<hit_chance) { str = attackers[0]->query("hit_points"); if (damage) attackers[0]->receive_damage(damage); qs = objective((string)attackers[0]->query("gender")); /* * We print different damage messages based on how much damage was * done. You might want to make this system a little more interesting. * Go nuts. */ switch (damage) { case 1: damstr = "scratch "+qs+"."; damstr2 = "scratches "+qs+"."; break; case 2..3 : damstr = "do light damage."; damstr2 = "does light damage."; break; case 4..6 : damstr = "hit."; damstr2 = "hits."; break; case 7..9 : damstr = "deliver a solid blow."; damstr2 = "delivers a solid blow."; break; case 10..15 : damstr = "hit hard!"; damstr2 = "hits hard!"; break; case 16..20 : damstr = "inflict massive damage!"; damstr2 = "inflicts massive damage!"; break; default : /* * Mobydick just ran out of ideas at this point. :) */ damstr = "whomp "+qs+"!"; damstr2 = "whomps "+qs+"!"; } /* * The routines below check to see if all the listeners really * want to hear how the battle is going (Watcher, 4/27/93). */ if (!(query("noise_level") && damage < 2)) message(MSG_COMBAT, sprintf("You %s %s with your %s and %s\n", verb1, vname, wepstr, damstr), this_player()); for (loop = sizeof(contents); loop--; ) { if (contents[loop]) noise = (int)contents[loop]->query("noise_level"); if (noise && (noise > 1 || (noise == 1 && damage < 2))) continue; message(MSG_COMBAT, sprintf("%s %s %s with %s %s and %s\n", name, verb2, vname, posgender, wepstr, damstr2), contents[loop]); } if (attackers[0] && !(attackers[0]->query("noise_level") && damage < 2)) message(MSG_COMBAT, sprintf("%s %s you with %s %s and %s\n", name, verb2, posgender, wepstr, damstr), attackers[0]); } else { /* * If we got here, it means we missed the hit roll, or did zero damage. */ if (!query("noise_level")) message(MSG_COMBAT, sprintf("You %s %s with your %s but you miss.\n", verb1, vname, wepstr), this_player()); for (loop = sizeof(contents); loop--; ) if (contents[loop] && !contents[loop]->query("noise_level")) message(MSG_COMBAT, sprintf("%s %s %s with %s %s but misses.\n", name, verb2, vname, posgender, wepstr), contents[loop]); if (attackers[0] && !attackers[0]->query("noise_level")) message(MSG_COMBAT, sprintf("%s %s you with %s %s but misses you.\n", name, verb2, posgender, wepstr), attackers[0]); } /* * Restore the old invisibility setting. */ if (old_inv) set ("invisible", old_inv); return; } /* * This function filters out the living objects who are listening * to the present battle. */ int filter_listening(object obj) { if (obj == this_object() || obj == attackers[0]) return 0; return living(obj); } /* * This one filters dead or absent people from an array. */ int valid_protect (string str) { object foo; foo = find_player(str); if (!foo) return 0; if (environment(foo) != environment(this_object())) { return 0; } if ((int)foo->query("hit_points")<0) return 0; return 1; } void create() { if (user_exists(getuid())) return; /* * Until the user's name and id is set ... give it a temporary one. */ set("name", "noname", MASTER_ONLY); set("id", ({ "noname" })); /* * We set EUID of 0 so that the login daemon can export the proper * UID onto the player. If you are running without AUTO_SETEUID, then * this has no effect, but under auto-EUID-setting it's important. * Also it makes it mildly harder for people to get themselves into * trouble by cloning user.c. */ seteuid(0); /* * there's some standard properties that need to be locked so that * Joe Random Wizard can't break security by setting them on people * who haven't used them yet. */ set("npc", 0, LOCKED); set("snoopable", 0, MASTER_ONLY); set("invisible", 0, MASTER_ONLY); set("short", "@@query_short"); set("cap_name", capitalize(query("name")), MASTER_ONLY); set("title", "@@query_title", MASTER_ONLY); set("linkdead", "@@query_linkdead"); set("age", 0, MASTER_ONLY); set("ghost", 0, MASTER_ONLY); set("shell", "none", MASTER_ONLY); set("user", 1, MASTER_ONLY); set("vision", "@@query_vision"); set("harass", 0, OWNER_ONLY); #ifdef NO_PKILL set("no_attack", 1, OWNER_ONLY); #else set("no_attack",0, OWNER_ONLY); #endif alias = ([ ]); /* * Complete the standard user attribute settings. */ set("volume", MAX_VOLUME); set("capacity", MAX_CAPACITY); set("mass", USER_MASS); set("bulk", USER_BULK); set ("time_to_heal", 10); set("short", "@@query_short"); set("channels", "", MASTER_ONLY); enable_commands(); } void remove() { string euid; mixed *inv; int loop; if (previous_object()) { euid = geteuid(previous_object()); if ( (euid != ROOT_UID) && (euid != geteuid(this_object())) && !adminp(euid) ) { message(MSG_SYSTEM, "You may not remove other players.\n", this_player()); return; } } free_locks(this_object()); save_data(); destroy_autoload_obj(); inv = all_inventory(this_object()); for (loop = sizeof(inv); loop--; ) if (inv[loop]->query("prevent_drop")) inv[loop]->remove(); // CMWHO_D->remove_user(this_object()); if (link) link->remove(); living::remove(); } static int in_de_quit_script; varargs int quit(string str) { object *stuff, *inv; int i, j; if (!(origin() == ORIGIN_LOCAL || origin() == ORIGIN_DRIVER) && geteuid(previous_object()) != ROOT_UID) return 0; if (str) { notify_fail("Quit what?\n"); return 0; } /* * If the #define is on, then save their location for starting next time. */ #ifdef REAPPEAR_AT_QUIT if (environment(this_player())) { set ("start_location", file_name(environment(this_player()))); } else { set ("start_location", START); } #endif /* * Free any outstanding file locks. */ free_locks(this_object()); /* * Get rid of any party memberships. */ // check_team(); PARTY_D->check_party(this_object()); if ( wizardp( this_object() ) ) { string quit_script; // Pallando 93-02-11 quit_script = user_path( query( "name" ) ) + ".quit"; if ( file_size( quit_script ) > 0 ) { if (in_de_quit_script++) message(MSG_SYSTEM, "Oi, stupid! Don't put `quit' in your ~/.quit file\n", this_object()); else call_other( this_object(), "tsh", quit_script ); in_de_quit_script = 0; } } /* * If the "harass" logging is still on, then turn it off. */ if (query("harass") > 0 ) { set("harass", 0); message(MSG_INFO, "Harass Log turned off.\n", this_object()); } /* * If this is an invisible wizard, we let the invisibility stay on: but * if it's a player who cast the invisibility spell, then we want him * to be visible when he reappears. */ if (!wizardp(this_object())) set("invis", 0); if (link) { // link->set("last_on", time()); link->set("ip", query_ip_name(this_object())); } #ifdef QUIT_LOG if (link) log_file(QUIT_LOG, CAP_NAME + ": quit from " + query_ip_name(this_object()) + " [" + extract(ctime(time()), 4, 15) + "]\n"); else log_file(QUIT_LOG, CAP_NAME + ": swept [" + extract(ctime(time()), 4, 15) + "]\n"); #endif /* * First save the user's personal data. */ save_data(); /* * Then destroy autoloading inventory. */ destroy_autoload_obj(); /* * Now drop everything droppable on the user. */ inv = all_inventory( this_object() ); inv = filter_array(inv, "inventory_check", this_object()); if (inv && sizeof(inv)) command("drop all"); /* * Announce the departure of the user. */ if (this_object() && visible(this_object())) message(MSG_MOVEMENT, query("cap_name") + " left the game.\n", environment(), this_player()); if (this_object() && interactive(this_object())) ANNOUNCE->announce_user(this_object(), 1); #ifdef LOGOUT_MSG if (previous_object() == this_player() && this_player()) message(MSG_INFO, LOGOUT_MSG, this_object()); #endif /* * Clean up a few loose ends before shutting down the user. */ // CMWHO_D->remove_user(this_object()); if (link) link->remove(); living::remove(); return 1; } /* * This function determines if the user has anything droppable * when they quit the mud. */ static int inventory_check(object obj) { if (obj->query_auto_load()) return 0; if (!obj->query("short") || obj->query("prevent_drop")) return 0; return 1; } /* * This procedure is called from the setup() function below. It is * basically here to check that existing users get whatever new settings * they need to function in today's changing mudlib. */ void consistency_check() { int i,j; mapping doms, doms2; string *domlist; /* * if you think everyone has been "fixed" then what you put here should * moved to create() and taken out. * right now it's empty because hopefully everyone has been updated. */ // set("no_attack",0,READ_ONLY); /* * I don't know why this was done. I'm changing back to the * original _stopping_ player killing. Blue. */ #ifdef NO_PKILL set("no_attack", 1, OWNER_ONLY); #endif /* * Added by Leto to fix people with stupid vol/cap, causing * the quad bug to appear more often. */ if (query("volume") < 0) { message(MSG_SYSTEM, "\nVolume too small, fixed by consistency check.\n\n", this_object()); set("volume",500); log_file("consistency_check", "Fixed negative volume of "+query("cap_name") + "\n"); } else if (query("volume") > 10000) { message(MSG_SYSTEM, "\nVolume too large, fixed by consistency check.\n\n", this_object()); set("volume",500); log_file("consistency_check", "Fixed too large volume of "+query("cap_name") + "\n"); } if (query("capacity") < 0) { message(MSG_SYSTEM, "\nCapacity too small, fixed by consistency check.\n\n", this_object()); set("capacity",5000); log_file("consistency_check", "Fixed negative capacity of "+query("cap_name") + "\n"); } else if (query("capacity") > 10000) { message(MSG_SYSTEM, "\nCapacity too large, fixed by consistency check.\n\n", this_object()); set("capacity",5000); log_file("consistency_check", "Fixed too large capacity of "+query("cap_name") + "\n"); } if (query("bulk") < 0) { message(MSG_SYSTEM, "\nBulk too small, fixed by consistency check.\n\n", this_object()); set("bulk",1000); log_file("consistency_check", "Fixed negative bulk of "+query("cap_name") + "\n"); } else if (query("bulk") > 10000) { message(MSG_SYSTEM, "\nBulk too large, fixed by consistency check.\n\n", this_object()); set("bulk",1000); log_file("consistency_check", "Fixed too large bulk of "+query("cap_name") + "\n"); } if (query("mass") < 0) { message(MSG_SYSTEM, "\nMass too small, fixed by consistency check.\n\n", this_object()); set("mass",7500); log_file("consistency_check", "Fixed negative mass of "+query("cap_name") + "\n"); } else if (query("mass") > 75000) { message(MSG_SYSTEM, "\nMass too large, fixed by consistency check.\n\n", this_object()); set("mass",7500); log_file("consistency_check", "Fixed too large mass of "+query("cap_name") + "\n"); } /* * we don't want folks to be snoopable when they first log in. */ set("snoopable", 0, MASTER_ONLY); // Leto added a check for 'leader' // I'd really like to see this done at logoff time..... if(query("leader")) delete("leader"); /* * Added demotion from domains that no longer exist. Blue, 950330. */ if (query_link()->query("last_on") < 796700000) { /* Commented out by Leto after Lucas reported it didn't work. * I'll debug it when i have the time doms = query_link()->query("domains"); if (!doms) doms = ([ ]); doms2 = ([ ]); domlist = DOMAIN_LIST + ({ "primary" }); for (i = 0, j = sizeof(domlist); i<j; i++) { if (doms[domlist[i]]) doms2 += ([ domlist[i] : doms[domlist[i]] ]); } query_link()->set("domains", doms2); */ } } /* * This function is called when the player enters the game. It handles * news displays, player positioning, and other initial user setups. */ void setup() { string *news; int i, s; /* * Check to see if the user body has a "name" */ if (!query("name")) return; seteuid(getuid()); /* * Initiate user shell setup protocal */ init_setup(); /* * Display last logon and logon site */ if (link_data("last_on")) message(MSG_INFO, sprintf("\nLast logon: %s from %s.\n\n", ctime(link_data("last_on")), link_data("ip")), this_object()); /* * Set termtype, info is in connection.c */ set("termtype",query_link()->query("termtype")); debug("Setup: Running the consistency check.\n"); consistency_check(); // A catch-all to upgrade old users debug("Setup: Setting special flags and reading news.\n"); if (query("inactive")) delete("inactive"); CHANNELS_D -> initialize_user(); if ( !query("cwd") ) set("cwd", "/doc"); /* * Get the news from the news daemon and put it out line by line * to avoid overloading one write output. */ news = explode( (string)MSG_D->display_news(), "\n"); for (i = 0, s = sizeof(news); i < s; i++) message(MSG_INFO, news[i] + "\n", this_object()); if (query("hushlogin")) { complete_setup(); return; } #ifdef NO_LOGIN_PAUSE complete_setup(); return; #endif if (query("busy")) message(MSG_INFO, "\nYour busy flag is still on!\n", this_object()); if (query("hide")) message(MSG_INFO, "You are still hidden. Not announced!\n", this_object()); message(MSG_INFO, "Terminal type is "+query("termtype")+".\n\n" "[Press ENTER to continue] ", this_object()); input_to("complete_setup",2); return; } /* * Complete remainder of character setup after NEWS * has been read by the entering player */ static varargs void complete_setup (string str) { object ghost; string temp; mixed student_time; /* * This is here to permit an admin to shut down the game from the * "Press ENTER to continue" prompt. Sometimes this is helpful if there * is an object that is interfering with commands or otherwise wedging * the game, but will go away if the game is reset. It's not a security * problem because only admins can use it, and they could just log in * and use the shutdown command anyway...;) */ #ifdef SAFETY_SHUTDOWN if (adminp(getuid(this_object()))) { if (str=="shutdown") { CMD_SHUTDOWN->cmd_shutdown("0 because safety shutdown invoked."); } } #endif message(MSG_INFO, "\n", this_object()); link->set("last_on", time()); link->set("ip", query_ip_name(this_object())); set("reply", 0); set("wreply", 0); debug("Complete_setup: Moving to the start location.\n"); temp = getenv("START"); if (!(temp && stringp(temp) && move(temp) == MOVE_OK)) { temp = query("start_location"); #ifdef REAPPEAR_AT_QUIT if (!(temp && stringp(temp) && move(temp) == MOVE_OK)) move(START); #else move (START); #endif } call_out("save_me", 1); ANNOUNCE->announce_user(this_object(), 0); /* * This is commented out because of a problem with socket handling * by some versions of UNIX. Basically, if you make the call to USERID_D, * then the driver will call back to the user's host machine and ask for * the user's account name. Under some OS's (Ultrix and HP_UX to name two, * but there may be more) if the query returns "Host is unreachable", eg if * there is a firewall machine between the driver and the user's machine, * then the driver will break the user's connection, and anyone from that machine * will be unable to play the MUD. * You can undefine this at your own risk, but you'll be cutting off anyone * from a protected site, which means most .com addresses and a fair * smattering of other hosts, if your OS behaves this way. TMI-2's host * runs Ultrix, so we leave it commented out. A good 90% of hosts don't * support the user name query protocol anyway, so we're not losing that * much. It's your decision if you want to get the names of the other 10% * of users, or leave it commented out... */ // USERID_D->query_userid(); debug("Complete_setup: Checking to see if user is dead.\n"); if (link_data("dead")) { ghost = create_ghost(); move( VOID ); set_temp("force_to_look", 1); ghost->force_me("look"); set_temp("force_to_look", 0); message(MSG_INFO, "\nYou suddenly realize that you are still a ghost.\n", ghost); message(MSG_MOVEMENT, sprintf("%s enters the game.\n", query("cap_name")), environment(ghost), ({ ghost })); remove(); return; } if (visible(this_object())) message(MSG_MOVEMENT, query("cap_name") + " enters the game.\n", environment(), this_player()); #ifdef LOGIN_LOG log_file(LOGIN_LOG, CAP_NAME + ": logged in from " + query_ip_name(this_object()) + " [" + extract(ctime(time()), 4, 15) + "]\n"); #endif set_temp("force_to_look", 1); command("look"); set_temp("force_to_look", 0); student_time = STUDENT_D->query_time_left(query("name")); if (student_time != -1) { if (student_time<0) message(MSG_SYSTEM, sprintf("\n%s\n\n", blink(" [WARNING: Your time period as a student has" " ended. You should copy any files\n" " you want to keep using ftpd. " "Type \"help ftpd\" if you need to.]")), this_object()); else message(MSG_SYSTEM, "\n [You have " + bold(format_time(student_time, 1)) + " left as a student]\n\n", this_object()); } debug("Complete_setup: Setup complete.\n"); } /* * This function is called cyclically to save the user data * periodically, if AUTOSAVE is defined. */ static void autosave_user() { remove_call_out("autosave_user"); call_out("autosave_user", AUTOSAVE); if (!wizardp(this_object())) message(MSG_INFO, "Autosave.\n", this_object()); save_me(); } void heart_beat() { int age; continue_attack(); unblock_attack(); heal_up(); #ifdef IDLE_DUMP if (this_object() && interactive(this_object()) && !wizardp(this_object()) && query_idle(this_object()) > IDLE_DUMP) call_user_dump("idle"); #endif /* * Add to the user's online age total. */ age = query("age"); if (!query_temp("last_age_set")) set_temp("last_age_set", time()); age += (time() - query_temp("last_age_set")); set_temp("last_age_set", time()); ob_data["age"] = age; } /* * This function returns whether the user is linkdead or not. */ nomask int query_linkdead() { return !interactive(this_object()); } static void net_dead() { save_data(); message(MSG_MOVEMENT,(string)query("cap_name") + " has gone net-dead.\n", environment(), this_object()); // check_team(); PARTY_D->check_party(this_object()); ANNOUNCE->announce_user(this_object(), 3); // CMWHO_D->remove_user(this_object()); set_heart_beat(0); #ifdef LINKDEAD_DUMP call_out("call_user_dump", LINKDEAD_DUMP, "linkdead"); #endif #ifdef NETDEAD_LOG log_file(NETDEAD_LOG, LINK_CAP_NAME + ": net-dead from " + query_ip_name(this_object()) + " [" + extract(ctime(time()), 4, 15) + "]\n"); #endif link->remove(); } void restart_heart() { message(MSG_MOVEMENT, query("cap_name")+" has reconnected.\n", environment(), this_object()); message(MSG_MOVEMENT, "Reconnected.\n", this_object()); ANNOUNCE->announce_user(this_object(), 2); USERID_D->query_userid(); set_heart_beat(1); set("inactive", 0); remove_call_out("call_user_dump"); #ifdef RECONNECT_LOG log_file(RECONNECT_LOG, CAP_NAME + ": reconnected from " + query_ip_name(this_object()) + " [ " + extract(ctime(time()), 4, 15) + "]\n"); #endif } void call_user_dump(string type) { if (this_player() && this_player() != this_object()) return; message(MSG_SYSTEM, "\nSorry, you have idled too long.\n", this_object()); if (environment()) { if (type == "linkdead") message(MSG_MOVEMENT, sprintf( "A janitor suddendly appears and sweeps " "%s into a nearby vortex.\n", query("cap_name")), environment(), this_object()); else message(MSG_MOVEMENT, sprintf("%s has idled too long\n", query("cap_name")), environment(), this_object()); } quit(); } static void die() { object killer, ghost, corpse, coins, *stuff; mapping wealth; string *names, name; int i, res, totcoins; /* * Set the user's killer variable */ killer = query("last_attacker"); if (!killer) killer = previous_object(); /* * If the wizard has themself set to "immortal", then * they cannot die ... stop death call. */ if (wizardp(this_object()) && query("immortal")) { message(MSG_COMBAT, "Your immortality protects you from certain death.\n", this_object()); return; } /* * If the user is already dead ... stop death call. */ if (link_data("dead")) return; /* * Bail out of any parties they may be involved in. */ // check_team(); PARTY_D->check_party(this_object()); /* * Announce the user's death */ message(MSG_INFO, "You have died.\n", this_object()); message(MSG_INFO, query("cap_name") + " has died.\n", environment(), this_player()); init_attack(); /* * Setup corpse with user's specifics */ corpse = clone_object("/obj/corpse"); corpse->set_name(query("cap_name")); i = query("mass"); if (i>0) corpse->set("mass", i); i = query("bulk"); if (i>0) corpse ->set("bulk", i); i = query("capacity"); if (i>0) corpse ->set ("capacity", i); i = query("volume"); if (i>0) corpse ->set("volume", i); corpse->move(environment(this_object())); stuff = all_inventory(this_object()); for (i = sizeof(stuff); i--; ) if (!stuff[i]->query("prevent_drop") && !stuff[i]->query_auto_load()) stuff[i]->move(corpse); wealth = query("wealth"); if (wealth) { names=keys(wealth); for (i = sizeof(wealth); i--; ) { coins = clone_object(COINS); coins->set_type(names[i]); coins->set_number(wealth[names[i]]); totcoins = totcoins + wealth[names[i]]; if (coins) coins->move(corpse); } } set ("wealth", ([ ])); res = query("capacity"); set ("capacity", res + totcoins); if (killer) name = (string)killer->query("cap_name"); #ifdef KILLS if (killer) log_file(KILLS, CAP_NAME + " was killed by " + (name ? name + " " : "") + "(" + file_name(killer) + ") [" + extract(ctime(time()), 4, 15) + "]\n"); else log_file(KILLS, CAP_NAME + " was killed by something [" + extract(ctime(time()), 4, 15) + "]\n"); #endif /* * Switch user to ghost body */ ghost = create_ghost(); if (!ghost) return; message(MSG_INFO, "\nYou have a strange feeling.\n" "You can see your own lifeless body from above.\n\n", ghost); if (killer) { ghost->set("killer_ob", killer); ghost->set("killer_name", (string)killer->query("name")); } /* * Use a call_out to make sure all the above calls have * completed their required processing (so we don't lose * this_object() before everything is done). */ call_out("remove", 0); } string query_short() { if (query("name") == "noname") return "Noname"; if (!interactive(this_object())) return (query("title") + " [disconnected]"); if (query("inactive")) return query("cap_name") + " is presently inactive"; else if (this_player() && attackers && sizeof(attackers) && environment(this_player()) == environment(this_object())) return query("cap_name") + " is attacking " + capitalize((string)attackers[0]->query("name")) + "."; else if (query_idle(this_object())>300) return query("title")+" (idle)"; return query("title"); } string query_title() { string str; if (!(str = getenv( "TITLE" ))) str = query("cap_name"); else if ( !sscanf(str, "%*s$N%*s") ) str = query("cap_name") + " " + str; else str = replace_string( str, "$N", ""+ query("cap_name") ); return str; } /* * not in use anymore? */ nomask void catch_msg(object source, string *msg) { int i, s; for (i = 0, s = sizeof(msg); i < s; i++) receive(msg[i]); } void hide(int i) { set_hide(i); } /* * support for debugging an error that a user encounters during this login */ private static mapping last_error; void set_error(mapping m) { if (previous_object() != master()) return; last_error = m; } mapping query_error() { if (file_name(previous_object()) == CMD_DBXFRAME || file_name(previous_object()) == CMD_DBXWHERE) return last_error; else message(MSG_SYSTEM, "No permission to query error!", this_player()); } /* * this driver apply is called when the user's environment is being * destructed; move the player to a safe place, or end up in limbo */ int move_or_destruct(object ob) { object old_env = environment(); if (origin() != ORIGIN_DRIVER) return 0; // I guess 0 Leto if (!ob && old_env != find_object(VOID)) ob = find_object(VOID); if (!ob && old_env != find_object(HALL)) ob = find_object_or_load(HALL); if (!ob) { /* * This is bad. Try to save them anyway. */ ob = clone_object("/std/room"); if (!ob) { /* * we did our best ... */ return 1; } ob->set_short("A Temporary room"); ob->set_long("Something really nasty happened.\n"); ob->set("light",1); // Nice to actually see the short and long;) } move(ob); if (environment() == old_env) { /* * we HAVE to move or we get dested. Override weight checks etc */ move_object(ob); // Leto (no longer need to pass this_object as arg) } return 0; } /* EOF */