blob: 49c3c38a9bcfa420eea86e78b5e240d825063d0f [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// invmaster.c by Nachtwind@MG V1.1 (5.3.2001)
Zesstra1c7da1d2019-10-24 23:01:15 +02002// A small master that provides a graphical display of the player´s
MG Mud User88f12472016-06-24 23:31:02 +02003// equipment.
4#pragma strong_types
5#pragma save_types,rtt_checks
6#pragma range_check
7#pragma no_clone
MG Mud User88f12472016-06-24 23:31:02 +02008
9#include <input_to.h>
10#include <properties.h>
11#include <ansi.h>
12#include <combat.h>
13#include <language.h>
14#include "invmaster.h"
15
16
17mapping data;
18closure abbreviate;
19
20
21// i'm aware this can be determined with m_indices(VALID_ARMOUR_TYPE),
22// but the position in the arrays is important for the drawing order.
23// first item in the array is drawn last
24static string *armour_types =
25({AT_BELT,
26 AT_SHIELD,
27 AT_HELMET,
28 AT_BOOT,
29 AT_TROUSERS,
30 AT_AMULET,
31 AT_RING,
32 AT_GLOVE,
33 AT_QUIVER,
34 AT_CLOAK,
35 AT_ARMOUR});
36
37static mapping colors =
38([0:ANSI_BLACK,
39 1:ANSI_RED,
40 2:ANSI_GREEN,
41 3:ANSI_YELLOW,
42 4:ANSI_BLUE,
43 5:ANSI_PURPLE,
44 6:ANSI_CYAN,
45 7:ANSI_WHITE,
46 8:""]);
47
48static mapping bgcolors =
49([0:ANSI_BG_BLACK,
50 1:ANSI_BG_RED,
51 2:ANSI_BG_GREEN,
52 3:ANSI_BG_YELLOW,
53 4:ANSI_BG_BLUE,
54 5:ANSI_BG_PURPLE,
55 6:ANSI_BG_CYAN,
56 7:ANSI_BG_WHITE,
57 8:""]);
58
59
60
61static string Mapping2ColoredText(mapping pic, object player);
62static string Mapping2PlainText(mapping pic);
63static void AddDescription(mapping pic, string type, object item);
64static string ComposeDesc(object item);
65static void ConfigureColors(string text);
66
67void ShowInv(object player, string arg);
68
Zesstra1c7da1d2019-10-24 23:01:15 +020069// ok, let´s just read in the graphics...
MG Mud User88f12472016-06-24 23:31:02 +020070// really takes some time (~250 eval ticks) but is only done
71// once in an uptime
72void create()
73{
74 mapping pic;
75 string *files, *lines, text;
76 int i,j,k, indentx,indenty, color;
77
78 data=([]);
79
80 DB("Trying to fire up master, path is '"+INVPATH+"'...");
81 files=get_dir(INVPATH+"gfx/*")-({".", ".."});
82 DB(sprintf("Files found in 'gfx/': \n%O", files));
83 for (i=sizeof(files)-1;i>=0;i--)
84 {
85 DB("Reading '"+files[i]+"' ...");
86 text=read_file(INVPATH+"gfx/"+files[i]);
87 if (!stringp(text))
88 {
89 DB("Failed to read file.");
90 continue;
91 }
92 lines=explode(text, "\n");
93 if (sizeof(lines) < 4)
94 {
95 DB("File corrupt.");
96 continue;
97 }
98 indentx=to_int(lines[1]);
99 indenty=to_int(lines[2]);
100 color=to_int(lines[0]);
101 pic=([]);
102 for (j=sizeof(lines)-1;j>2;j--)
103 {
104 for (k=sizeof(lines[j])-1;k>=0;k--)
105 {
106 if (lines[j][k..k]!="?")
107 pic+=([(j-3+indenty)*80+k+indentx:lines[j][k..k];color]);
108 }
109 }
110 data+=([files[i]:pic]);
111 DB("File successfully read.");
112 }
113 DB(sprintf("Types covered:\n%O\n", m_indices(data)));
114
115 // create closure only once to save time
116 // needed by ComposeDesc()
117 // the closure ist not as complicated as it seems ;)
118 // it just checks every word of the name, if it does not begin
119 // with a capital letter, it is abbreviated
120 // this happens only if the name length exceeds 20 chars...
Arathorndc28afc2018-11-26 22:20:59 +0100121 abbreviate = function string (string x) {
122 if ( member(({"der","des"}), x) > 0 )
123 return "d.";
124 else if ( sizeof(x) > 3)
125 return regreplace(x, "^[a-z].+", x[0..0]+".", 0x1);
126 else
127 return x;
128 };
MG Mud User88f12472016-06-24 23:31:02 +0200129}
130
131// function that tries to guess a good item name and use abbrevations
132// where possible
133static string ComposeDesc(object item)
134{
135 int i;
136 string text, *buff;
137
138 text=regreplace(item->QueryProp(P_SHORT)
Arathorn74cbee22018-01-05 23:44:25 +0100139 ||item->name(RAW)
MG Mud User88f12472016-06-24 23:31:02 +0200140 ||"<?>",
141 "^(Ein Paar|Ein|Eine|Der|Die|Das) ","",0);
142
143// try to shorten the name with the closure
144 if (sizeof(text) > 20)
145 return implode(map(explode(text, " "), abbreviate), " ");
146 else
147 return text;
148}
149
150// converts a mapping with characters and color info into a
151// text. data in the mapping is stored in a one-dimensional
152// order with the position as key (ypos is pos/80, xpos pos%80)
153// this setup has a huge advantage: combining several
154// graphics just takes a "+" operator, the rest is handled
155// by the game driver. freakin' fast, much better than doing an
156// iteration over one or more array in lpc.
157static string Mapping2ColoredText(mapping pic, object player)
158{
159 string text;
160 mapping configmap;
161 int i,j,color;
162
163 configmap=default_config+(player->QueryProp(P_INVMASTER_CONFIG)||([]));
164
165 text="";
166 color=0;
167 for (i=0;i<22;i++)
168 {
169 text+=bgcolors[configmap[8]];
170 for (j=0;j<78;j++)
171 {
172 if (pic[i*80+j,1]!=color)
173 {
174 color=pic[i*80+j,1];
175 text+=colors[configmap[color]];
176 }
177 text+=pic[i*80+j];
178 }
179 text+=ANSI_NORMAL+"\n";
180 color=0;
181 }
182 return text;
183}
184
185static string Mapping2PlainText(mapping pic)
186{
187 string text;
188 int i,j;
189
190 text="";
191
192 for (i=0;i<22;i++)
193 {
194 for (j=0;j<78;j++)
195 text+=pic[i*80+j];
196 text+="\n";
197 }
198 return text;
199}
200static void AddDescription(mapping pic, string type, object item)
201{
202 int indentx, indenty, i;
203 string text;
204
205 switch(type)
206 {
207 case AT_HELMET:
208 indentx=47;
209 indenty=0;
210 text=sprintf("%-30s",ComposeDesc(item)[0..30]);break;
211 case AT_QUIVER:
212 indentx=49;
213 indenty=2;
214 text=sprintf("%-28s",ComposeDesc(item)[0..28]);break;
215 case AT_AMULET:
216 indentx=49;
217 indenty=4;
218 text=sprintf("%-27s",ComposeDesc(item)[0..28]);break;
219 case AT_ARMOUR:
220 indentx=53;
221 indenty=7;
222 text=sprintf("%-24s",ComposeDesc(item)[0..25]);break;
223 case AT_SHIELD:
224 indentx=54;
225 indenty=10;
226 text=sprintf("%-20s",ComposeDesc(item)[0..24]);break;
227 case AT_CLOAK:
228 indentx=53;
229 indenty=15;
230 text=sprintf("%-20s",ComposeDesc(item)[0..25]);break;
231 case AT_TROUSERS:
232 indentx=49;
233 indenty=17;
234 text=sprintf("%-20s",ComposeDesc(item)[0..20]);break;
235 case AT_RING:
236 indentx=0;
237 indenty=9;
238 text=sprintf("%14s",ComposeDesc(item)[0..17]);break;
239 case AT_GLOVE:
240 indentx=0;
241 indenty=11;
242 text=sprintf("%14s",ComposeDesc(item)[0..17]);break;
243 case AT_BELT:
244 indentx=1;
245 indenty=13;
246 text=sprintf("%14s",ComposeDesc(item)[0..18]);break;
247 case AT_BOOT:
248 indentx=1;
249 indenty=20;
250 text=sprintf("%18s",ComposeDesc(item)[0..18]);break;
251 case "Waffe":
252 indentx=1;
253 indenty=1;
254 text=sprintf("%18s",ComposeDesc(item)[0..25]);
255 if (item->QueryProp(P_NR_HANDS) > 1 &&
256 this_player() &&
257 !(this_player()->QueryArmorByType(AT_SHIELD)))
258 AddDescription(pic, AT_SHIELD, item);break;
259 default: return;
260 }
261 for (i=0;i<sizeof(text);i++)
262 pic+=([(80*indenty+indentx+i):text[i..i];2]);
263}
264
265varargs static void ConfigureColors(string text)
266{
267 mapping config, display;
268 string *strs;
269
270 if (!objectp(this_player())) return;
271
272 if (this_player()->InFight())
273 {
274 write(break_string(
275 "Im Kampf? Na Du hast Nerven, das lassen wir doch mal lieber! "
276 "Probier es danach nochmal...", 78));
277 return;
278 }
279
280 if (stringp(text)) text=lower_case(text);
281
282 if (text=="ok")
283 {
284 write("Farbkonfiguration beendet.\n");
285 return;
286 }
287
288 //"ansi_config", def in invmaster.h
289 config=this_player()->QueryProp(P_INVMASTER_CONFIG)||([]);
290 display=default_config+config;
291
292 if (!text || text=="")
293 {
294 write(
295 "*** Farbkonfiguration fuer den Ausruestungsbefehl ***\n\n"
296 " Farbe: wird dargestellt mit:\n"
297 "------------------ --------------------\n"
298 " Hintergrund "+COLORNAMES[display[8]]+"\n"
299 " Schwarz "+COLORNAMES[display[0]]+"\n"
300 " Rot "+COLORNAMES[display[1]]+"\n"
301 " Gruen "+COLORNAMES[display[2]]+"\n"
302 " Gelb "+COLORNAMES[display[3]]+"\n"
303 " Blau "+COLORNAMES[display[4]]+"\n"
304 " Magenta "+COLORNAMES[display[5]]+"\n"
305 " Tuerkis "+COLORNAMES[display[6]]+"\n"
306 " Weiss "+COLORNAMES[display[7]]+"\n\n"
307 "Farbe aendern mit '<farbe> <gewuenschte farbe>'.\n"
308 "Beispiel: 'gelb rot'.\n"
309 "Alles, was standardmaessig gelb waere, wuerde dann mit der ANSI-Farbe \n"
310 "Rot dargestellt.\n"
311 "Der Hintergrund kann zusaetzlich die Farbe 'keine' haben, bei der der \n"
312 "Hintergrund eben ueberhaupt nicht gefaerbt wird.\n"
313 "Beispiel: 'hintergrund keine'. Schaltet die Hintergrundfarbe aus.\n\n"
314 "Beenden mit 'ok'. \n"
315 "Wiederholung der Farbliste mit <Return>.\n"
316 "Farbliste auf Standard zuruecksetzen mit 'reset'.\n");
317 }
318 else
319 if (text=="reset")
320 {
321 this_player()->Set(P_INVMASTER_CONFIG, SAVE, F_MODE_AD);
322 this_player()->SetProp(P_INVMASTER_CONFIG, 0);
323 write("Farben zurueckgesetzt!\n");
324 }
325 else
326 {
327 if ( sizeof(strs=explode(text, " ")-({""})) !=2
328 || !member((COLORCODES-(["keine"])), strs[0])
329 || !member((COLORCODES-(["hintergrund"])), strs[1])
330 || ((strs[0]!="hintergrund") && (strs[1]=="keine")) )
331 {
332 write("Falsche Eingabe.\n"
333 "Format: <farbe|hintergrund> <zugewiesene Farbe>\n"
334 "Abbrechen mit 'ok'.\n");
335 }
336 else
337 {
338 if (COLORCODES[strs[1]]==default_config[COLORCODES[strs[0]]])
339 config-=([COLORCODES[strs[0]]]);
340 else
341 config+=([COLORCODES[strs[0]]:COLORCODES[strs[1]]]);
342 if (!sizeof(config))
343 {
344 this_player()->Set(P_INVMASTER_CONFIG, SAVE, F_MODE_AD);
345 this_player()->SetProp(P_INVMASTER_CONFIG, 0);
346 }
347 else
348 {
349 this_player()->SetProp(P_INVMASTER_CONFIG, deep_copy(config));
350 this_player()->Set(P_INVMASTER_CONFIG, SAVE, F_MODE_AS);
351 }
352 write("Ok, Farbe gewaehlt!\n");
353 }
354 }
355 input_to("ConfigureColors", INPUT_PROMPT, "\nEingabe: ");
356}
357
358
359string* armour_order=({
360 AT_HELMET, AT_AMULET, AT_QUIVER, AT_ARMOUR, AT_CLOAK,
361 AT_GLOVE, AT_RING, AT_BELT,
362 AT_TROUSERS, AT_BOOT, AT_SHIELD, AT_MISC});
363
364mapping weapon_names=([
365 WT_SPEAR : "Speer",
366 WT_SWORD : "Schwert",
367 WT_STAFF : "Kampfstab",
368 WT_WHIP : "Peitsche",
369 WT_CLUB : "Keule",
370 WT_KNIFE : "Messer",
371 WT_MISC : "Irgendwas",
372 WT_MAGIC : "Artefakt",
373 WT_AXE : "Axt",
Arathorn74cbee22018-01-05 23:44:25 +0100374 WT_RANGED_WEAPON : "Fernwaffe"
MG Mud User88f12472016-06-24 23:31:02 +0200375 ]);
376
377string SimpleInv(object player) {
378 object* armours=player->QueryProp(P_ARMOURS);
379 int count=sizeof(armour_order);
380 string* list=allocate(count);
381 string result="Ausruestung\n";
382 int i;
383
384 foreach(object ob: armours) {
385 if (!objectp(ob)) continue;
386 int idx = member(armour_order, ob->QueryProp(P_ARMOUR_TYPE));
387 if (idx>=0)
Arathorn74cbee22018-01-05 23:44:25 +0100388 list[idx]=ob->QueryProp(P_SHORT)||ob->name(RAW);
MG Mud User88f12472016-06-24 23:31:02 +0200389 }
390
391 // AT_MISC (letztes Element in list und armour_order) weglassen.
392 for (i=0;i<count-1;i++) {
393 result+=sprintf("%-20s %-57s\n",armour_order[i],list[i] || "");
394 }
395
Arathorn5d0ed1e2019-11-26 22:06:28 +0100396 object ob=player->QueryProp(P_WEAPON);
Arathorn74cbee22018-01-05 23:44:25 +0100397 if (objectp(ob))
398 {
399 string waffentyp;
400 if ( ob->QueryProp(P_WEAPON_TYPE)!=WT_HANDS )
401 {
402 waffentyp = (ob->QueryProp(P_NR_HANDS)==1 ? "Einhand-":"Zweihand-")
403 +weapon_names[ob->QueryProp(P_WEAPON_TYPE)];
404 }
405 else
406 {
407 waffentyp = "Waffe";
408 }
409 result+=sprintf("%-20s %-57s\n", waffentyp,
410 ob->QueryProp(P_SHORT)||ob->name(RAW));
411 }
412 else
413 result+="Keine Waffe\n";
MG Mud User88f12472016-06-24 23:31:02 +0200414
415 return result;
416}
417// the main function called by the player object.
418// determines gender, then adds armor/weapon graphics
419// dynamically. still very fast due to the use of the "+" operator,
420// see above.
421void ShowInv(object player, string arg)
422{
423 string gender, type;
424 mapping pic;
425 int i;
426 object item;
427
428 if (!objectp(player)||!interactive(player)) return;
429
430 // split args.
431 string *args;
432 if (stringp(arg))
433 args = explode(lower_case(arg), " ") - ({" "});
434 else
435 args = ({});
436
437 if (member(args, "farben") > -1) {
438 ConfigureColors();
439 return;
440 }
441
442 if (member(args, "-k") > -1 || player->QueryProp(P_NO_ASCII_ART)) {
443 tell_object(player, SimpleInv(player));
444 return;
445 }
446
447 gender=player->QueryProp(P_GENDER)==FEMALE?"_female":"_male";
448 pic=deep_copy(data["base"+gender]);
449 pic+=data["Beschriftung"];
450 for (i=sizeof(armour_types)-1;i>=0;i--)
451 if (objectp(item=player->QueryArmourByType(armour_types[i])))
452 {
453 pic+=data[armour_types[i]+gender];
454 AddDescription(pic, armour_types[i], item);
455 }
456 if (item=player->QueryProp(P_WEAPON))
457 {
458 pic+=data[(VALID_WEAPON_TYPE(type=item->QueryProp(P_WEAPON_TYPE)))?
459 type:WT_MISC];
460 AddDescription(pic, "Waffe", item);
461 }
462 if (player->QueryProp(P_TTY)!="ansi")
463 player->More(Mapping2PlainText(pic));
464 else
465 player->More(Mapping2ColoredText(pic, player));
466 DB(geteuid(player)+" eval cost: "+(1000000-get_eval_cost())+" ticks.\n");
467}
468