blob: 2ac7bd21192aab32a0021bb6c127f18f9bd1ca92 [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{
Arathornb3051452021-05-13 21:13:03 +0200135 string text=regreplace(item->QueryProp(P_SHORT)
Arathorn74cbee22018-01-05 23:44:25 +0100136 ||item->name(RAW)
MG Mud User88f12472016-06-24 23:31:02 +0200137 ||"<?>",
138 "^(Ein Paar|Ein|Eine|Der|Die|Das) ","",0);
139
140// try to shorten the name with the closure
141 if (sizeof(text) > 20)
142 return implode(map(explode(text, " "), abbreviate), " ");
143 else
144 return text;
145}
146
147// converts a mapping with characters and color info into a
148// text. data in the mapping is stored in a one-dimensional
149// order with the position as key (ypos is pos/80, xpos pos%80)
150// this setup has a huge advantage: combining several
151// graphics just takes a "+" operator, the rest is handled
152// by the game driver. freakin' fast, much better than doing an
153// iteration over one or more array in lpc.
154static string Mapping2ColoredText(mapping pic, object player)
155{
156 string text;
157 mapping configmap;
158 int i,j,color;
159
160 configmap=default_config+(player->QueryProp(P_INVMASTER_CONFIG)||([]));
161
162 text="";
163 color=0;
164 for (i=0;i<22;i++)
165 {
166 text+=bgcolors[configmap[8]];
167 for (j=0;j<78;j++)
168 {
169 if (pic[i*80+j,1]!=color)
170 {
171 color=pic[i*80+j,1];
172 text+=colors[configmap[color]];
173 }
174 text+=pic[i*80+j];
175 }
176 text+=ANSI_NORMAL+"\n";
177 color=0;
178 }
179 return text;
180}
181
182static string Mapping2PlainText(mapping pic)
183{
184 string text;
185 int i,j;
186
187 text="";
188
189 for (i=0;i<22;i++)
190 {
191 for (j=0;j<78;j++)
192 text+=pic[i*80+j];
193 text+="\n";
194 }
195 return text;
196}
197static void AddDescription(mapping pic, string type, object item)
198{
199 int indentx, indenty, i;
200 string text;
201
202 switch(type)
203 {
204 case AT_HELMET:
205 indentx=47;
206 indenty=0;
207 text=sprintf("%-30s",ComposeDesc(item)[0..30]);break;
208 case AT_QUIVER:
209 indentx=49;
210 indenty=2;
211 text=sprintf("%-28s",ComposeDesc(item)[0..28]);break;
212 case AT_AMULET:
213 indentx=49;
214 indenty=4;
215 text=sprintf("%-27s",ComposeDesc(item)[0..28]);break;
216 case AT_ARMOUR:
217 indentx=53;
218 indenty=7;
219 text=sprintf("%-24s",ComposeDesc(item)[0..25]);break;
220 case AT_SHIELD:
221 indentx=54;
222 indenty=10;
223 text=sprintf("%-20s",ComposeDesc(item)[0..24]);break;
224 case AT_CLOAK:
225 indentx=53;
226 indenty=15;
227 text=sprintf("%-20s",ComposeDesc(item)[0..25]);break;
228 case AT_TROUSERS:
229 indentx=49;
230 indenty=17;
231 text=sprintf("%-20s",ComposeDesc(item)[0..20]);break;
232 case AT_RING:
233 indentx=0;
234 indenty=9;
235 text=sprintf("%14s",ComposeDesc(item)[0..17]);break;
236 case AT_GLOVE:
237 indentx=0;
238 indenty=11;
239 text=sprintf("%14s",ComposeDesc(item)[0..17]);break;
240 case AT_BELT:
241 indentx=1;
242 indenty=13;
243 text=sprintf("%14s",ComposeDesc(item)[0..18]);break;
244 case AT_BOOT:
245 indentx=1;
246 indenty=20;
247 text=sprintf("%18s",ComposeDesc(item)[0..18]);break;
248 case "Waffe":
249 indentx=1;
250 indenty=1;
251 text=sprintf("%18s",ComposeDesc(item)[0..25]);
252 if (item->QueryProp(P_NR_HANDS) > 1 &&
253 this_player() &&
254 !(this_player()->QueryArmorByType(AT_SHIELD)))
255 AddDescription(pic, AT_SHIELD, item);break;
256 default: return;
257 }
258 for (i=0;i<sizeof(text);i++)
259 pic+=([(80*indenty+indentx+i):text[i..i];2]);
260}
261
262varargs static void ConfigureColors(string text)
263{
264 mapping config, display;
265 string *strs;
266
267 if (!objectp(this_player())) return;
268
269 if (this_player()->InFight())
270 {
271 write(break_string(
272 "Im Kampf? Na Du hast Nerven, das lassen wir doch mal lieber! "
273 "Probier es danach nochmal...", 78));
274 return;
275 }
276
277 if (stringp(text)) text=lower_case(text);
278
279 if (text=="ok")
280 {
281 write("Farbkonfiguration beendet.\n");
282 return;
283 }
284
285 //"ansi_config", def in invmaster.h
286 config=this_player()->QueryProp(P_INVMASTER_CONFIG)||([]);
287 display=default_config+config;
288
289 if (!text || text=="")
290 {
291 write(
292 "*** Farbkonfiguration fuer den Ausruestungsbefehl ***\n\n"
293 " Farbe: wird dargestellt mit:\n"
294 "------------------ --------------------\n"
295 " Hintergrund "+COLORNAMES[display[8]]+"\n"
296 " Schwarz "+COLORNAMES[display[0]]+"\n"
297 " Rot "+COLORNAMES[display[1]]+"\n"
298 " Gruen "+COLORNAMES[display[2]]+"\n"
299 " Gelb "+COLORNAMES[display[3]]+"\n"
300 " Blau "+COLORNAMES[display[4]]+"\n"
301 " Magenta "+COLORNAMES[display[5]]+"\n"
302 " Tuerkis "+COLORNAMES[display[6]]+"\n"
303 " Weiss "+COLORNAMES[display[7]]+"\n\n"
304 "Farbe aendern mit '<farbe> <gewuenschte farbe>'.\n"
305 "Beispiel: 'gelb rot'.\n"
306 "Alles, was standardmaessig gelb waere, wuerde dann mit der ANSI-Farbe \n"
307 "Rot dargestellt.\n"
308 "Der Hintergrund kann zusaetzlich die Farbe 'keine' haben, bei der der \n"
309 "Hintergrund eben ueberhaupt nicht gefaerbt wird.\n"
310 "Beispiel: 'hintergrund keine'. Schaltet die Hintergrundfarbe aus.\n\n"
311 "Beenden mit 'ok'. \n"
312 "Wiederholung der Farbliste mit <Return>.\n"
313 "Farbliste auf Standard zuruecksetzen mit 'reset'.\n");
314 }
315 else
316 if (text=="reset")
317 {
318 this_player()->Set(P_INVMASTER_CONFIG, SAVE, F_MODE_AD);
319 this_player()->SetProp(P_INVMASTER_CONFIG, 0);
320 write("Farben zurueckgesetzt!\n");
321 }
322 else
323 {
324 if ( sizeof(strs=explode(text, " ")-({""})) !=2
325 || !member((COLORCODES-(["keine"])), strs[0])
326 || !member((COLORCODES-(["hintergrund"])), strs[1])
327 || ((strs[0]!="hintergrund") && (strs[1]=="keine")) )
328 {
329 write("Falsche Eingabe.\n"
330 "Format: <farbe|hintergrund> <zugewiesene Farbe>\n"
331 "Abbrechen mit 'ok'.\n");
332 }
333 else
334 {
335 if (COLORCODES[strs[1]]==default_config[COLORCODES[strs[0]]])
336 config-=([COLORCODES[strs[0]]]);
337 else
338 config+=([COLORCODES[strs[0]]:COLORCODES[strs[1]]]);
339 if (!sizeof(config))
340 {
341 this_player()->Set(P_INVMASTER_CONFIG, SAVE, F_MODE_AD);
342 this_player()->SetProp(P_INVMASTER_CONFIG, 0);
343 }
344 else
345 {
346 this_player()->SetProp(P_INVMASTER_CONFIG, deep_copy(config));
347 this_player()->Set(P_INVMASTER_CONFIG, SAVE, F_MODE_AS);
348 }
349 write("Ok, Farbe gewaehlt!\n");
350 }
351 }
352 input_to("ConfigureColors", INPUT_PROMPT, "\nEingabe: ");
353}
354
355
356string* armour_order=({
357 AT_HELMET, AT_AMULET, AT_QUIVER, AT_ARMOUR, AT_CLOAK,
358 AT_GLOVE, AT_RING, AT_BELT,
359 AT_TROUSERS, AT_BOOT, AT_SHIELD, AT_MISC});
360
361mapping weapon_names=([
362 WT_SPEAR : "Speer",
363 WT_SWORD : "Schwert",
364 WT_STAFF : "Kampfstab",
365 WT_WHIP : "Peitsche",
366 WT_CLUB : "Keule",
367 WT_KNIFE : "Messer",
368 WT_MISC : "Irgendwas",
369 WT_MAGIC : "Artefakt",
370 WT_AXE : "Axt",
Arathorn74cbee22018-01-05 23:44:25 +0100371 WT_RANGED_WEAPON : "Fernwaffe"
MG Mud User88f12472016-06-24 23:31:02 +0200372 ]);
373
374string SimpleInv(object player) {
375 object* armours=player->QueryProp(P_ARMOURS);
376 int count=sizeof(armour_order);
377 string* list=allocate(count);
378 string result="Ausruestung\n";
379 int i;
380
381 foreach(object ob: armours) {
382 if (!objectp(ob)) continue;
383 int idx = member(armour_order, ob->QueryProp(P_ARMOUR_TYPE));
384 if (idx>=0)
Arathorn74cbee22018-01-05 23:44:25 +0100385 list[idx]=ob->QueryProp(P_SHORT)||ob->name(RAW);
MG Mud User88f12472016-06-24 23:31:02 +0200386 }
387
388 // AT_MISC (letztes Element in list und armour_order) weglassen.
389 for (i=0;i<count-1;i++) {
390 result+=sprintf("%-20s %-57s\n",armour_order[i],list[i] || "");
391 }
392
Arathorn5d0ed1e2019-11-26 22:06:28 +0100393 object ob=player->QueryProp(P_WEAPON);
Arathorn74cbee22018-01-05 23:44:25 +0100394 if (objectp(ob))
395 {
396 string waffentyp;
397 if ( ob->QueryProp(P_WEAPON_TYPE)!=WT_HANDS )
398 {
399 waffentyp = (ob->QueryProp(P_NR_HANDS)==1 ? "Einhand-":"Zweihand-")
400 +weapon_names[ob->QueryProp(P_WEAPON_TYPE)];
401 }
402 else
403 {
404 waffentyp = "Waffe";
405 }
406 result+=sprintf("%-20s %-57s\n", waffentyp,
407 ob->QueryProp(P_SHORT)||ob->name(RAW));
408 }
409 else
410 result+="Keine Waffe\n";
MG Mud User88f12472016-06-24 23:31:02 +0200411
412 return result;
413}
414// the main function called by the player object.
415// determines gender, then adds armor/weapon graphics
416// dynamically. still very fast due to the use of the "+" operator,
417// see above.
418void ShowInv(object player, string arg)
419{
420 string gender, type;
421 mapping pic;
422 int i;
423 object item;
424
425 if (!objectp(player)||!interactive(player)) return;
426
427 // split args.
428 string *args;
429 if (stringp(arg))
430 args = explode(lower_case(arg), " ") - ({" "});
431 else
432 args = ({});
433
434 if (member(args, "farben") > -1) {
435 ConfigureColors();
436 return;
437 }
438
439 if (member(args, "-k") > -1 || player->QueryProp(P_NO_ASCII_ART)) {
440 tell_object(player, SimpleInv(player));
441 return;
442 }
443
444 gender=player->QueryProp(P_GENDER)==FEMALE?"_female":"_male";
445 pic=deep_copy(data["base"+gender]);
446 pic+=data["Beschriftung"];
447 for (i=sizeof(armour_types)-1;i>=0;i--)
448 if (objectp(item=player->QueryArmourByType(armour_types[i])))
449 {
450 pic+=data[armour_types[i]+gender];
451 AddDescription(pic, armour_types[i], item);
452 }
453 if (item=player->QueryProp(P_WEAPON))
454 {
455 pic+=data[(VALID_WEAPON_TYPE(type=item->QueryProp(P_WEAPON_TYPE)))?
456 type:WT_MISC];
457 AddDescription(pic, "Waffe", item);
458 }
459 if (player->QueryProp(P_TTY)!="ansi")
460 player->More(Mapping2PlainText(pic));
461 else
462 player->More(Mapping2ColoredText(pic, player));
463 DB(geteuid(player)+" eval cost: "+(1000000-get_eval_cost())+" ticks.\n");
464}
465