blob: 0719d08c514d9c58877a13ee8e7962e9308a2c64 [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 }
Arathornc7237e72020-03-09 22:32:27 +0100819 string|int nodrop = obs[0]->QueryProp(P_NODROP);
820 if (nodrop) {
821 if (stringp(nodrop))
822 write(nodrop);
823 else
824 write(break_string(
825 "Du kannst "+obs[0]->name(WEN,1)+" nicht verkaufen!", 78));
MG Mud User88f12472016-06-24 23:31:02 +0200826 return 1;
827 }
828
829 if (obs[0]->QueryProp(P_DAMAGED)) // Bei beschaedigten Objekten gibt
830 { // es auch hier eine andere Meldung
831 write(break_string("Da "+obs[0]->name(WER)+" beschaedigt "
832 +(obs[0]->QueryProp(P_PLURAL)?"sind":"ist")+", kann ich Dir "
833 "nur "+val+" Muenze"+(val == 1?"":"n")+" dafuer bieten. Und "
834 "damit mache ich noch Verlust! Nimmst Du mein Angebot an? "
835 "(ja/nein)",78,Name(WER,1)+" sagt: "));
836 }
837 else // Default
838 {
839 write(break_string(Name(WER, 1)+" sagt: "
840 +"Nach der aktuellen Marktlage kann ich Dir dafuer nur "
841 +val+" Muenze"+(val==1?"":"n")+" bieten, obwohl "
842 +obs[0]->name(WER)+" eigentlich "+oval+" Muenze"
843 +(oval==1?"":"n")+" wert waere. Willst Du "
844 +(QueryProp(P_PLURAL) ? "sie" : "es")
845 +" mir dafuer ueberlassen?", 78));
846 }
847 // in ask_sell() gibt es das query_verb() nicht mehr, weswegen U_REQ in
848 // Unitobjekten zurueckgesetzt wird. Damit geht die info verloren,
849 // wieviele Objekte der Spieler angegeben hat. Diese muss gerettet und
850 // via ask_sell() in make_to_money() ankommen. In normalen Objekten ist
851 // U_REQ 0.
852 input_to("ask_sell",INPUT_PROMPT, "(ja/nein) ",obs[0], val,
853 (obs[0])->QueryProp(U_REQ) );
854 return 1;
855 }
856 for (--i; i>=0 && get_eval_cost()>50000; i--) {
857 if (oval=obs[i]->QueryProp(P_VALUE)) {
858 if (obs[i]->QueryProp(P_KEEP_ON_SELL)==getuid(PL)
859 || obs[i]->QueryProp(P_WORN) || obs[i]->QueryProp(P_WIELDED))
860 write(break_string(obs[i]->Name(WER)+": Du behaeltst "
861 +obs[i]->name(WEN)+".", 78));
862 else if (str=sell_obj(obs[i], 1))
863 write(break_string(obs[i]->Name(WER)+": "+str, 78));
864 else {
865 tmp=QuerySellValue(obs[i], PL);
866 if (!tmp) {
867 write(break_string(
868 "Ich bin absolut pleite. Tut mir aufrichtig leid.", 78,
869 Name(WER, 1)+" sagt: "));
870 break;
871 }
872 else if (!f && tmp*10<oval)
873 write(break_string(obs[i]->Name(WER)+": "+Name(WER, 1)
874 +" bietet Dir aber nur "+tmp+" Goldstueck"
875 +(tmp>1 ? "e" : "")+" dafuer.", 78));
876 else {
877 str=(obs[i]->Name(WER));
878 if (tmp=make_to_money(obs[i], tmp)) {
879 write(break_string(str+": "+Name(WER, 1)
880 +" gibt Dir dafuer "+tmp+" Goldstueck"
881 +(tmp==1?".":"e."), 78));
882 val+=tmp;
883 }
884 }
885 }
886 }
887 }
888 if (!val)
889 write(break_string("Hmmm, Du hast aber nicht besonders viel zu bieten...",
890 78, Name(WER)+" sagt: "));
891 else give_money(val);
892 return 1;
893}
894
895static int force_sell(string str)
896{ return sell(str, 1); }
897
898static int evaluate(string str)
899{
900 object ob;
901 int val,rval;
902
903 if (!str) return 0;
904 if(stringp(QueryProp(P_KEEPER)) && !present(QueryProp(P_KEEPER), ME)) {
905 write("Es ist niemand da, der Dich bedienen koennte.\n");
906 return 1;
907 }
908
909 ob=present(str, ME);
910 if (!ob) ob=deep_present(str,PL);
911 if (!ob) {
912 write("Hm? "+capitalize(str)+"? Wovon redest Du?\n");
913 return 1;
914 }
915 if (living(ob)) {
916 _notify_fail("Nanu, seit wann werden hier Lebewesen verkauft?\n");
917 return 0;
918 }
919 if (str=sell_obj(ob, 0)) {
920 write(break_string(str, 78, Name(WER)+" sagt: "));
921 return 1;
922 }
923 rval=ob->QueryProp(P_VALUE);
924 if (rval) {
925 val=QuerySellValue(ob, PL);
926 if (rval==val) {
927 tell_object(this_player(),break_string(
928 "Naja, ich denke, " +val+ " Muenze"
929 + (val==1 ? "" : "n")
930 + " waere"+(ob->QueryProp(P_AMOUNT)>1?"n ":" ")
931 + (ob->QueryPronoun(WER))+" schon wert.\n",78));
932 }
933 else if (val) {
934 tell_object(this_player(),break_string(
935 "Oh, nach der aktuellen Marktlage kann ich nur "+val+" Muenze"+
936 (val==1?"":"n")+" bezahlen, obwohl "
937 + (QueryProp(P_PLURAL) ? "sie" : "es")
938 + " eigentlich "+rval
939 + " Muenze"+(rval==1?"":"n")+" wert ist.\n",78));
940 }
941 else write("Ich bin vollkommen pleite. Tut mir leid.\n");
942 }
943 else write("Das ist voellig wertlos.\n");
944 return 1;
945}
946
947static int show_obj(string str)
948{
949 int i;
950 string was;
951 mixed ob;
952
953 if (!str) return 0;
954 if (sscanf(str,"%s im laden",was)>0 || sscanf(str,"%s in laden",was)>0) {
955 _notify_fail("Das kann ich im Lager nicht finden.\n");
956 ob=FindInStore(was);
957 } else if (sscanf(str,"%d",i)>0 && i>0) {
958 _notify_fail("Das kann ich im Lager nicht finden.\n");
959 ob=FindInStore(i);
960 }
961 if (!ob) return 0;
962 write(ob->Name(WER)+":\n"+ob->long()+capitalize(ob->QueryPronoun())
963 +" kostet "+QueryBuyValue(ob,PL)+" Muenzen.\n");
964 return 1;
965}
966
967// benutzt von trading_price::QueryValue(object, int, object)
968static int ObjectCount(object ob)
969{
970 string tmp;
971
972 if (!objectp(ob)) return 0;
973 tmp = BLUE_NAME(ob);
974 if (tmp[0..2]!="/d/" && tmp[0..8]!="/players/") tmp=ob->short()+tmp;
975 return ob_anz[tmp];
976}
977
978// benutzt von trading_price::QuerySellValue(object, object)
979static varargs int QueryValue(object ob, int value, object client)
980{
981 int new_value, mymoney;
982
983 if (!objectp(ob)) return 0;
984 if (Query("laden::compat")) {
985 new_value=(value>1000?1000:value);
986 mymoney = QueryProp(P_CURRENT_MONEY);
987 if (new_value>mymoney)
988 return (mymoney>0?mymoney:0);
989 else return new_value;
990 }
991 return ::QueryValue(ob, value, client);
992}
993
994void reset()
995{
996 mixed *keys;
997 int i;
998
999 trading_price::reset();
1000
1001 if (!mappingp(ob_anz))
1002 return;
1003 keys=m_indices(ob_anz);
1004 for (i=sizeof(keys)-1;i>=0;i--) {
1005 ob_anz[keys[i]]=ob_anz[keys[i]]*7/8;
1006 if (!ob_anz[keys[i]])
1007 m_delete(ob_anz,keys[i]);
1008 }
1009}
1010
1011varargs int CheckFindRestrictions(object ob, mixed restr, closure qp) {
1012 return 0;
1013}
1014
1015int EvalWeapon(object ob, closure qp) {
1016 int wc,val;
1017
1018 wc=funcall(qp,P_WC);
1019 val=funcall(qp,P_EFFECTIVE_WC);
1020 if (val>wc) wc=val;
1021 return wc;
1022}
1023
1024varargs object FindBestWeapon(mixed type, int maxmon, int maxw, int hands,
1025 int bestwc, mixed restr) {
1026 object bestob,ob;
1027 string otype;
1028 int wc,bestval,val,w,bestw;
1029 closure qp;
1030
1031 if (!stringp(storage) || !objectp(ob=find_object(storage))) return 0;
1032 if (!maxmon) maxmon=100000;
1033 if (!maxw) maxw=75000;
1034 if (!hands) hands=2;
1035 if (val=QueryBuyFact()) maxmon=(maxmon*100)/val;
1036 if (type && !pointerp(type) && !mappingp(type)) type=({type});
1037
1038 for (ob=first_inventory(ob);ob;ob=next_inventory(ob)) {
1039 qp=symbol_function("QueryProp",ob);
1040 if (!otype=funcall(qp,P_WEAPON_TYPE)) continue;
1041 if (type && member(type,otype)<0) continue;
1042 wc=EvalWeapon(ob,qp);
1043 if (wc<bestwc) continue;
1044 if (funcall(qp,P_NR_HANDS)>hands) continue;
1045 w=funcall(qp,P_WEIGHT);
1046 if (w>maxw) continue;
1047 val=funcall(qp,P_VALUE);
1048 if (val>maxmon) continue;
1049 if (bestob && wc<=bestwc) {
1050 if (val>bestval) continue;
1051 else if (val==bestval && w>bestw) continue;
1052 }
1053 if (val>bestval && bestob && wc<=bestwc) continue;
1054 if (CheckFindRestrictions(ob,restr,qp)) continue;
1055 bestob=ob;
1056 bestwc=wc;
1057 bestval=val;
1058 bestw=w;
1059 }
1060 return bestob;
1061}
1062
1063int EvalArmour(object ob,closure qp) {
1064 int ac,val;
1065
1066 ac=funcall(qp,P_AC);
1067 val=funcall(qp,P_EFFECTIVE_AC);
1068 if (val>ac) ac=val;
1069 return ac;
1070}
1071
1072varargs mapping FindBestArmoursT(mixed type, int maxmon, int maxw,
1073 mapping bestac, mixed restr) {
1074 object ob;
1075 string otype;
1076 int ac,val,sum,w,wsum;
1077 mapping bestob,bestval,bestw;
1078 closure qp;
1079
1080 if (!stringp(storage) || !objectp(ob=find_object(storage))) return ([]);
1081 if (!maxmon) maxmon=100000;
1082 if (!maxw) maxw=75000;
1083 if (val=QueryBuyFact()) maxmon=(maxmon*100)/val;
1084 if (type && !pointerp(type) && !mappingp(type)) type=({type});
1085 if (!mappingp(bestac)) bestac=([]);
1086 bestob=([]);bestval=([]);bestw=([]);
1087
1088 for (ob=first_inventory(ob);ob;ob=next_inventory(ob)) {
1089 qp=symbol_function("QueryProp",ob);
1090 if (!otype=funcall(qp,P_ARMOUR_TYPE)) continue;
1091 if (type && member(type,otype)<0) continue;
1092 ac=EvalArmour(ob,qp);
1093 if (ac<bestac[otype]) continue;
1094 w=funcall(qp,P_WEIGHT);
1095 if (wsum-bestw[otype]+w>maxw) continue;
1096 val=funcall(qp,P_VALUE);
1097 if (sum-bestval[otype]+val>maxmon) continue;
1098 if (bestob[otype] && ac<=bestac[otype]) {
1099 if (val>bestval[otype]) continue;
1100 else if (val==bestval[otype] && w>bestw[otype]) continue;
1101 }
1102 if (CheckFindRestrictions(ob,restr,qp)) continue;
1103 sum=sum-bestval[otype]+val;
1104 wsum=wsum-bestw[otype]+w;
1105 bestob[otype]=ob;
1106 bestac[otype]=ac;
1107 bestval[otype]=val;
1108 bestw[otype]=w;
1109 }
1110 return bestob;
1111}
1112
1113varargs object *FindBestArmours(mixed type, int maxmon, int maxw,
1114 mapping bestac, mixed restr) {
1115 return m_values(FindBestArmoursT(type,maxmon,maxw,bestac,restr));
1116}