/* -*- LPC -*- */
/*
* $Locker: $
* $Id: teach.c,v 1.48 2003/02/25 17:20:10 trilogy Exp $
*/
#include <skills.h>
#include <tune.h>
#include <command.h>
#include <cmds/teach.h>
#include <playtesters.h>
#include <player.h>
inherit "/cmds/base";
//#define DEBUG 1
#define DEBUGGER "ceres"
/** If this is defined it enforces a maximum level to teach others. */
#undef ENFORCE_MAX_TEACH_LEVEL
int command_teach( object *obs, string comm, object teacher );
int spell_teach(object *obs, string spell, object teacher);
int teach_skill(object *obs, string str, object teacher);
int cmd_int(string str, object *obs, object teacher);
int check_ignoring(object ignorer, object ignoree);
int query_auto_teaching(object teacher, object learner) {
return teacher->query_property(TEACH_COMMAND_AUTO_PROPERTY);
} /* query_auto_teaching() */
int cmd(string str, object *obs, object player) {
int ret;
object ob;
object* ear;
if (this_player()->query_property(PASSED_OUT_PROP)) {
add_failed_mess("You cannot teach while passed out.\n");
return 0;
}
if (this_player()->query_fighting()) {
add_failed_mess("You cannot teach or learn anything while you are "
"fighting!\n");
return 0;
}
if (this_player()->check_earmuffs("teach")) {
add_failed_mess("You have teaching earmuffed.\n");
return 0;
}
if (player) {
ear = filter(obs, (: userp($1) && !interactive($1) :));
if (sizeof(ear)) {
obs -= ear;
add_failed_mess("$C$$I does not have enough responsiveness to teach "
"you.\n", ear);
if (!obs) {
return 0;
}
}
ear = filter(obs, (: $1->check_earmuffs("teach") :));
if (sizeof(ear)) {
obs -= ear;
if (sizeof(ear) == 1) {
add_failed_mess("$I has " + ear[0]->query_possessive() + " earmuffs "
"on.\n", ear);
} else {
add_failed_mess("$I have their earmuffs on.\n", ear);
}
if (!obs) {
return 0;
}
}
ear = filter(obs, (: check_ignoring($1, this_player()) :));
if (sizeof(ear)) {
obs -= ear;
add_failed_mess("$I is ignoring you, or you are ignoring them.\n",
ear);
if (!obs) {
return 0;
}
}
ear = filter(obs, (: $1->query_property(PASSED_OUT_PROP) :));
if (sizeof(ear)) {
obs -= ear;
add_failed_mess("$I is passed out and looks rather worse for wear.\n",
ear);
if (!obs) {
return 0;
}
}
ear = filter(obs, (: $1->query_fighting() :));
if (sizeof(ear)) {
obs -= ear;
add_failed_mess("$I is currently beating stuff up, please wait "
"for them to finish.\n", ear);
if (!obs) {
return 0;
}
}
obs = filter(obs, (: !$1->query_creator() :) );
if (!sizeof(obs)) {
add_failed_mess("None of those people are allowed to teach you.\n");
return 0;
}
obs = filter(obs, (: query_auto_teaching($1, this_player()) :));
if (!sizeof(obs)) {
add_failed_mess("None of those people have auto teaching turned "
"on.\n");
return 0;
}
obs = filter(obs, (: $1->query_time_left() > 0 :));
if (!sizeof(obs)) {
add_failed_mess("All the people you are trying to learn from appear "
"to be busy.\n");
return 0;
}
foreach (ob in obs) {
ret |= cmd_int(str, ({ this_player() }), ob);
}
return ret;
}
return cmd_int(str, obs, this_player());
} /* cmd() */
int cmd_int(string str, object *obs, object teacher) {
if (teacher->query_property("dead")) {
add_failed_mess("You wave your arms around, and your lips move but "
"they can't hear what you are saying.\n");
return 0;
}
obs = filter(obs, (: !$1->query_property("dead") :));
if (!sizeof(obs)) {
add_failed_mess("You must teach someone, preferably living.\n");
return 0;
}
if (sizeof(obs) > 1) {
add_failed_mess("You can only teach one person at a time.\n");
return 0;
}
if (!command_teach(obs, str, teacher) &&
!spell_teach(obs, str, teacher) &&
!teach_skill(obs, str, teacher))
{
return 0;
}
return 1;
} /* cmd() */
int check_ignoring(object ignorer, object ignoree) {
return (ignorer->query_property("ignoring") &&
member_array(ignoree->query_name(),
ignorer->query_property("ignoring")) != -1);
} /* check_ignoring() */
int command_teach(object *obs, string comm, object teacher) {
string cmd_ob;
object *succ;
object *no_go;
object *me_low;
mixed *him_low;
object *know_already;
object *ear_muffed;
object *ignored;
object *ignoring;
object ob;
string *tmp_stuff;
int i;
class command cmd = new(class command, verb : comm);
mixed tmp;
if (member_array(comm, teacher->query_known_commands()) == -1 ||
!CMD_D->HandleStars(cmd))
{
if (CMD_D->HandleStars(cmd)) {
if (teacher == this_player()) {
add_failed_mess("You do not know the command " + comm + ".\n");
} else {
add_failed_mess(teacher->the_short(1) +
" does not know the command " + comm + ".\n");
}
}
return 0;
}
obs -= ({ teacher });
if (!sizeof(obs)) {
add_failed_mess("You cannot teach yourself a command.\n");
return 0;
}
if (teacher->check_earmuffs("teach")) {
if (teacher == this_player()) {
add_failed_mess("You have teaching earmuffed.\n");
} else {
add_failed_mess(teacher->the_short(1) + " has teaching earmuffed.\n");
}
return 0;
}
cmd_ob = CMD_D->GetPaths(cmd->verb)[0] + "/" + cmd->verb;
succ = ({ });
ear_muffed = ({ });
know_already = ({ });
no_go = ({ });
me_low = ({ });
him_low = ({ });
ignored = ({ });
if (teacher->query_property("ignoring")) {
ignoring = filter(obs, (: check_ignoring($(teacher), $1) :));
obs -= ignoring;
}
foreach(ob in obs) {
if (!living(ob)) {
continue;
}
if (teacher == this_player()) {
tmp = ob;
} else {
tmp = "you";
}
if (member_array(comm, ob->query_known_commands()) != -1) {
know_already += ({ tmp });
} else if (ob->check_earmuffs("teach")) {
ear_muffed += ({ tmp });
} else if (check_ignoring(ob, teacher)) {
ignored += ({ tmp });
} else {
switch (cmd_ob->can_teach_command(teacher, ob)) {
case 1:
succ += ({ ob });
break;
case 0 :
no_go += ({ tmp });
break;
case -1 :
me_low += ({ tmp });
break;
case -2 :
him_low += ({ tmp });
break;
}
}
}
if (!succ || !sizeof(succ)) {
if (teacher != this_player()) {
tell_object(teacher, this_player()->the_short() + " tried to "
"learn " + comm + " from you automatically, "
"but failed.\n");
}
if (sizeof(no_go) > 0) {
add_failed_mess(teacher->the_short() + " cannot teach " + comm +
" to $I.\n", no_go);
}
if (sizeof(me_low)) {
if (teacher == this_player()) {
add_failed_mess(teacher->the_short() +
" are too low a level to teach " + comm +
" to $I.\n", me_low);
} else {
add_failed_mess(teacher->the_short() +
" is too low a level to teach " + comm +
" to $I.\n", me_low);
}
}
if (sizeof(ear_muffed) > 0) {
add_failed_mess("$I has teaching events earmuffed, you are unable "
"to teach them.\n", ear_muffed);
}
if (sizeof(him_low) > 0) {
add_failed_mess("$I " +
((sizeof(him_low) == 1 &&
him_low[0] != this_player()) ? "is" : "are") +
" too low a level to learn " + comm + ".\n", him_low);
}
if (sizeof(know_already)) {
add_failed_mess("$I already know" +
(sizeof(know_already) > 1 ||
teacher != this_player()? "" : "s") +
" the command " + comm + ".\n", know_already);
}
if (sizeof(ignoring)) {
if (this_player() == teacher) {
add_failed_mess("You are ignoring $I.\n", ignoring);
} else {
add_failed_mess(teacher->the_short(1) + " is ignoring $I.\n",
ignoring);
}
}
if (sizeof(ignored)) {
add_failed_mess("You are being ignored by $I.\n", ignored);
}
return 0;
}
write(teacher->the_short() + " offer" +
(this_player() == teacher ? "" : "s") + " to teach " + comm +
" to " + query_multiple_short(succ) + ".\n");
if (teacher == this_player()) {
if (sizeof(no_go)) {
write("You cannot teach " + comm + " to " +
query_multiple_short(no_go, "the", 0, 1 ) + ".\n");
}
if (sizeof(ear_muffed) > 0) {
write(capitalize(query_multiple_short(ear_muffed, "the", 0, 1)) +
" has teaching events earmuffed, you are unable "
"to teach them.\n");
}
if (sizeof(me_low) > 0) {
write("You are too low a level to teach " + comm + " to " +
query_multiple_short(me_low, "the", 0, 1) + ".\n");
}
if (sizeof(him_low) > 0) {
write(capitalize(query_multiple_short(him_low, "the", 0, 1)) +
((sizeof(him_low) == 1) ? " is" : " are") +" too low a "
"level to learn " + comm + ".\n");
}
} else {
tell_object(teacher,
"You automatically offer to teach " + comm + " to " +
query_multiple_short(succ) + ".\n");
}
say(capitalize((string)teacher->short()) + " teaches something to " +
query_multiple_short(succ) + ".\n", succ + ({ teacher }));
for (i = 0; i < sizeof(succ); i++) {
tell_object(succ[i], capitalize(teacher->the_short()) +
" offers to teach " +
query_multiple_short(delete(succ, i, 1) +
({ "you" }) ) + " the command \"" + comm + "\".\n"
"Type \"learn " + comm + " from " +
teacher->query_name() + "\" to learn the command.\n");
tmp_stuff = succ[i]->query_respond_command(TEACH_COMMAND_TYPE, teacher);
if (!tmp_stuff) {
tmp_stuff = ({ comm });
} else if (member_array(comm, tmp_stuff) == -1) {
tmp_stuff += ({ comm });
}
succ[i]->add_respond_command(TEACH_COMMAND_TYPE, teacher, tmp_stuff);
}
add_succeeded_mess("");
return 1;
} /* command_teach() */
int spell_teach(object *obs, string spell, object teacher) {
object *succ, *no_go, *me_low, *him_low, ob;
object *ignoring;
object *ignored;
object *ear_muffed;
string ret;
string is_are;
mapping spells;
mixed tmp;
spells = teacher->query_spells();
if (!spells[spell]) {
return 0;
}
if (teacher == this_player()) {
is_are = "are";
} else {
is_are = "is";
}
if (teacher->check_earmuffs("teach")) {
add_failed_mess("You have teaching earmuffed.\n");
return 0;
}
ear_muffed = succ = no_go = me_low = him_low = ({ });
ignored = ({ });
if (teacher->query_property("ignoring")) {
ignoring = filter(obs, (: check_ignoring($(teacher), $1) :));
obs -= ignoring;
}
foreach(ob in obs) {
if (living(ob)) {
if (ob->check_earmuffs("teach")) {
ear_muffed += ({ ob });
continue;
} else if (check_ignoring(ob, teacher)) {
ignored += ({ ob });
} else {
if (teacher == this_player()) {
tmp = ob;
} else {
tmp = "you";
}
switch (spells[spell][0]->teach(ob, spell)) {
case 1:
succ += ({ ob });
break;
case 0:
no_go += ({ ob });
break;
case -1:
me_low += ({ ob });
break;
case -2:
him_low += ({ ob });
break;
}
}
}
}
if (sizeof(succ) == 0) {
if (teacher != this_player()) {
tell_object(teacher, this_player()->the_short() + " tried to "
"learn " + spell +
" from you automatically, but failed.\n");
}
ret = "";
if (sizeof(no_go) > 0) {
add_failed_mess(teacher->the_short() +
" cannot teach " + spell + " to $I.\n", no_go);
}
if (sizeof(me_low) > 0) {
add_failed_mess(teacher->the_short() +
" " + is_are +
" too low a level to teach $I " + spell + ".\n",
me_low);
}
if (sizeof(him_low) > 0) {
add_failed_mess("$I " + is_are + " too low a level to learn " +
spell + ".\n", him_low);
}
if (sizeof(ignoring)) {
add_failed_mess("You are ignoring $I.\n", ignoring);
}
if (sizeof(ignored) > 0) {
add_failed_mess("You are being ignored by $I.\n", ignored);
}
if (sizeof(ignored) > 0) {
add_failed_mess("$I has teaching earmuffed.\n", ignored);
}
return 0;
}
if (teacher == this_player()) {
write(teacher->the_short() +
" successfuly teach " + query_multiple_short(succ) + " " +
spell + ".\n");
if (sizeof(no_go) > 0) {
write("You cannot teach " + spell + " to " +
query_multiple_short(no_go, "the", 0, 1) + ".\n");
}
if (sizeof(me_low) > 0) {
write("You are too low a level to teach " +
query_multiple_short(me_low, "the", 0, 1) + " " + spell +
".\n");
}
if (sizeof(him_low) > 0) {
write(capitalize(query_multiple_short(him_low, 0, 1)) +
(sizeof(him_low) == 1 ? " is " : " are ") +
"too low a level to learn " + spell + ".\n");
}
if (sizeof(ignoring) > 0) {
add_failed_mess("You are ignoring " +
query_multiple_short(ignoring, "the", 0, 1) +
".\n");
}
if (sizeof(ignored) > 0) {
add_failed_mess("You are being ignored by " +
query_multiple_short(ignored, "the", 0, 1) + ".\n");
}
} else {
write(teacher->the_short() +
" successfuly teaches " + query_multiple_short(succ) + " " +
spell + ".\n");
}
return 1;
} /* spell_teach() */
#define CANNOT 0
#define TOO_LOW 1
#define ONLY_LEAF 2
#define TOO_HIGH 3
#define CANNOT_TEACH 4
int teach_skill(object *obs, string str, object teacher) {
int num;
int lvl;
int my_lvl;
int lvl_up;
int j;
int cost;
int total;
int total2;
int sk;
int depth;
object *ok;
object *too_little;
mixed *too_low;
object *cannot_teach;
object *only_leaf;
object *ignored;
object *ignoring;
object *ear_muffed;
object *too_high;
object ob;
string skill;
string skill_start;
mixed *bits;
class teaching_skill frog;
float k;
mixed tmp;
num = 1;
if (sscanf(str, "%d levels of %s", num, skill) != 2) {
if (sscanf(str, "%d level of %s", num, skill) != 2) {
if (sscanf(str, "%d %s", num, skill) != 2) {
num = 1;
skill = str;
}
}
}
if (num < 1) {
add_failed_mess("You cannot teach a negative or zero number of "
"levels.\n");
return 0;
}
if (num > 50) {
add_failed_mess("You cannot teach more than 50 levels at a time.\n");
return 0;
}
if (teacher->check_earmuffs("teach")) {
add_failed_mess("You have teaching earmuffed.\n");
return 0;
}
/* Make sure its a valid skill */
skill_start = skill;
bits = explode(implode(explode(skill, " "), "."), ".") - ({ "" });
if (!bits || !(skill = (string)SKILL_OB->query_skill(bits))) {
if (member_array(skill_start, teacher->query_known_commands()) != -1) {
return 0;
}
add_failed_mess("The skill " + implode(bits, ".") + " is invalid.\n");
return 0;
}
bits = explode(skill, ".");
/*
* We don't do the teaching here. Figure out how much xp it will cost
* to go up the levels, and inform the person we are teaching...
*/
my_lvl = teacher->query_skill_bonus(skill, 1);
ear_muffed = ({ });
too_high = ({ });
only_leaf = ({ });
too_low = ({ });
ok = ({ });
too_little = ({ });
cannot_teach = ({ });
ignored = ({ });
if (teacher->query_property("ignoring")) {
ignoring = filter(obs, (: check_ignoring($(teacher), $1) :));
obs -= ignoring;
}
foreach (ob in obs) {
/*
* First make sure the skill can be taught. There are only restrictions
* (currently) on teaching of languages. You cannot teach yourself
* levels in a language. You have to go and research them.
*/
if (teacher == this_player()) {
tmp = ob;
} else {
tmp = "you";
}
if (!interactive(ob)) {
cannot_teach += ({ tmp });
continue;
}
if (!SKILL_OB->query_allowed_to_teach(skill)) {
cannot_teach += ({ tmp });
continue;
}
if (ob->check_earmuffs("teach")) {
ear_muffed += ({ tmp });
continue;
}
if (check_ignoring(ob, teacher)) {
ignored += ({ tmp });
continue;
}
if (SKILL_OB->query_only_leaf(skill) &&
sizeof((mixed *)SKILL_OB->query_immediate_children(skill)))
{
only_leaf += ({ tmp });
continue;
}
lvl = (int)ob->query_skill(skill);
if (ob != teacher) {
if ((int)ob->calc_bonus(lvl + num, skill, 1) > my_lvl) {
add_failed_mess(teacher->the_short() +
" cannot teach $I, since their bonus is " +
(teacher == this_player() ? "higher" : "lower") +
" than yours.\n", ({ tmp }));
continue;
}
#ifdef ENFORCE_MAX_TEACH_LEVEL
/*
* Check to see if your level, or the level being taught too is
* too high.
*/
if (lvl + num > SKILL_MAX_TEACH) {
too_high += ({ tmp });
continue;
}
#endif
}
if (sizeof(bits) > 1) {
lvl_up = ob->query_skill(implode(bits[0 .. sizeof(bits) - 2], "."));
} else {
lvl_up = lvl;
}
depth = SKILL_OB->query_skill_depth(bits);
if (!SKILL_OB->query_only_leaf(skill) && depth * 5 > lvl_up) {
too_low += ({ tmp });
continue;
}
total = 0;
total2 = 0;
foreach (sk in SKILL_OB->query_all_children(skill)) {
if (SKILL_OB->query_immediate_children(sk) != ({ })) {
continue;
}
my_lvl = teacher->query_skill_bonus(sk, 1);
lvl = (int)ob->query_skill(sk);
cost = DEFAULT_COST;
cost *= STD_COST / 5;
if (!my_lvl) {
my_lvl = 1;
}
for (j = 0; j < num; j++) {
k = 0.5 * (int)ob->calc_bonus(lvl + j, sk, 1 ) / my_lvl + 1.0;
total2 += 500 + to_int(cost * (lvl+j) * exp((lvl + j) / 500.0) * k);
if (total > total2) {
num = j;
break;
} else {
total = total2;
}
}
#ifdef DEBUG
tell_object(find_player(DEBUGGER),
sprintf("%s:%s j:%d, lvl: %d, mylvl: %d, newbonus: %d, "
"total: %d\n",
this_player()->query_name(),
ob->query_name(),
j, lvl,
my_lvl,
(int)ob->calc_bonus(lvl + j, sk, 1),
lvl,
total2));
#endif
if (!total) {
total = cost;
}
}
if (total > (int)ob->query_xp()) {
if (teacher == this_player()) {
add_failed_mess("It would have cost " + total + " xp to teach " +
num + " level" + (num > 1 ? "s" : "") + " of " +
skill + " to $I.\n", ({ ob }));
} else {
add_failed_mess("It would have cost " + total + " xp to teach " +
num + " level" + (num > 1 ? "s" : "") + " of " +
skill + " to you from " + teacher->the_short() +
".\n");
}
too_little += ({ tmp });
continue;
}
if (ob != teacher) {
if (teacher == this_player()) {
tell_object(ob, teacher->the_short() + " offers to teach "
"you " + num + " level" + (num > 1 ? "s" : "") +
" of " + skill + " for " + total + " xp.\nUse "
"\"learn\" to learn the skill.\n");
write("You offer to teach $M$$the_short:" + file_name(ob) +
"$$M$ " + num + " level" + (num > 1 ? "s" : "") + " of " +
skill + " for " + total + " xp.\n");
} else {
tell_object(ob, teacher->the_short() + " offers to teach "
"you " + num + " level" + (num > 1 ? "s" : "") +
" of " + skill + " for "+total+" xp.\nUse \"learn\" "
"to learn the skill.\n");
tell_object(teacher,
"You offer to teach $M$$the_short:" + file_name(ob) +
"$$M$ " + num + " level" + (num > 1 ? "s" : "") +
" of " + skill + " for " + total + " xp.\n");
}
} else {
write("You can teach yourself " + num + " level" +
(num > 1 ? "s" : "") + " of " + skill + " for " + total +
" xp.\nUse \"learn\" to learn the skill.\n");
write("You offer to teach $M$$the_short:" + file_name(ob) +
"$$M$ " + num + " level" + (num > 1 ? "s" : "") + " of " +
skill + " for " + total + " xp.\n");
}
ok += ({ tmp });
frog = new(class teaching_skill);
frog->num = num;
frog->lvl = ob->query_skill(skill);
frog->skill = skill;
frog->xp = total;
frog->room = environment(this_player());
ob->add_respond_command(TEACH_SKILL_TYPE, teacher, frog);
}
if (sizeof(ok) == 0) {
if (teacher != this_player()) {
tell_object(teacher,
this_player()->the_short() + " tried to learn " + skill +
" from you automatically but failed.\n");
}
if (sizeof(only_leaf) > 0) {
add_failed_mess("You cannot teach the skill " + skill +
", as it is only possible to teach leaf skills in "
"this skill tree.\n");
}
if (sizeof(ear_muffed) > 0) {
add_failed_mess("You cannot teach any levels of " + skill +
" to $I, they have teaching earmuffed and cannot "
"hear you.\n", ear_muffed);
}
if (sizeof(cannot_teach) > 0) {
add_failed_mess("You cannot teach any levels of " + skill +
"; you need to look for alternative methods of "
"advancement.\n");
}
if (sizeof(too_low) > 0) {
add_failed_mess(query_multiple_short(too_low, "the", 0, 1) +
((sizeof(too_low) > 1 ||
too_low[0] == teacher ||
teacher != this_player()) ? " are " : " is ") +
"not at a high enough level in the outer skills to "
"learn " + num + " levels of " + skill + ". See "
"'help skills' for more details.\n");
}
if (sizeof(too_high) > 0) {
add_failed_mess(query_multiple_short(too_low, "the", 0, 1) +
((sizeof(too_high) > 1 ||
teacher != this_player() ||
too_low[0] == teacher) ? " are " : " is ") +
"too high a level to learn " + num + " levels of " +
skill + ", they must be less than " +
SKILL_MAX_TEACH + " to learn from someone else.\n");
}
if (sizeof(ignoring) > 0) {
add_failed_mess("You are ignoring $I.\n", ignoring);
}
if (sizeof(ignored) > 0) {
add_failed_mess("You are being ignored by $I.\n", ignored);
}
}
return sizeof(ok);
} /* teach_skill() */
mixed *query_patterns() {
return ({
"<string:'skill|n levels of skill|command'> to <indirect:living>",
(: cmd($4[0], $1, 0) :),
"me <string:'skill|n levels of skill|command'> from <indirect:living>",
(: cmd($4[0], $1, this_player()) :)});
} /* query_patterns() */