blob: c1681d52aac06af3042159f5a3e62a882faf14c0 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001#pragma strong_types
2#pragma save_types
MG Mud User88f12472016-06-24 23:31:02 +02003#pragma range_check
4#pragma no_clone
5
6inherit "/std/trading_price";
7
8#define NEED_PROTOTYPES
9#include <thing/commands.h>
10#include <thing/description.h>
11#undef NEED_PROTOTYPES
12
13#include <defines.h>
14#include <properties.h>
15#include <rooms.h>
16#include <language.h>
17#include <moving.h>
18#include <routingd.h>
19#include <bank.h>
20#include <combat.h>
21#include <input_to.h>
22#include <unit.h>
23#include <money.h>
24
25// TODO: pruefen, um die Variablen private sein koennen.
26
27// allgemein benoetige Variablen
28nosave string storage; // Filename des Stores in dem die Objekte lagern...
29nosave mapping ob_anz; // wie oft ist ein Objekt im Laden vorhanden?
30
31// Jetzt Variablen fuer staendig verfuegbare Objekte:
32nosave string *fixed_obj; // Liste der staendig verfuegbaren Objekte
33nosave mapping fixed_value; // Preise bei Sonderangeboten
34nosave mapping fixed_ids; // Ids
35
36varargs void AddFixedObject(string str, int val, string|string* ids)
37{
38 int i;
39
40 // Achtung, bei solchen Objekten muss die Blueprint initialisiert werden!
41 if (!str) return;
42 if (!val) val=str->QueryProp(P_VALUE);
43 if (!ids)
44 {
45 if (str->QueryProp("u_ids")) // units haben keine P_IDS
46 ids=str->QueryProp("u_ids")[0];
47 else
48 ids=str->QueryProp(P_IDS);
49 }
50 if (!pointerp(ids))
51 {
52 if (stringp(ids))
53 ids=({ids});
54 else
55 ids=({});
56 }
57
58 fixed_obj += ({str});
59 fixed_value[str] = val;
60 // Alle IDs entfernen, die Sonderzeichen enthalten. Die koennte ein Spieler
61 // bei "kaufe" ohnehin nicht eingeben.
62 ids -= regexp(ids, "[\b\n\r\t]");
63 foreach(string id : ids)
64 {
65 // Nur IDs aufnehmen, die keine Grossbuchstaben enthalten, da Spieler
66 // diese ebenfalls nicht eingeben koennte.
67 if ( lowerstring(id) == id )
68 fixed_ids[id]=str;
69 }
70}
71
72void RemoveFixedObject(string filename)
73{
74 if( !stringp(filename) || !sizeof(fixed_obj))
75 return;
76 if( member(fixed_obj, filename)==-1 )
77 return;
78
79 fixed_obj -= ({ filename });
80 m_delete(fixed_value, filename);
81
82 foreach(string id, string file : fixed_ids)
83 {
84 if ( file == filename )
85 m_delete(fixed_ids, id);
86 }
87}
88
89static string SetStorageRoom(string str)
90{
91 if (str && stringp(str)) return storage=str;
92 return 0;
93}
94
95string QueryStorageRoom()
96{ return storage; }
97
98protected void create()
99{
100 object router;
101
102 if (object_name(this_object()) == __FILE__[0..<3])
103 {
104 set_next_reset(-1);
105 return;
106 }
107
108 trading_price::create();
109
110 SetProp( P_NAME, "Haendler" );
111 SetProp( P_GENDER, MALE );
112 SetProp( P_ROOM_TYPE, QueryProp(P_ROOM_TYPE) | RT_SHOP );
113
114 AddCmd("zeige","list");
115 AddCmd(({"kauf","kaufe","bestell","bestelle"}),"buy");
116 AddCmd(({"verkauf","verkaufe","verk"}),"sell");
117 AddCmd(({"versetz","versetze"}),"force_sell");
118 AddCmd(({"schaetz","schaetze"}),"evaluate");
119 AddCmd(({"untersuche","unt"}), "show_obj");
120
121 SetTradingData(50000,300,3);
122
123 ob_anz=([]);
124 fixed_obj=({});fixed_value=([]);fixed_ids=([]);
125
126 AddFixedObject(BOERSE, 80,({ "boerse","geldboerse"}));
127
128 if (!clonep(ME) && objectp(router=find_object(ROUTER)))
129 router->RegisterTarget(TARGET_SHOP,object_name(ME));
130}
131
132protected void create_super() {
133 set_next_reset(-1);
134}
135
136// Legacy-Version von GetShopItems() fuer die erbenden Laeden, die auf
137// die Funktion in dieser Form angewiesen sind.
138static mixed *GetList()
139{
140 object store = load_object(storage);
141 store->_register_shop(ME);
142
143 mixed *output=({});
144 if (!objectp(store))
145 return output;
146
147 mixed tmp = map(fixed_obj, #'load_object)+all_inventory(store);
148 mapping tmp2 = ([]);
149 mixed str;
150 string comp;
151 int i;
152 int s=1;
153
154 for (i=sizeof(tmp)-1 ; i>=0 ; i--)
155 {
156 str = ({ ({ sprintf("%-25.25s%7.7d",
157 (tmp[i]->short()||"???")[0..<3],
158 QueryBuyValue(tmp[i], PL)),
159 tmp[i] }) });
160 comp=str[0][0][0..25];
161 if (!tmp2[comp])
162 {
163 tmp2[comp] = s++;
164 output += str;
165 }
166 else output[tmp2[comp]-1][0] = str[0][0];
167 }
168 return output;
169}
170
171// Legacy-Version von PrintList() fuer die erbenden Laeden, die auf
172// die Funktion in dieser Form angewiesen sind.
173static int DoList(string query_fun)
174{
175 mixed* output=GetList();
176 int si = sizeof(output);
177 if (!si)
178 {
179 write("Im Moment sind wir leider VOELLIG ausverkauft!\n");
180 return 1;
181 }
182 string out="";
183 int i;
184 int indent;
185 for (i=0; i<si; i++)
186 {
187 if (call_other(ME, query_fun, output[i][1]))
188 {
189 indent = !indent;
190 out += sprintf("%3d. %s", i+1, output[i][0]);
191 if (!indent)
192 out += "\n";
193 else out += " | ";
194 }
195 }
196 if (indent)
197 out+="\n";
198 PL->More(out);
199 return 1;
200}
201
202// Liefert eine Liste der Objekte zurueck, die gerade im Storage liegen,
203// pro Blueprint jeweils eins.
204protected object* GetShopItems()
205{
206 object store = load_object(storage);
207 store->_register_shop(ME);
208 object* output = ({});
209 object* all_items = all_inventory(store);
210
211 // Wir brauchen eine Liste, die von jeder Blueprint nur einen Clone
212 // enthaelt. Daher werden die Ladenamen der Objekte als Keys im Mapping
213 // <items> verwendet und jeweils der aktuelle Clone als Value zugeordnet.
214 mapping items = m_allocate(sizeof(all_items));
215 foreach(object ob: all_items)
216 {
217 items[load_name(ob)] = ob;
218 }
219 // Die Fixed Objects werden ans Ende angehaengt, damit in dem Fall, dass
220 // ein Clone eines solchen Objektes im Lager liegt, dieser zuerst verkauft
221 // wird und nicht immer wieder ein neuer erstellt wird.
222 return m_values(items) + map(fixed_obj, #'load_object);
223}
224
MG Mud User88f12472016-06-24 23:31:02 +0200225// Kuemmert sich um die Listenausgabe fuer den Befehl "zeige"
226varargs protected int PrintList(string filter_fun, int liststyle)
227{
228 // Alle Items im Lager holen. Wenn keine vorhanden, tut uns das leid.
229 object *items_in_store = GetShopItems();
230 if ( !sizeof(items_in_store) ) {
231 write("Im Moment sind wir leider VOELLIG ausverkauft!\n");
232 return 1;
233 }
234
235 // Das Listenformat ist von der Spielereingabe abhaengig. Wurde "lang"
236 // angefordert, geben wir einspaltig aus mit groesserer Spaltenbreite.
237 // Die Spaltenbreite wird dabei von dem Item mit dem laengsten Namen im
238 // gesamten Shop-Inventar bestimmt, damit nicht bei jeder Teilliste
239 // (Waffen, Ruestungen, Verschiedenes) unterschiedliche Breiten rauskommen.
240 //
241 // Der erste Parameter enthaelt die Katalognummer des Items, der zweite
242 // die Kurzbeschreibung, der dritte den Preis.
243 string listformat = "%3d. %-25.25s %6.6d";
244 if ( liststyle == LIST_LONG )
245 {
246 string* names = sort_array(
247 items_in_store->short(),
248 function int (string s1, string s2) {
249 return (sizeof(s1) < sizeof(s2));
250 });
251 // Wenn einspaltig ausgegeben wird, soll die Liste nicht beliebig breit
252 // werden. Daher wird die Short auf 65 Zeichen limitiert.
253 int len = 65;
254 if ( sizeof(names) )
255 len = min(len, sizeof(names[0]));
256 listformat = "%3d. %-"+len+"."+len+"s %6.6d";
257 }
258
259 string out="";
260 // Variablen, die innerhalb der Schleife benoetigt werden.
261 string kurz;
262 int indent, preis;
263 object item;
264 // Ueber die Liste laufen. <i> wird benoetigt, um die ausgegebene Liste
265 // konsistent numerieren zu koennen, damit kaufe <nr> funktioniert.
266 foreach(int i : sizeof(items_in_store))
267 {
268 item = items_in_store[i];
269 if ( call_other(ME, filter_fun, item) )
270 {
271 // Kurzbeschreibung und Preis ermitteln. Items ohne Short werden
272 // als "?" angezeigt.
273 kurz = (item->short() || "???")[0..<3];
274 preis = QueryBuyValue(item, PL);
275 // Beschreibung des Items anfuegen.
276 out += sprintf(listformat, i+1, kurz, preis);
277 indent = !indent;
278 // Wenn indent gesetzt ist, handelt es sich um das linke Item in der
279 // zweispaltigen Liste, dann fuegen wir einen Spaltentrenner an,
280 // ansonsten ist es das rechte, dann brechen wir um.
281 // Gilt natuerlich nur fuer kurze Listen.
282 out += ((indent && liststyle==LIST_SHORT)? " | " : "\n");
283 }
284 }
285 // Wenn die Liste eine ungerade Zahl Items enthaelt, ist in der letzten
286 // Zeile links ein Item aufgefuehrt, daher muss danach umbrochen werden.
287 // Gilt natuerlich nur fuer kurze Listen
288 if (indent && liststyle==LIST_SHORT)
289 out+="\n";
290
291 // Vor den Listen wird eine Info-Zeile ausgegeben, um gefilterte Listen
292 // kenntlich zu machen. Wird nach der Filterung des Inventars erzeugt,
293 // um eine leere Meldung ausgeben zu koennen, wenn nach Filterung nichts
294 // mehr uebrigbleibt.
295 string was;
296 switch(filter_fun)
297 {
298 case "IsArmour": was = "Ruestungen"; break;
299 case "IsWeapon": was = "Waffen"; break;
300 case "NoWeaponNoArmour":
301 was = (out==""?"sonstigen Waren":"sonstige Waren"); break;
302 default: was = "Waren"; break;
303 }
304 // <out> ist ein Leerstring, wenn keine Waren da sind, die dem Filterkri-
305 // terium entsprechen. Dann gibt's eine entsprechende Meldung.
306 if ( out == "" )
307 out = sprintf("Leider sind momentan keine %s im Angebot.\n", was);
308 else
309 out = sprintf("Folgende %s kannst Du hier kaufen:\n",was) + out;
310
311 PL->More(out);
312 return 1;
313}
314
315// Hilfsfunktionen zum Filtern des Ladeninventars fuer den "zeige"-Befehl
316static int AlwaysTrue(object ob)
317{ return 1; }
318
319static string IsWeapon(object ob)
320{ return ob->QueryProp(P_WEAPON_TYPE); }
321
322static string IsArmour(object ob)
323{ return ob->QueryProp(P_ARMOUR_TYPE); }
324
325static int NoWeaponNoArmour(object ob)
326{ return (!ob->QueryProp(P_WEAPON_TYPE) && !ob->QueryProp(P_ARMOUR_TYPE)); }
327
328
329// Diese Funktion ist oeffentlich, falls Magier abfragen wollen, ob ein laden
330// ein Objekt zerstoeren wuerde. Aber: Benutzung auf eigenes Risiko! Es wird
331// nicht garantiert, dass diese Funktion bzw. ihr Interface sich nicht
332// aendert.
333public int CheckForDestruct(object ob) // Pruefen, ob zerstoert werden soll
334{
335 string str;
336 /*
337 * P_NOBUY - Objekte auf jeden Fall zerstoeren
338 */
339 if(ob->QueryProp(P_NOBUY)) return 1;
340 /*
341 * Beschaedigte Objekte werden ebenfalls zerstoert
342 */
343 if(ob->QueryProp(P_DAMAGED)) return 1;
344 /*
345 * Ruestungen wenn sie a) eine DefendFunc definiert haben oder
346 * b) ueber der in KEEP_ARMOUR_CLASS definierten AC
347 * liegen (siehe /sys/combat.h)
348 */
349 if(str = IsArmour(ob))
350 {
351 if(objectp(ob->QueryProp(P_DEFEND_FUNC))) return 1;
352 if(ob->QueryProp(P_AC) >= KEEP_ARMOUR_CLASS[str]) return 1;
353 return 0;
354 }
355 /*
356 * Waffen wenn sie a) 1-haendig sind und eine WC > 120 besitzen oder
357 * b) 2-haendig sind und eine WC > 150 besitzen oder aber
358 * c) eine HitFunc definiert haben
359 */
360 if(str = IsWeapon(ob))
361 {
362 if(ob->QueryProp(P_NR_HANDS) > 1 && ob->QueryProp(P_WC) > 150) return 1;
363 if(ob->QueryProp(P_NR_HANDS) < 2 && ob->QueryProp(P_WC) > 120) return 1;
364 if(objectp(ob->QueryProp(P_HIT_FUNC))) return 1;
365 return 0;
366 }
367 return 0;
368}
369
370static int list(string str)
371{
372 _notify_fail(
373 "Bitte 'zeige', 'zeige waffen', 'zeige ruestungen' oder\n"
374 "'zeige verschiedenes' eingeben. Wenn Du das Schluesselwort 'lang'\n"
375 "oder '-1' anhaengst, wird die Liste einspaltig ausgegeben.\n");
376
377 if (!stringp(str) || !sizeof(str) )
378 return PrintList("AlwaysTrue");
379 if ( str == "lang" || str == "-1" )
380 return PrintList("AlwaysTrue", LIST_LONG);
381
382 string *params = explode(str," ");
383 if (sizeof(params[0])<3)
384 return 0;
385
386 int liststyle = LIST_SHORT;
387 if ( sizeof(params)>1 && params[1] == "lang" )
388 liststyle = LIST_LONG;
389
390 str=params[0][0..2];
391 if (str=="waf")
392 return PrintList("IsWeapon", liststyle);
393 if (str=="ver")
394 return PrintList("NoWeaponNoArmour", liststyle);
395 if (str=="rue")
396 return PrintList("IsArmour", liststyle);
397 return 0;
398}
399/*
400static varargs int QueryBuyValue(mixed ob, object client)
401{
402 if (objectp(ob))
403 return trading_price::QueryBuyValue(ob, client);
404 return (fixed_value[ob]*QueryBuyFact(client))/100;
405}
406*/
407
408static varargs int QueryBuyValue(object ob, object client)
409{
410 int fprice = fixed_value[load_name(ob)];
411
412 return (fprice>0) ?
413 fprice*QueryBuyFact(client)/100 :
414 trading_price::QueryBuyValue(ob, client);
415}
416
417static void UpdateCounter(object ob, int num)
418{
419 string tmp;
420
421 if (!num || !objectp(ob)) return;
422 tmp=BLUE_NAME(ob);
423 if (tmp[0..2]!="/d/" && tmp[0..8]!="/players/")
424 tmp=ob->short()+tmp;
425 ob_anz[tmp] += num;
426 if (ob_anz[tmp] <= 0)
427 m_delete(ob_anz,tmp);
428}
429
430protected object FindInStore(string|int x)
431{
432 object* list = GetShopItems();
433 if ( intp(x) && x>0 && x<=sizeof(list) ) {
434 return list[x-1];
435 }
436 if (stringp(x))
437 {
438 if ( fixed_ids[x] )
439 return load_object(fixed_ids[x]);
440 list = filter_objects(list, "id", x);
441 if ( sizeof(list) )
442 return list[0];
443 // Wenn nix im Store gefunden (das schliesst eigentlicht schon die BPs der
444 // fixen Objekte ein, aber nicht, wenn diese nicht konfiguriert sind. D.h.
445 // diese Pruefung ist fuer nicht-konfigurierte BPs), Liste der
446 // FixedObjects pruefen unde so die eventuell manuell in
447 // AddFixedObject() angegebene Liste von IDs beruecksichtigen.
448 else if ( fixed_ids[x] )
449 return load_object(fixed_ids[x]);
450 }
451 return 0;
452}
453
454static string buy_obj(mixed ob, int short)
455{ return 0; }
456
457private void really_buy(int val, object pl, object ob, int u_req)
458{
459 // Staendig verfuegbare Objekte (fixed_obj) sind daran erkennbar, dass sie
460 // nicht im Lager liegen. Daher hier einen Clone erstellen, der dann
461 // stattdessen rausgegeben wird.
462 if ( !present(ob, find_object(storage)) )
463 ob = clone_object(ob);
464
465 // In Unitobjekten U_REQ (wieder) setzen (wegen input_to (bei dem sich das
466 // Kommandoverb aendert und deswegen U_REQ geloescht wird), und wegen
467 // Kaufens von Fixed-Objekt-Unitobjekten (bei diesen muss U_REQ _nach_ dem
468 // Clonen im Clone gesetzt werden, nicht automagisch in der BP durch den
469 // Aufruf von id() weiter vorher).
470 if (u_req>0)
471 {
472 // Das QueryProp() ist nicht unnoetig. Bei der Abfrage von U_REQ wird
473 // U_REQ genullt, wenn das aktuelle query_verb() != dem letzten ist.
474 // Bei der ersten Abfrage wuerde als das hier gesetzt U_REQ wieder
475 // geloescht. Daher muss das jetzt hier als erstes einmal abgefragt
476 // werden...
477 ob->QueryProp(U_REQ);
478 ob->SetProp(U_REQ, u_req);
479 }
480
481 pl->AddMoney(-val);
482 _add_money(val);
483
484 if (ob->move(pl,M_GET) != MOVE_OK) // Kann der Spieler das Objekt tragen?
485 {
486 write(break_string("Du kannst "+ob->name(WEN,1)+" nicht mehr tragen. "
487 "Ich lege "+ob->QueryPronoun(WEN)+" hier auf den Boden.",78,
488 Name(WER)+" sagt: "));
489 ob->move(ME,M_NOCHECK); // Nein :-)
490 }
491 else
492 {
493 // Falls das Objekt sich vereinigt hat, muss jetzt wieder U_REQ
494 // restauriert werden.
495 ob->SetProp(U_REQ, u_req);
496 write(break_string("Du kaufst "+ob->name(WEN,1)+".", 78));
497 }
498
499 say(break_string(PL->Name(WER)+" kauft "+ob->name(WEN)+".",78), ({PL}));
500 UpdateCounter(ob,-1);
501}
502
503static void ask_buy(string str, int val, object pl, object ob, int u_req)
504{
505 _notify_fail(break_string("Gut, Du kannst es Dir ja jederzeit "
506 "noch anders ueberlegen.",78,Name(WER)+" sagt: "));
507
508 if(!str || !stringp(str) || str == "nein" || str == "n")
509 {
510 return;
511 }
512 if(str != "ja" && str != "j")
513 {
514 return;
515 }
516 really_buy(val, pl, ob, u_req);
517}
518
519static int buy(string str)
520{
521 int i, val, par, dex;
522 mixed ob, res;
523 string dummy;
524
525 if (!str) {
526 write("Was willst Du kaufen?\n");
527 return 1;
528 }
529
530 if (stringp(QueryProp(P_KEEPER)) && !present(QueryProp(P_KEEPER), ME)) {
531 write("Es ist niemand da, der Dich bedienen koennte.\n");
532 return 1;
533 }
534
535 _notify_fail(break_string("Das kann ich in meinem Lager nicht finden.",78,
536 Name(WER)+" sagt: "));
537
538 // Um auch Teile von Unit-Stacks kaufen zu koennen, z.B. "kaufe 5 phiolen",
539 // darf hier zusaetzlich <dummy> nur ein Leerstring sein, sonst verzweigt
540 // die Syntaxpruefung hierhin und es wird das 5. Item der Liste gekauft.
541 if (sscanf(str,"%d%s",i,dummy)>0 && i>0 && !sizeof(dummy)) {
542 ob=FindInStore(i);
543 }
544 else ob=FindInStore(str);
545
546 if (!ob) return 0;
547
548 if (str = buy_obj(ob,0))
549 {
550 write(break_string(str,78,Name(WER)+" sagt: "));
551 return 1;
552 }
553
554 val = QueryBuyValue(ob,PL);
555
556 if (PL->QueryMoney() < val)
557 {
558 write(break_string(capitalize(ob->QueryPronoun(WER))+" wuerde "+val+
559 " Muenzen kosten, und Du hast nur "+PL->QueryMoney()+".",78,
560 Name(WER)+" bedauert: "));
561 return 1;
562 }
563
564 // Anzahl der angeforderten Einheiten vor dem Bewegen zwischenspeichern.
565 // Weil dabei im Fall von Units eine Vereinigung mit bereits im Inventar
566 // befindlichen Einheiten stattfindet, muss das ggf. nach Bewegen
567 // zurueckgesetzt werden.
568 int u_req = ob->QueryProp(U_REQ);
569
570 if ((res = ob->QueryProp(P_RESTRICTIONS)) && mappingp(res) &&
571 (res = call_other("/std/restriction_checker",
572 "check_restrictions",PL,res)) &&
573 stringp(res))
574 {
575 _notify_fail(break_string("Du koenntest "+ob->name(WEN,2)+" nicht "
576 "verwenden. Grund: "+res+"Moechtest Du "+ob->QueryPronoun(WEN)+
577 " dennoch kaufen?",78,Name(WER)+" sagt: "));
578
579 input_to("ask_buy",INPUT_PROMPT, "(ja/nein) ", val,PL,ob,u_req);
580 return 0;
581 }
582
Arathorn26e02d32019-12-02 23:55:09 +0100583 par = ob->QueryProp(P_PARRY);
584 dex = PL->QueryAttribute(A_DEX);
MG Mud User88f12472016-06-24 23:31:02 +0200585
586 if ((((par < PARRY_ONLY) && ((dex + 8) * 10) < ob->QueryProp(P_WC)) ||
587 ((par > PARRY_NOT) && ((dex + 5) * 2) < ob->QueryProp(P_AC))) &&
588 VALID_WEAPON_TYPE(ob))
589 {
590 _notify_fail(break_string("Du koenntest "+ob->name(WEN,2)+" nicht "
591 "zuecken, da Deine Geschicklichkeit dafuer nicht ausreicht. Moechtest "
592 "Du "+ob->QueryPronoun(WEN)+" dennoch kaufen?",78,
593 Name(WER)+" sagt: "));
594
595 input_to("ask_buy",INPUT_PROMPT, "(ja/nein) ", val,PL,ob,u_req);
596 return 0;
597 }
598
599 really_buy(val, PL, ob, u_req);
600
601 return 1;
602}
603
604private void give_money(int value)
605// Geld gutschreiben...
606{
607 if (!value) return;
608 write(break_string(Name(WER, 1)+" zahlt Dir "+value+" Goldstueck"
609 +(value==1?".":"e."), 78));
610 if ((PL->AddMoney(value))<=0) {
611 object mon;
612
613 write("Du kannst das Geld nicht mehr tragen!\n");
614 mon=clone_object(GELD);
615 mon->SetProp(P_AMOUNT,value);
616 mon->move(ME,M_MOVE_ALL|M_NOCHECK);
617 }
618}
619
620static int make_to_money(object ob, int value)
621// Interne Funktion, die ob versucht in das Lager zu ueberfuehren und das
622// Geld das dabei fuer den Spieler abfaellt zurueckliefert.
623{
MG Mud User88f12472016-06-24 23:31:02 +0200624 int ret;
625
626 if (!objectp(ob) || environment(ob)==find_object(storage)) {
627 write(break_string(Name(WER, 1)+" wundert sich ueber Dich.", 78));
628 return 0;
629 }
630 if (value>QueryProp(P_CURRENT_MONEY)) {
631 write(break_string("Ich hab das Geld leider nicht mehr.", 78,
632 Name(WER, 1)+" sagt: "));
633 return 0;
634 }
635 // U_REQ merken, falls sich Objekte vereinigen. Sonst stimmt nicht nur der
636 // Name, sondern es werden ggf. auch die falsche Anzahl zerstoert.
637 //TOOO: Oder Units entsorgen und als Feature deklarieren?
638 int req = ob->QueryProp(U_REQ);
639 if (CheckForDestruct(ob) > 0) // soll ob zerstoert werden?
640 {
641 ret = ob->move(storage,M_PUT|M_GET);
642 // Falls das Objekt sich vereinigt hat (Units), muessen die gewuenschten
643 // Einheiten restauriert werden.
644 // Problem: falls das verkaufte Objekt Units hat, beschaedigt ist und sich
645 // vereinigt hat, sind jetzt leider alle Einheiten im Lager beschaedigt.
646 // Das ist unschoen - aber mir jetzt zuviel AUfwand, das korrekt zu bauen,
647 // weil es nur sehr selten vorkommt. (Hint: separater Muellraum)
648 ob->SetProp(U_REQ, req);
649 if (ret > 0) // Sonst werden auch Sachen zerstoert, die man nicht
650 { // weglegen kann
651 say(break_string(PL->Name()+" verkauft "+ob->name(WEN)+".", 78));
652 if(ob->QueryProp(P_DAMAGED)) // Andere Meldung bei beschaedigten
653 { // Objekten ...
654 write(break_string(Name(WER,1)+" findet zwar keinen Gefallen an "
655 +ob->name(WEM,1)+", nimmt "+ob->QueryPronoun(WEN)+" Dir zuliebe "
656 "aber trotzdem.",78));
657 }
658 else
659 {
660 write(break_string(Name(WER, 1)+" findet Gefallen an "
661 +ob->name(WEM, 1) + " und legt "+ob->QueryPronoun(WEN)
662 +" zu "+(QueryProp(P_GENDER)==FEMALE?"ihren":"seinen")
663 +" Privatsachen.", 78));
664 }
665 /* Er zahlt Dir "+value+" Muenze"+(value==1?"":"n")+" dafuer.", 78)); */
666 _add_money(-value);
667 _add_money(value*QueryProp(P_SHOP_PERCENT_LEFT)/100); // Wegen Zerstoerung des Objektes
668 UpdateCounter(ob,1);
669 ob->remove(1);
670 return value;
671 }
Arathorna1fc9352019-12-03 00:06:39 +0100672 else if (ret == ME_CANT_BE_DROPPED)
673 {
674 string|int nodrop = ob->QueryProp(P_NODROP);
675 if (stringp(nodrop) && sizeof(nodrop))
676 write(nodrop);
677 else
678 write(break_string("Du kannst "+ob->name(WEN,1)+" nicht verkaufen!",
679 78));
MG Mud User88f12472016-06-24 23:31:02 +0200680 return 0;
681 }
682 else
683 write(break_string(ob->Name(WER)+" interessiert mich nicht.", 78,
684 Name(WER, 1)+" sagt: "));
685 }
686 else if ((ret=(ob->move(storage,M_PUT|M_GET)))>0)
687 {
688 // Falls das Objekt sich vereinigt hat (Units), muessen die gewuenschten
689 // Einheiten restauriert werden.
690 ob->SetProp(U_REQ, req);
691 say(break_string(PL->Name(WER)+" verkauft "+ob->name(WEN)+".", 78));
692 _add_money(-value);
693 UpdateCounter(ob,1);
694 return value;
695 }
Arathorna1fc9352019-12-03 00:06:39 +0100696 else if (ret == ME_CANT_BE_DROPPED)
697 {
698 string|int nodrop = ob->QueryProp(P_NODROP);
699 if (stringp(nodrop) && sizeof(nodrop))
700 write(nodrop);
701 else
702 write(break_string("Du kannst "+ob->name(WEN,1)+" nicht verkaufen!",
703 78));
704 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200705 }
Arathorna1fc9352019-12-03 00:06:39 +0100706 else
707 write(break_string("Du kannst "+ob->name(WEN,1)+" nicht verkaufen!",
708 78));
MG Mud User88f12472016-06-24 23:31:02 +0200709 return 0;
710}
711
712static void ask_sell(string str, object ob, int val, int u_req)
713// Wenn ein einzelnen Stueck unter Wert verkauft werden soll, wird nachgefragt
714// u_req ist bei Unitobjekten die Anzahl an zu verkaufenden Einheiten. Bei
715// normalen Objekten ist u_req 0.
716{
717 str=lower_case(str||"");
718 if (str=="ja"||str=="j")
719 {
720 // In Unitobjekten U_REQ (wieder) setzen.
721 if (u_req>0)
722 {
723 // Das QueryProp() ist nicht unnoetig. Bei der Abfrage von U_REQ wird
724 // U_REQ genullt, wenn das aktuelle query_verb() != dem letzten ist.
725 // Bei der ersten Abfrage wuerde als das hier gesetzt U_REQ wieder
726 // geloescht. Daher muss das jetzt hier als erstes einmal abgefragt
727 // werden...
728 ob->QueryProp(U_REQ);
729 ob->SetProp(U_REQ, u_req);
730 }
731 give_money(make_to_money(ob,val));
732 }
733 else
734 write(break_string("Ok, dann behalts!", 78,
735 Name(WER, 1)+" sagt: "));
736}
737
738static string sell_obj(object ob, int short)
739// Ist der Haendler bereit ob zu kaufen? wenn nein, Rueckgabe einer Meldung,
740// die der Haendler sagen soll.
741{ mixed nosell;
742
743 if (BLUE_NAME(ob)==GELD)
744 return "Das waere ja wohl Unsinn, oder ...?";
745 if (nosell=ob->QueryProp(P_NOSELL))
746 {
747 if (stringp(nosell))
748 return nosell;
749 return ("Du kannst "+ob->name(WEN,1)+" nicht verkaufen!");
750 }
751 if (ob->QueryProp(P_CURSED))
752 return ob->Name(WER,1)
753 +" ist mir irgendwie ungeheuer! Das kannst Du nicht verkaufen!";
754 // man sollte keine COntainer mit Inhalt verkaufen koennen, ggf. kauft sie
755 // dann ein anderer Spieler.
756 if (first_inventory(ob))
757 {
758 return ob->Name(WER, 1) + " ist nicht leer!";
759 }
760 return 0;
761}
762
763static varargs int sell(string str, int f)
764{
765 int i, val, oval, tmp;
766 object *obs;
767
768 if (stringp(QueryProp(P_KEEPER)) && !present(QueryProp(P_KEEPER),ME)) {
769 write("Es ist niemand da, der Dich bedienen koennte.\n");
770 return 1;
771 }
772
773 if (!str) {
774 notify_fail("Was moechtest Du denn verkaufen?\n");
775 return 0;
776 }
777
778 /* Ergebnis von find_obs() sollte unifiziert werden, damit ein mehrfach
779 gefundenes Objekt nicht mehrfach versucht wird zu verkaufen.
780 Beispiel: Objekt hat P_NOBUY gesetzt und mehrere IDs gesetzt. Wenn
781 ein Spieler es mit "verkaufe ID1 und ID2" versucht zu verkaufen,
782 wuerde das einen Bug ausloesen. Derselbe Bug entsteht, wenn man mit
783 "verkaufe ID1 und ID1" verkauft. */
784 obs = PL->find_obs(str, PUT_GET_DROP);
785 /* Erst im Inventar schauen, dann im Environment. find_obs() ohne 2.
786 Parameter macht das standardmaessig andersherum.
787 TODO: Aenderung ueberpruefen, sobald das neue put_and_get.c
788 eingebaut wurde. */
789 if ( !sizeof(obs) )
790 obs = PL->find_obs(str, PUT_GET_TAKE) || ({});
791 obs = m_indices(mkmapping(obs));
792 if (!i=sizeof(obs)) {
793 notify_fail("Was moechtest Du denn verkaufen?\n");
794 return 0;
795 }
796 call_other(storage, "_register_shop", ME);
797 if (i==1) {
798 if (str=sell_obj(obs[0], 0)) {
799 write(break_string(str, 78, Name(WER, 1)+" sagt: "));
800 return 1;
801 }
802 if ((oval=obs[0]->QueryProp(P_VALUE))<=0) {
803 write(break_string(obs[0]->Name(WER)
804 +(obs[0]->QueryProp(P_PLURAL) ? " haben" : " hat")
805 +" keinen materiellen Wert, tut mir leid.", 78,
806 Name(WER, 1)+" sagt: "));
807 return 1;
808 }
809 val=QuerySellValue(obs[0], PL);
810 if (!val) {
811 write(break_string("Ich bin absolut pleite. Tut mir aufrichtig leid.",
812 78, Name(WER, 1)+" sagt: "));
813 return 1;
814 }
815 if (val==oval || f) {
816 give_money(make_to_money(obs[0], val));
817 return 1;
818 }
819 if (str=obs[0]->QueryProp(P_NODROP)) {
820 if (stringp(str))
821 write(str);
822 else write(break_string("Du kannst "+obs[0]->name(WEN,1)
823 +" nicht verkaufen!", 78));
824 return 1;
825 }
826
827 if (obs[0]->QueryProp(P_DAMAGED)) // Bei beschaedigten Objekten gibt
828 { // es auch hier eine andere Meldung
829 write(break_string("Da "+obs[0]->name(WER)+" beschaedigt "
830 +(obs[0]->QueryProp(P_PLURAL)?"sind":"ist")+", kann ich Dir "
831 "nur "+val+" Muenze"+(val == 1?"":"n")+" dafuer bieten. Und "
832 "damit mache ich noch Verlust! Nimmst Du mein Angebot an? "
833 "(ja/nein)",78,Name(WER,1)+" sagt: "));
834 }
835 else // Default
836 {
837 write(break_string(Name(WER, 1)+" sagt: "
838 +"Nach der aktuellen Marktlage kann ich Dir dafuer nur "
839 +val+" Muenze"+(val==1?"":"n")+" bieten, obwohl "
840 +obs[0]->name(WER)+" eigentlich "+oval+" Muenze"
841 +(oval==1?"":"n")+" wert waere. Willst Du "
842 +(QueryProp(P_PLURAL) ? "sie" : "es")
843 +" mir dafuer ueberlassen?", 78));
844 }
845 // in ask_sell() gibt es das query_verb() nicht mehr, weswegen U_REQ in
846 // Unitobjekten zurueckgesetzt wird. Damit geht die info verloren,
847 // wieviele Objekte der Spieler angegeben hat. Diese muss gerettet und
848 // via ask_sell() in make_to_money() ankommen. In normalen Objekten ist
849 // U_REQ 0.
850 input_to("ask_sell",INPUT_PROMPT, "(ja/nein) ",obs[0], val,
851 (obs[0])->QueryProp(U_REQ) );
852 return 1;
853 }
854 for (--i; i>=0 && get_eval_cost()>50000; i--) {
855 if (oval=obs[i]->QueryProp(P_VALUE)) {
856 if (obs[i]->QueryProp(P_KEEP_ON_SELL)==getuid(PL)
857 || obs[i]->QueryProp(P_WORN) || obs[i]->QueryProp(P_WIELDED))
858 write(break_string(obs[i]->Name(WER)+": Du behaeltst "
859 +obs[i]->name(WEN)+".", 78));
860 else if (str=sell_obj(obs[i], 1))
861 write(break_string(obs[i]->Name(WER)+": "+str, 78));
862 else {
863 tmp=QuerySellValue(obs[i], PL);
864 if (!tmp) {
865 write(break_string(
866 "Ich bin absolut pleite. Tut mir aufrichtig leid.", 78,
867 Name(WER, 1)+" sagt: "));
868 break;
869 }
870 else if (!f && tmp*10<oval)
871 write(break_string(obs[i]->Name(WER)+": "+Name(WER, 1)
872 +" bietet Dir aber nur "+tmp+" Goldstueck"
873 +(tmp>1 ? "e" : "")+" dafuer.", 78));
874 else {
875 str=(obs[i]->Name(WER));
876 if (tmp=make_to_money(obs[i], tmp)) {
877 write(break_string(str+": "+Name(WER, 1)
878 +" gibt Dir dafuer "+tmp+" Goldstueck"
879 +(tmp==1?".":"e."), 78));
880 val+=tmp;
881 }
882 }
883 }
884 }
885 }
886 if (!val)
887 write(break_string("Hmmm, Du hast aber nicht besonders viel zu bieten...",
888 78, Name(WER)+" sagt: "));
889 else give_money(val);
890 return 1;
891}
892
893static int force_sell(string str)
894{ return sell(str, 1); }
895
896static int evaluate(string str)
897{
898 object ob;
899 int val,rval;
900
901 if (!str) return 0;
902 if(stringp(QueryProp(P_KEEPER)) && !present(QueryProp(P_KEEPER), ME)) {
903 write("Es ist niemand da, der Dich bedienen koennte.\n");
904 return 1;
905 }
906
907 ob=present(str, ME);
908 if (!ob) ob=deep_present(str,PL);
909 if (!ob) {
910 write("Hm? "+capitalize(str)+"? Wovon redest Du?\n");
911 return 1;
912 }
913 if (living(ob)) {
914 _notify_fail("Nanu, seit wann werden hier Lebewesen verkauft?\n");
915 return 0;
916 }
917 if (str=sell_obj(ob, 0)) {
918 write(break_string(str, 78, Name(WER)+" sagt: "));
919 return 1;
920 }
921 rval=ob->QueryProp(P_VALUE);
922 if (rval) {
923 val=QuerySellValue(ob, PL);
924 if (rval==val) {
925 tell_object(this_player(),break_string(
926 "Naja, ich denke, " +val+ " Muenze"
927 + (val==1 ? "" : "n")
928 + " waere"+(ob->QueryProp(P_AMOUNT)>1?"n ":" ")
929 + (ob->QueryPronoun(WER))+" schon wert.\n",78));
930 }
931 else if (val) {
932 tell_object(this_player(),break_string(
933 "Oh, nach der aktuellen Marktlage kann ich nur "+val+" Muenze"+
934 (val==1?"":"n")+" bezahlen, obwohl "
935 + (QueryProp(P_PLURAL) ? "sie" : "es")
936 + " eigentlich "+rval
937 + " Muenze"+(rval==1?"":"n")+" wert ist.\n",78));
938 }
939 else write("Ich bin vollkommen pleite. Tut mir leid.\n");
940 }
941 else write("Das ist voellig wertlos.\n");
942 return 1;
943}
944
945static int show_obj(string str)
946{
947 int i;
948 string was;
949 mixed ob;
950
951 if (!str) return 0;
952 if (sscanf(str,"%s im laden",was)>0 || sscanf(str,"%s in laden",was)>0) {
953 _notify_fail("Das kann ich im Lager nicht finden.\n");
954 ob=FindInStore(was);
955 } else if (sscanf(str,"%d",i)>0 && i>0) {
956 _notify_fail("Das kann ich im Lager nicht finden.\n");
957 ob=FindInStore(i);
958 }
959 if (!ob) return 0;
960 write(ob->Name(WER)+":\n"+ob->long()+capitalize(ob->QueryPronoun())
961 +" kostet "+QueryBuyValue(ob,PL)+" Muenzen.\n");
962 return 1;
963}
964
965// benutzt von trading_price::QueryValue(object, int, object)
966static int ObjectCount(object ob)
967{
968 string tmp;
969
970 if (!objectp(ob)) return 0;
971 tmp = BLUE_NAME(ob);
972 if (tmp[0..2]!="/d/" && tmp[0..8]!="/players/") tmp=ob->short()+tmp;
973 return ob_anz[tmp];
974}
975
976// benutzt von trading_price::QuerySellValue(object, object)
977static varargs int QueryValue(object ob, int value, object client)
978{
979 int new_value, mymoney;
980
981 if (!objectp(ob)) return 0;
982 if (Query("laden::compat")) {
983 new_value=(value>1000?1000:value);
984 mymoney = QueryProp(P_CURRENT_MONEY);
985 if (new_value>mymoney)
986 return (mymoney>0?mymoney:0);
987 else return new_value;
988 }
989 return ::QueryValue(ob, value, client);
990}
991
992void reset()
993{
994 mixed *keys;
995 int i;
996
997 trading_price::reset();
998
999 if (!mappingp(ob_anz))
1000 return;
1001 keys=m_indices(ob_anz);
1002 for (i=sizeof(keys)-1;i>=0;i--) {
1003 ob_anz[keys[i]]=ob_anz[keys[i]]*7/8;
1004 if (!ob_anz[keys[i]])
1005 m_delete(ob_anz,keys[i]);
1006 }
1007}
1008
1009varargs int CheckFindRestrictions(object ob, mixed restr, closure qp) {
1010 return 0;
1011}
1012
1013int EvalWeapon(object ob, closure qp) {
1014 int wc,val;
1015
1016 wc=funcall(qp,P_WC);
1017 val=funcall(qp,P_EFFECTIVE_WC);
1018 if (val>wc) wc=val;
1019 return wc;
1020}
1021
1022varargs object FindBestWeapon(mixed type, int maxmon, int maxw, int hands,
1023 int bestwc, mixed restr) {
1024 object bestob,ob;
1025 string otype;
1026 int wc,bestval,val,w,bestw;
1027 closure qp;
1028
1029 if (!stringp(storage) || !objectp(ob=find_object(storage))) return 0;
1030 if (!maxmon) maxmon=100000;
1031 if (!maxw) maxw=75000;
1032 if (!hands) hands=2;
1033 if (val=QueryBuyFact()) maxmon=(maxmon*100)/val;
1034 if (type && !pointerp(type) && !mappingp(type)) type=({type});
1035
1036 for (ob=first_inventory(ob);ob;ob=next_inventory(ob)) {
1037 qp=symbol_function("QueryProp",ob);
1038 if (!otype=funcall(qp,P_WEAPON_TYPE)) continue;
1039 if (type && member(type,otype)<0) continue;
1040 wc=EvalWeapon(ob,qp);
1041 if (wc<bestwc) continue;
1042 if (funcall(qp,P_NR_HANDS)>hands) continue;
1043 w=funcall(qp,P_WEIGHT);
1044 if (w>maxw) continue;
1045 val=funcall(qp,P_VALUE);
1046 if (val>maxmon) continue;
1047 if (bestob && wc<=bestwc) {
1048 if (val>bestval) continue;
1049 else if (val==bestval && w>bestw) continue;
1050 }
1051 if (val>bestval && bestob && wc<=bestwc) continue;
1052 if (CheckFindRestrictions(ob,restr,qp)) continue;
1053 bestob=ob;
1054 bestwc=wc;
1055 bestval=val;
1056 bestw=w;
1057 }
1058 return bestob;
1059}
1060
1061int EvalArmour(object ob,closure qp) {
1062 int ac,val;
1063
1064 ac=funcall(qp,P_AC);
1065 val=funcall(qp,P_EFFECTIVE_AC);
1066 if (val>ac) ac=val;
1067 return ac;
1068}
1069
1070varargs mapping FindBestArmoursT(mixed type, int maxmon, int maxw,
1071 mapping bestac, mixed restr) {
1072 object ob;
1073 string otype;
1074 int ac,val,sum,w,wsum;
1075 mapping bestob,bestval,bestw;
1076 closure qp;
1077
1078 if (!stringp(storage) || !objectp(ob=find_object(storage))) return ([]);
1079 if (!maxmon) maxmon=100000;
1080 if (!maxw) maxw=75000;
1081 if (val=QueryBuyFact()) maxmon=(maxmon*100)/val;
1082 if (type && !pointerp(type) && !mappingp(type)) type=({type});
1083 if (!mappingp(bestac)) bestac=([]);
1084 bestob=([]);bestval=([]);bestw=([]);
1085
1086 for (ob=first_inventory(ob);ob;ob=next_inventory(ob)) {
1087 qp=symbol_function("QueryProp",ob);
1088 if (!otype=funcall(qp,P_ARMOUR_TYPE)) continue;
1089 if (type && member(type,otype)<0) continue;
1090 ac=EvalArmour(ob,qp);
1091 if (ac<bestac[otype]) continue;
1092 w=funcall(qp,P_WEIGHT);
1093 if (wsum-bestw[otype]+w>maxw) continue;
1094 val=funcall(qp,P_VALUE);
1095 if (sum-bestval[otype]+val>maxmon) continue;
1096 if (bestob[otype] && ac<=bestac[otype]) {
1097 if (val>bestval[otype]) continue;
1098 else if (val==bestval[otype] && w>bestw[otype]) continue;
1099 }
1100 if (CheckFindRestrictions(ob,restr,qp)) continue;
1101 sum=sum-bestval[otype]+val;
1102 wsum=wsum-bestw[otype]+w;
1103 bestob[otype]=ob;
1104 bestac[otype]=ac;
1105 bestval[otype]=val;
1106 bestw[otype]=w;
1107 }
1108 return bestob;
1109}
1110
1111varargs object *FindBestArmours(mixed type, int maxmon, int maxw,
1112 mapping bestac, mixed restr) {
1113 return m_values(FindBestArmoursT(type,maxmon,maxw,bestac,restr));
1114}