--- deliantra/server/common/item.C 2009/09/16 22:50:50 1.55
+++ deliantra/server/common/item.C 2010/04/05 20:33:13 1.81
@@ -1,22 +1,23 @@
/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
*
- * Copyright (©) 2005,2006,2007,2008,2009 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
- * Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team
- * Copyright (©) 1992,2007 Frank Tore Johansen
+ * Copyright (©) 2005,2006,2007,2008,2009,2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
+ * Copyright (©) 2002 Mark Wedel & Crossfire Development Team
+ * Copyright (©) 1992 Frank Tore Johansen
*
- * Deliantra is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Deliantra is free software: you can redistribute it and/or modify it under
+ * the terms of the Affero GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * You should have received a copy of the Affero GNU General Public License
+ * and the GNU General Public License along with this program. If not, see
+ * .
*
* The authors can be reached via e-mail to
*/
@@ -48,6 +49,7 @@
* Basically, for the use/nonuse, the code does something like:
* "This item goes %s\n", with the use/nonuse values filling in the %s
*/
+// see include/object.h
Body_Locations body_locations[NUM_BODY_LOCATIONS] = {
{KW_body_skill , "You can use it as your skill" , "It is used as a skill"},
{KW_body_combat , "You can wield it as your weapon" , "It is used as a combat weapon"},
@@ -66,25 +68,19 @@
/*{"body_dragon_torso", "your body", "a dragon's body"} */
};
-static char numbers[21][20] = {
- "no", "", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
- "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen",
- "eighteen", "nineteen", "twenty"
-};
-
static char numbers_10[10][20] = {
"zero", "ten", "twenty", "thirty", "fourty", "fifty", "sixty", "seventy",
"eighty", "ninety"
};
-static char levelnumbers[21][20] = {
+static char ordnumbers[21][20] = {
"zeroth", "first", "second", "third", "fourth", "fifth", "sixth", "seventh",
"eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth",
- "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteen",
- "nineteen", "twentieth"
+ "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth",
+ "nineteenth", "twentieth"
};
-static char levelnumbers_10[11][20] = {
+static char ordnumbers_10[11][20] = {
"zeroth", "tenth", "twentieth", "thirtieth", "fortieth", "fiftieth", "sixtieth",
"seventieth", "eightieth", "ninetieth"
};
@@ -171,7 +167,7 @@
{SPINNER, "spinner", "spinners", 0, 0},
{GATE, "gate", "gates", 0, 0},
{BUTTON, "button", "buttons", 0, 0},
- {CF_HANDLE, "cf handle", "cf handles", 0, 0},
+ {T_HANDLE, "cf handle", "cf handles", 0, 0},
{HOLE, "hole", "holes", 0, 0},
{TRAPDOOR, "trapdoor", "trapdoors", 0, 0},
{SIGN, "sign", "signs", 0, 0},
@@ -208,32 +204,7 @@
{ITEM_TRANSFORMER, "item_transformer", "item_transformers", 0, 0},
};
-const int item_types_size = sizeof (item_types) / sizeof (*item_types);
-
-materialtype_t *materialt;
-
-/*
-materialtype material[NROFMATERIALS] = {
- * P M F E C C A D W G P S P T F C D D C C G H B I *
- * H A I L O O C R E H O L A U E A E E H O O O L N *
- * Y G R E L N I A A O I O R R A N P A A U D L I T *
- * S I E C D F D I P S S W A N R C L T O N Y N R *
- * I C T U N O T O L E E H S T P D N *
- {"paper", {15,10,17, 9, 5, 7,13, 0,20,15, 0,0,0,0,0,10,0,0,0,0,0,0,0,0}},
- {"metal", { 2,12, 3,12, 2,10, 7, 0,20,15, 0,0,0,0,0,10,0,0,0,0,0,0,0,0}},
- {"glass", {14,11, 8, 3,10, 5, 1, 0,20,15, 0,0,0,0,0, 0,0,0,0,0,0,0,0,0}},
- {"leather", { 5,10,10, 3, 3,10,10, 0,20,15, 0,0,0,0,0,12,0,0,0,0,0,0,0,0}},
- {"wood", {10,11,13, 2, 2,10, 9, 0,20,15, 0,0,0,0,0,12,0,0,0,0,0,0,0,0}},
- {"organics", { 3,12, 9,11, 3,10, 9, 0,20,15, 0,0,0,0,0, 0,0,0,0,0,0,0,0,0}},
- {"stone", { 2, 5, 2, 2, 2, 2, 1, 0,20,15, 0,0,0,0,0, 5,0,0,0,0,0,0,0,0}},
- {"cloth", {14,11,13, 4, 4, 5,10, 0,20,15, 0,0,0,0,0, 5,0,0,0,0,0,0,0,0}},
- {"adamant", { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0,0,0,0, 0,0,0,0,0,0,0,0,0}},
- {"liquid", { 0, 8, 9, 6,17, 0,15, 0,20,15,12,0,0,0,0,11,0,0,0,0,0,0,0,0}},
- {"soft metal",{ 6,12, 6,14, 2,10, 1, 0,20,15, 0,0,0,0,0,10,0,0,0,0,0,0,0,0}},
- {"bone", {10, 9, 4, 5, 3,10,10, 0,20,15, 0,0,0,0,0, 2,0,0,0,0,0,0,0,0}},
- {"ice", {14,11,16, 5, 0, 5, 6, 0,20,15, 0,0,0,0,0, 7,0,0,0,0,0,0,0,0}}
-};
-*/
+static const int item_types_size = sizeof (item_types) / sizeof (*item_types);
/* This curve may be too steep. But the point is that there should
* be tough choices - there is no real point to this if everyone can
@@ -320,14 +291,12 @@
/* Do spell paths now */
for (i = 1; i < NRSPELLPATHS; i++)
- {
- if (op->path_attuned & (1 << i))
- enc++;
- else if (op->path_denied & (1 << i))
- enc -= 2;
- else if (op->path_repelled & (1 << i))
- enc--;
- }
+ if (op->path_attuned & (1 << i))
+ enc++;
+ else if (op->path_denied & (1 << i))
+ enc -= 2;
+ else if (op->path_repelled & (1 << i))
+ enc--;
if (op->flag [FLAG_LIFESAVE ]) enc += 5;
if (op->flag [FLAG_REFL_SPELL ]) enc += 3;
@@ -382,28 +351,15 @@
const char *
describe_resistance (const object *op, int newline)
{
- static char buf[VERY_BIG_BUF];
- char buf1[VERY_BIG_BUF];
- int tmpvar;
-
- buf[0] = 0;
- for (tmpvar = 0; tmpvar < NROFATTACKS; tmpvar++)
- {
- if (op->resist[tmpvar] && (op->type != FLESH || atnr_is_dragon_enabled (tmpvar) == 1))
- {
- if (!newline)
- sprintf (buf1, "(%s %+d)", resist_plus[tmpvar], op->resist[tmpvar]);
- else
- sprintf (buf1, "%s %d\n", resist_plus[tmpvar], op->resist[tmpvar]);
+ static dynbuf_text buf; buf.clear ();
- strcat (buf, buf1);
- }
- }
+ for (int i = 0; i < NROFATTACKS; i++)
+ if (op->resist[i] && (op->type != FLESH || atnr_is_dragon_enabled (i) == 1))
+ buf.printf (newline ? "%s %d\n" : "(%s %+d)", resist_plus[i], op->resist[i]);
return buf;
}
-
/*
* query_weight(object) returns a character pointer to a static buffer
* containing the text-representation of the weight of the given object.
@@ -433,45 +389,29 @@
* the number requested (of the form first, second, third...)
*/
const char *
-get_levelnumber (int i)
+ordinal (int i)
{
- static char buf[MAX_BUF];
-
- if (i > 99)
- {
- sprintf (buf, "%d.", i);
- return buf;
- }
+ if (i < 0)
+ return format ("minus %s", ordinal (-i));
if (i < 21)
- return levelnumbers[i];
+ return ordnumbers[i];
- if (!(i % 10))
- return levelnumbers_10[i / 10];
+ int digit = i % 10;
- strcpy (buf, numbers_10[i / 10]);
- strcat (buf, levelnumbers[i % 10]);
- return buf;
-}
+ if (i >= 100)
+ return format (
+ digit == 1 ? "%dst"
+ : digit == 2 ? "%dnd"
+ : digit == 3 ? "%drd"
+ : "%dth",
+ i
+ );
-/*
- * get_number(integer) returns the text-representation of the given number
- * in a static buffer. The buffer might be overwritten at the next
- * call to get_number().
- * It is currently only used by the query_name() function.
- */
-const char *
-get_number (int i)
-{
- if (i <= 20)
- return numbers[i];
+ if (digit == 0)
+ return ordnumbers_10[i / 10];
else
- {
- static char buf[MAX_BUF];
-
- sprintf (buf, "%d", i);
- return buf;
- }
+ return format ("%s%s", numbers_10[i / 10], ordnumbers[i % 10]);
}
/*
@@ -487,7 +427,7 @@
/* Aug 95 modified this slightly so that Skill tools don't have magic bonus
* from stats.sp - b.t.
*/
-const char *
+static const char *
ring_desc (const object *op)
{
static dynbuf_text buf; buf.clear ();
@@ -628,6 +568,7 @@
*
* It is used extensively within messages, so should return only a prose
* and short description of the item.
+ * It is also used by examine/ex and similar functions.
*/
const char *
query_name (const object *op)
@@ -642,15 +583,16 @@
dynbuf_text &buf = bufs [use_buf];
buf.clear ();
- if ((op->is_armor () || op->is_weapon ()) && op->materialname)
- if (materialtype_t *mt = name_to_material (op->materialname))
- buf << mt->description << ' ';
+#if 0
+ if ((op->is_armor () || op->is_weapon ()) && op->material)
+ buf << op->material->description << ' ';
+#endif
buf << query_short_name (op);
if (QUERY_FLAG (op, FLAG_INV_LOCKED))
buf << " *";
- if (op->type == CONTAINER && ((op->env && op->env->container == op) || (!op->env && QUERY_FLAG (op, FLAG_APPLIED))))
+ if (op->is_open_container ())
buf << " (open)";
if (QUERY_FLAG (op, FLAG_KNOWN_CURSED))
@@ -690,10 +632,10 @@
case WAND:
case ROD:
case HORN:
- buf << (op->env && op->env->current_weapon == op ? " (readied)" : " (applied)");
+ buf << " (applied)";
break;
case WEAPON:
- buf << (op->env && op->env->current_weapon == op ? " (wielded)" : " (applied)");
+ buf << " (applied)";
break;
case ARMOUR:
case HELMET:
@@ -752,6 +694,7 @@
*
* It is sometimes used to display messages, and usually only used to match stuff,
* so maybe this function should be removed.
+ * It is also used for client-side inventory/item descriptions.
*/
const char *
query_base_name (const object *op, int plural)
@@ -759,15 +702,17 @@
if ((!plural && !op->name) || (plural && !op->name_pl))
return "(null)";
- if (!op->nrof && !op->weight && !op->title && !is_magical (op))
+ if (!op->nrof && !op->weight && !op->title && !is_magical (op)
+ && op->type != EXIT)
return op->name; /* To speed things up (or make things slower?) */
static dynbuf_text buf; buf.clear ();
- if ((op->is_armor () || op->is_weapon ()) && op->materialname)
- if (materialtype_t *mt = name_to_material (op->materialname))
- if (op->arch->materialname != mt->name)
- buf << mt->description << ' ';
+#if 0
+ if ((op->is_armor () || op->is_weapon ()) && op->material)
+ if (op->arch->material != op->material)
+ buf << op->material->description << ' ';
+#endif
buf << (plural ? op->name_pl : op->name);
@@ -804,6 +749,14 @@
}
break;
+ case EXIT:
+ // random map exits "unfortunately" get patched, so this only works before entering
+ if (EXIT_PATH (op) == shstr_random_map_exit)
+ buf << " (random map)";
+ else if (!EXIT_PATH (op))
+ buf << " (closed)";
+ break;
+
default:
if (op->magic && ((QUERY_FLAG (op, FLAG_BEEN_APPLIED) && need_identify (op)) || QUERY_FLAG (op, FLAG_IDENTIFIED)))
buf.printf (" %+d", op->magic);
@@ -828,7 +781,7 @@
* fall into the 'lightning fast movement' category.
*/
if (op->has_active_speed ())
- switch ((int) ((FABS (op->speed)) * 15))
+ switch ((int)(op->speed * 15.))
{
case 0:
buf << "(very slow movement)";
@@ -908,7 +861,7 @@
}
/* describe attacktypes */
- if (is_dragon_pl (op))
+ if (op->is_dragon ())
{
/* for dragon players display the attacktypes from clawing skill
* Break apart the for loop - move the comparison checking down -
@@ -1001,28 +954,30 @@
i = (op->stats.maxsp % 1000) / 100;
if (i)
- buf.printf ("(capacity %d.%dk). It is ", op->stats.maxsp / 1000, i);
+ buf.printf ("(capacity %d.%dk; it is ", op->stats.maxsp / 1000, i);
else
- buf.printf ("(capacity %dk). It is ", op->stats.maxsp / 1000);
+ buf.printf ("(capacity %dk; it is ", op->stats.maxsp / 1000);
}
else
- buf.printf ("(capacity %d). It is ", op->stats.maxsp);
+ buf.printf ("(capacity %d; it is ", op->stats.maxsp);
i = (op->stats.sp * 10) / op->stats.maxsp;
if (op->stats.sp == 0)
- buf << "empty.";
+ buf << "empty";
else if (i == 0)
- buf << "almost empty.";
+ buf << "almost empty";
else if (i < 3)
- buf << "partially filled.";
+ buf << "partially filled";
else if (i < 6)
- buf << "half full.";
+ buf << "half full";
else if (i < 9)
- buf << "well charged.";
+ buf << "well charged";
else if (op->stats.sp == op->stats.maxsp)
- buf << "fully charged.";
+ buf << "fully charged";
else
- buf << "almost full.";
+ buf << "almost full";
+
+ buf << ')';
break;
case LAMP:
@@ -1210,11 +1165,14 @@
if (op->slaying && op->type != FOOD)
buf.printf ("(slay %s)", &op->slaying);
+ if (op->type == SKILL_TOOL && op->skill)
+ buf.printf ("(%s)", &op->skill);
+
buf.add_abilities ("Attacks", op->attacktype);
/* resistance on flesh is only visible for quetzals. If
* non flesh, everyone can see its resistances
*/
- if (op->type != FLESH || (owner && is_dragon_pl (owner)))
+ if (op->type != FLESH || (owner && owner->is_dragon ()))
buf << describe_resistance (op, 0);
buf.add_paths ("Attuned", op->path_attuned);
@@ -1231,6 +1189,178 @@
return std::string (::describe_item (this, who));
}
+static void
+describe_dump_object (dynbuf &buf, object *ob)
+{
+ char *txt = dump_object (ob);
+ for (char *p = txt; *p; ++p) if (*p == '\n') *p = '\r';
+ buf << "\n" << txt << "\n";
+
+ if (!ob->is_arch ())
+ describe_dump_object (buf, ob->arch);
+}
+
+std::string
+object::describe (object *who)
+{
+ dynbuf_text buf (1024, 1024);
+
+ buf.printf ("That is: %s.\r", long_desc (who).c_str ());
+
+ if (custom_name)
+ buf.printf ("You call it %s.\r", &custom_name);
+
+ switch (type)
+ {
+ case SPELLBOOK:
+ if (flag [FLAG_IDENTIFIED] && inv)
+ buf.printf ("%s is a %s level %s spell.\r", &inv->name, ordinal (inv->level), &inv->skill);
+ break;
+
+ case BOOK:
+ if (msg)
+ buf << "Something is written in it.\r";
+ break;
+
+ case CONTAINER:
+ if (race)
+ {
+ if (weight_limit && stats.Str < 100)
+ buf.printf ("It can hold only %s and its weight limit is %.1f kg.\r",
+ &race, weight_limit / (10.0 * (100 - stats.Str)));
+ else
+ buf.printf ("It can hold only %s.\r", &race);
+ }
+ else if (weight_limit && stats.Str < 100)
+ buf.printf ("Its weight limit is %.1f kg.\r", weight_limit / (10.0 * (100 - stats.Str)));
+ break;
+
+ case WAND:
+ if (flag [FLAG_IDENTIFIED])
+ buf.printf ("It has %d %s left.\r", stats.food, stats.food == 1 ? "charge" : "charges");
+ break;
+ }
+
+ if (material != MATERIAL_NULL && !msg)
+ buf << (nrof > 1 ? "They are made of " : "It is made of ")
+ << material->description
+ << ".\r";
+
+ if (who)
+ /* Where to wear this item */
+ for (int i = 0; i < NUM_BODY_LOCATIONS; i++)
+ if (slot[i].info)
+ {
+ buf << (who->slot[i].info ? body_locations[i].use_name : body_locations[i].nonuse_name);
+
+ if (slot[i].info < -1 && who->slot[i].info)
+ buf.printf ("(%d)", -slot[i].info);
+
+ buf << ".\r";
+ }
+
+ if (weight)
+ buf.printf ("%s %3.3f kg.\r", nrof > 1 ? "They weigh" : "It weighs", weight * (nrof ? nrof : 1) / 1000.0);
+
+ if (flag [FLAG_STARTEQUIP])
+ buf << (nrof > 1 ? "They were" : "It was")
+ << " given by a god and will vanish when dropped.\r";
+
+ if (value && !flag [FLAG_STARTEQUIP] && !flag [FLAG_NO_PICK] && who)
+ {
+ buf.printf ("You reckon %s worth %s.\r", nrof > 1 ? "they are" : "it is", query_cost_string (this, who, F_TRUE | F_APPROX));
+
+ if (who->is_in_shop ())
+ {
+ if (flag [FLAG_UNPAID])
+ buf.printf ("%s would cost you %s.\r", nrof > 1 ? "They" : "It", query_cost_string (this, who, F_BUY | F_SHOP));
+ else
+ buf.printf ("You are offered %s for %s.\r", query_cost_string (this, who, F_SELL + F_SHOP), nrof > 1 ? "them" : "it");
+ }
+ }
+
+ if (flag [FLAG_MONSTER])
+ buf << describe_monster (who);
+
+ /* Is this item buildable? */
+ if (flag [FLAG_IS_BUILDABLE])
+ buf << "This is a buildable item.\r";
+
+ /* Does the object have a message? Don't show message for all object
+ * types - especially if the first entry is a match
+ */
+ if (msg)
+ {
+ if (type != EXIT && type != BOOK && type != CORPSE && !move_on && !has_dialogue ())
+ {
+ buf << '\r';
+
+ /* This is just a hack so when identifying the items, we print
+ * out the extra message
+ */
+ if (need_identify (this) && flag [FLAG_IDENTIFIED])
+ buf << "The object has a story:\r";
+
+ buf << msg << '\n';
+ }
+ }
+ else if (inv && inv->type == SPELL && flag [FLAG_IDENTIFIED]
+ && (type == SPELLBOOK || type == ROD || type == WAND
+ || type == ROD || type == POTION || type == SCROLL))
+ // for spellbooks and other stuff that contains spells, print the spell message,
+ // unless the object has a custom message handled above.
+ buf << '\r' << inv->msg << '\n';
+
+ // try to display the duration for some potions and scrolls
+ // this includes change ability potions and group spells,
+ // but does not handle protection potions
+ if (inv && inv->type == SPELL && flag [FLAG_IDENTIFIED]
+ && (type == POTION || type == SCROLL))
+ {
+ object *spell = inv;
+
+ if (spell->subtype == SP_PARTY_SPELL)
+ spell = spell->other_arch;
+
+ if (spell->subtype == SP_CHANGE_ABILITY)
+ buf.printf ("\nH",
+ TICK2TIME (change_ability_duration (spell, this)));
+ }
+
+ // some help text for skill tools
+ if (type == SKILL_TOOL)
+ buf << "\nH";
+
+ // Display a hint about inscribable items [empty books]
+ // This includes the amount of text they can hold.
+ if (type == INSCRIBABLE)
+ {
+ if (other_arch && other_arch->type == SCROLL)
+ buf.printf ("\nH");
+ else
+ buf.printf ("\nH",
+ weight_limit);
+ }
+
+ buf << '\n';
+
+ // the dungeon master additionally gets a complete dump
+ if (who && who->flag [FLAG_WIZLOOK])
+ {
+ buf << "\nT