/* Do not remove the headers from this file! see /USAGE for more info. */
//### lots of functions in here need to be autodoc'd
// /std/player.c Written after login.c 1-12-94
// Rust@ZorkMUD
// with mods by alot of us:
// including: Nevyn@ZorkMUD (GUE)
// Peregrin @ZorkMUD (GUE)
// Beek ... Deathblade ... The rest of the Zorkmud staff ...
//
#include <daemons.h>
#include <mudlib.h>
#include <config.h>
#include <security.h>
#include <setbit.h>
#include <playerflags.h>
#include <commands.h>
#include <move.h>
#include <hooks.h>
#include <size.h>
#include <combat.h>
#include <clean_up.h>
// Files we need to inherit --
inherit MONSTER;
inherit M_ACCESS;
inherit M_SMARTMOVE;
#ifndef EVERYTHING_SAVES
private inherit M_SAVE; // don't want people calling load_from_string()
// externally
#endif
inherit __DIR__ "body/quests";
inherit __DIR__ "body/mailbase";
inherit __DIR__ "body/newsdata";
inherit __DIR__ "body/cmd";
inherit __DIR__ "body/help";
inherit __DIR__ "body/wizfuncs";
inherit __DIR__ "body/money";
inherit __DIR__ "body/start";
inherit __DIR__ "body/time";
inherit __DIR__ "body/naming";
#ifdef USE_STATUS_LINE
inherit __DIR__ "body/status_line";
#endif
#ifdef USE_SKILLS
inherit __DIR__ "body/skills";
#endif
#ifdef USE_TITLES
inherit __DIR__ "body/title";
#endif
#ifdef USE_SIMPLE_LEVEL
inherit __DIR__ "body/simple_level";
#endif
#ifdef USE_WIZ_POSITION
inherit __DIR__ "body/wiz_position";
#endif
#ifdef USE_GUILDS
inherit __DIR__ "body/guilds";
#endif
#ifdef USE_STATS
inherit M_BODY_STATS;
#endif
// Global variables --
private string reply;
private string array channel_list = ({ });
private string plan;
private static object link;
private mixed saved_items;
// Spouse, for marriage. Added by Aziz
private string spouse;
// interfaces for other objects to manipulate our global variables
//:FUNCTION query_link
//Return our link object
nomask object query_link()
{
return link;
}
// Functions to set and query spouse. Added by Aziz.
nomask string query_spouse(){
return spouse;
}
void set_spouse(string new_spouse){
spouse=new_spouse;
save_me();
}
// End Aziz code block. :)
#ifdef EVERYONE_HAS_A_PLAN
//:FUNCTION query_plan
//Returns our plan
nomask string query_plan()
{
return plan;
}
//:FUNCTION set_plan
//Sets our plan
nomask void set_plan(string new_plan)
{
if ( this_body() != this_object() )
error("illegal attempt to set plan\n");
plan = new_plan;
save_me();
}
#endif /* EVERYONE_HAS_A_PLAN */
static void update_for_new_body(mapping tmp) {
/* nothing for now; can be overloaded for races that need it */
}
/* initialize various internal things */
//### needs a new name
private nomask void init_cmd_hook()
{
object mailbox;
int idx;
link = previous_object();
mailbox = MAILBOX_D->get_mailbox(query_userid());
idx = mailbox->first_unread_message();
if ( idx == -1 )
{
mailbox->set_message_index(mailbox->query_message_count() - 1);
}
else
{
mailbox->set_message_index(idx);
write("\n>>You have new mail<<\n");
}
write( "\n" );
naming_init_ids();
#ifdef USE_MASS
set_mass(100);
#endif
#ifdef USE_SIZE
set_size(VERY_LARGE);
#endif
set_max_capacity(VERY_LARGE);
if (saved_items) {
string e;
if (e = catch(load_from_string(saved_items, 1))) {
mapping tmp = restore_variable(saved_items);
if (tmp["#base_name#"] != base_name(this_object())) {
update_for_new_body(tmp);
tmp["#base_name#"] = base_name(this_object());
load_from_string(save_variable(tmp), 1);
} else
error("Rethrown: " + e);
}
saved_items = 0;
}
}
nomask void su_enter_game(object where)
{
init_cmd_hook();
//### this should go away once we torch the corresponding leave msg for 'su'
CHANNEL_D->deliver_emote("announce", query_name(),
sprintf("enters %s.", mud_name()));
if ( is_visible() )
simple_action("$N $venter "+mud_name()+".");
CHANNEL_D->register_channels(channel_list);
move(where);
}
void enter_game(int state)
{
switch (state) {
case 1:
/* new user */
if (wizardp(link)) {
write("\n"
"Hi, new wiz! Tuning you in to all the mud's important channels.\n"
"Doing: wiz /on\n"
"Doing: chan news /on (you'll see when new news is posted.)\n"
"Doing: gossip /on\n"
"Doing: newbie /on\n"
"Doing: announce /on\n"
"\n");
/* these will be registered later */
channel_list = ({ "wiz", "news", "gossip",
"newbie", "announce" });
/* So the hapless new wizard doesn't get spammed. */
set_ilog_time(time());
} else {
write("\n"
"Tuning in the newbie channel for you. (newbie /on)\n"
"Tuning in the gossip channel for you. (gossip /on)\n"
"\n");
/* these will be registered later */
channel_list = ({ "gossip", "newbie" });
}
/* FALLTHROUGH */
case 0:
/* existing user */
init_cmd_hook();
CHANNEL_D->deliver_emote("announce", query_name(),
sprintf("enters %s.", mud_name()));
/* move the body. make sure this comes before the simple_action */
if ( !move_to_start() ) {
write("Uh-oh, you have no environment.\n");
} else {
/* we don't want other people to get the extra newlines */
write("\n");
if(is_visible())
simple_action("$N $venter "+mud_name()+".");
write("\n");
}
CHANNEL_D->register_channels(channel_list);
if ( wizardp(link) ) {
DID_D->dump_did_info(query_ilog_time(),
({ "",
"Changes since you last logged in",
"********************************",
"" }),
0,
(: enter_game, 2 :));
set_ilog_time(time());
return;
}
/* FALLTHROUGH */
case 2:
do_game_command("look");
}
}
//:FUNCTION save_me
//Saves us :-)
void save_me()
{
object shell_ob = link && link->query_shell_ob();
string userid = query_userid();
/* save the shell information */
if ( shell_ob )
shell_ob->save_me();
//### This check is bogus. What should it be?
// This check also doesn't work for su's -- John
// if (previous_object()==this_object())
saved_items = save_to_string(1); // 1 meaning it is recursive.
unguarded( 1, (: save_object , USER_PATH(userid) :) );
saved_items = 0;
}
//:FUNCTION remove
//Handle mailboxes and the last login daemon, as well as the normal stuff
void remove()
{
object ob;
if ( !clonep() )
{
::remove();
return;
}
save_me();
MAILBOX_D->unload_mailbox(query_userid());
unload_mailer();
LAST_LOGIN_D->register_last(query_userid());
::remove();
}
//### This should be protected.
//:FUNCTION quit
//Quit the game.
void quit()
{
if ( !clonep() )
{
::remove();
return;
}
if (is_visible())
simple_action("$N $vhave left "+mud_name()+".");
CHANNEL_D->deliver_emote("announce", query_name(),
sprintf("has left %s.", mud_name()));
CHANNEL_D->unregister_channels();
#ifdef PLAYERS_START_WHERE_THEY_QUIT
if (environment() && !wizardp(link))
set_start_location(file_name(environment()));
#endif
remove();
}
void do_receive(string msg, int msg_type) {
if ( link )
link->do_receive(msg, msg_type);
}
//:FUNCTION set_reply
//set_reply(s) sets the person to whom 'reply' goes to.
void set_reply(string o)
{
reply = o;
}
//:FUNCTION query_reply
//query the person to whom reply goes to
string query_reply()
{
return reply;
}
//:FUNCTION net_dead
//This function is called when we lose our link
void net_dead()
{
//### add security here?
if ( is_visible() )
simple_action("$N $vhave gone link-dead.");
CHANNEL_D->deliver_emote("announce", query_name(),
sprintf("has gone link-dead.", mud_name()));
}
//:FUNCTION reconnect
//This function is called when we get our link back
void reconnect(object new_link)
{
//### add security here?
link = new_link;
if(is_visible())
simple_action("$N $vhave reconnected.");
CHANNEL_D->deliver_emote("announce", query_name(),
sprintf("has reconnected.", mud_name()));
}
//:FUNCTION die
//This function is called when we die :-)
void die()
{
/* Unavoidable historical problem. I originally spec'ed die() as the function
* that got called when you died; i.e. it does whatever else needs to be done
* that the combat code hasn't already taken care of.
*
* Unfortunately, at some later date, someone decided that @ .me->die() should
* kill you, which is incompatible with the first definition. Of course, they
* quickly found out that this leaves you only half dead or so, since only the
* extra stuff gets done.
*
* Some really clever person put a 'set_hp(0)' in here to fix that. This
* tells the combat code to kill us. Unfortunately, that means die() gets
* called *again*, resulting in this function running twice.
*
* In order to be compatible with both abu^H^H^Huses, the following check
* asks the combat code if it considers use dead already. If we aren't
* dead, we ask the combat code to kill us. If we were called by the
* combat code, we continue on and do the right thing.
*
* This fixes yet ANOTHER problem that klu^H^H^Hfeature introduced, which
* is that calling die() in dead people isn't a NOP like it should be.
* In that case, we now call the combat code to kill us, which notices
* we are already dead, and correctly does nothing.
*/
if (!query_ghost()) {
set_hp(0);
return;
}
if ( wizardp(link) )
{
if(is_visible())
simple_action("If $n $vwere mortal, $n would now no longer be mortal.");
set_hp(query_max_hp());
stop_fight();
return;
}
if(is_visible())
simple_action("$N $vhave kicked the bucket, and $vare now pushing up the daisies.");
receive_private_msg("\n\n **** You have died ****\n\n"
"A pity, really. Way too many people dying these days for me to just patch\n"
"everyone up. Oh well, you'll live.\n",0,0);
rack_up_a_death();
#ifdef DEATH_MESSAGES
{
// another option is to choose the message here based on player level
// or something, instead of just letting action() pick at random.
// something like:
// action(({ this_object()}),
// (MESSAGES_D->get_messages("player_death"))[query_level()/5])[1];
string msg = action(({this_object()}),
MESSAGES_D->get_messages("player-death"))[1];
tell( bodies() - ({ this_body() }), msg );
}
#endif
}
int clean_up() {
return NEVER_AGAIN;
}
//:FUNCTION id
//id(s) returns 1 if we respond to the name 's'
int id(string arg)
{
if(!is_visible() && arg == lower_case(query_invis_name()))
return 1;
return ::id(arg);
}
string stat_me()
{
string result = short() + "\n" +
"Userid: " + query_userid() + "\n" +
::stat_me();
if ( link )
result += link->stat_me();
return result;
}
private void create(string userid)
{
if ( !clonep() )
return;
if ( base_name(previous_object()) != USER_OB )
error("security violation: illegal attempt to change name\n");
messages = ([]);
monster::create();
#ifdef USE_STATS
m_bodystats::create();
#endif
/*
** Make some of the flags non-persistent (they won't be saved).
*/
configure_set(PLAYER_NP_FLAGS, 1);
set_long( (: our_description :) );
naming_create(userid);
unguarded(1, (: restore_object, USER_PATH(userid), 1 :));
// up to the player
set_attack_speed(0);
}
/*
** Listen to channel messages and manipulate the channels
*/
void channel_rcv_string(string channel_name, string message)
{
receive_private_msg(message);
}
void channel_rcv_soul(string channel_name, array data)
{
string msg;
if ( data[0][0] == this_object() )
msg = data[1][0];
else if ( sizeof(data[0]) == 2 && data[0][1] != this_object() )
msg = data[1][2];
else
msg = data[1][1];
receive_private_msg(msg);
}
void channel_add(string which_channel)
{
channel_list += ({ which_channel });
CHANNEL_D->register_channels(({ which_channel }));
}
void channel_remove(string which_channel)
{
channel_list -= ({ which_channel });
CHANNEL_D->unregister_channels(({ which_channel }));
}
string * query_channel_list()
{
return channel_list;
}
//### temp hack. be both user and body
nomask object query_body()
{
return this_object();
}
/* verb interaction */
mixed indirect_give_obj_to_liv(object ob, object liv) {
return 1;
}
int go_somewhere(string arg)
{
object env = environment( this_object());
int ret;
if(!(ret=::go_somewhere(arg)) && (env))
return env->do_go_somewhere(arg);
return ret;
}
string inventory_header()
{
return query_name() + " is carrying:\n";
}
int ob_state()
{
return -1;
}
void force_look(int force_long_desc)
{
environment(this_object())->do_looking(force_long_desc, this_object());
}
// Called when our environment destructs. If we don't move, we get dested too.
void move_or_destruct(object suggested_dest) {
mixed err;
object dested_env = environment();
mixed destination;
if ( !query_link() )
return;
// Might want to add another failsafe room on the end of this list
// that doesn't inherit room.c and is guaranteed to load/accept people.
foreach (destination in ({ suggested_dest, VOID_ROOM, this_body()->query_start_location(), START, WIZARD_START })) {
err = catch {
if (stringp(destination))
destination = load_object(destination);
if (destination != dested_env) {
err = move(destination);
if (stringp(err))
throw(err);
} else
throw("Being destructed.\n");
};
if (destination && !err) {
receive_private_msg(dested_env->short() + " being destructed: You have been moved to " + destination->short() + ".\n");
return;
} else {
if (destination)
receive_private_msg("Cannot move to " + destination->short() + ": " + err);
}
}
receive_private_msg("Uh oh..., couldn't move you anywhere. Goodbye.\n");
(this_object()->query_link())->remove();
}
#ifdef USE_STATS
int to_hit_base() {
return 50 - query_agi();
}
int damage_bonus() {
return fuzzy_divide(query_str(), 10);
}
void refresh_stats() {
m_bodystats::refresh_stats();
}
#endif /* USE_STATS */
#ifdef USE_SKILLS
int hit_skill()
{
//### change to something based on skill...
return fuzzy_divide(query_agi(), 2);
}
#endif /* USE_SKILLS */
/*
** These are overrides from our ancestor (MONSTER)
*/
string query_name() { return naming::query_name(); }
string short() { return query_name(); }
string a_short() { return query_name(); }
string the_short() { return query_name(); }
string in_room_desc() { return base_in_room_desc() + query_idle_string(); }
/* end of naming overrides */
#ifdef USE_SKILLS
class combat_result array negotiate_result(class combat_result array result)
{
result = (class combat_result array)::negotiate_result(result);
/*
** See if we have disarmed the opponent by using our 'combat/disarm'
** skill. Note that testing the skill will also train it.
*/
//### where's a better place to put this?
//### ack. we should be caching our aggregate_skill()
//### what the heck is the opposing skill?
//### ... for now, use 5000; eventually, use target's experience
if ( test_skill("combat/disarm", 5000) )
{
result = ({ new(class combat_result,
special : RES_DISARM,
message : "!disarm") }) + result;
}
return result;
}
#endif /* USE_SKILLS */
int allow(string what)
{
if ( this_body() == this_object() )
{
return 1;
}
return 0;
}