blob: 880aadb7f5c0fdce80bc5cb136f270da4f26ef69 [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{
MG Mud User88f12472016-06-24 23:31:02 +020038 // Achtung, bei solchen Objekten muss die Blueprint initialisiert werden!
39 if (!str) return;
40 if (!val) val=str->QueryProp(P_VALUE);
41 if (!ids)
42 {
43 if (str->QueryProp("u_ids")) // units haben keine P_IDS
44 ids=str->QueryProp("u_ids")[0];
45 else
46 ids=str->QueryProp(P_IDS);
47 }
48 if (!pointerp(ids))
49 {
50 if (stringp(ids))
51 ids=({ids});
52 else
53 ids=({});
54 }
55
56 fixed_obj += ({str});
57 fixed_value[str] = val;
58 // Alle IDs entfernen, die Sonderzeichen enthalten. Die koennte ein Spieler
59 // bei "kaufe" ohnehin nicht eingeben.
60 ids -= regexp(ids, "[\b\n\r\t]");
61 foreach(string id : ids)
62 {
63 // Nur IDs aufnehmen, die keine Grossbuchstaben enthalten, da Spieler
64 // diese ebenfalls nicht eingeben koennte.
65 if ( lowerstring(id) == id )
66 fixed_ids[id]=str;
67 }
68}
69
70void RemoveFixedObject(string filename)
71{
72 if( !stringp(filename) || !sizeof(fixed_obj))
73 return;
74 if( member(fixed_obj, filename)==-1 )
75 return;
76
77 fixed_obj -= ({ filename });
78 m_delete(fixed_value, filename);
79
80 foreach(string id, string file : fixed_ids)
81 {
82 if ( file == filename )
83 m_delete(fixed_ids, id);
84 }
85}
86
87static string SetStorageRoom(string str)
88{
89 if (str && stringp(str)) return storage=str;
90 return 0;
91}
92
93string QueryStorageRoom()
94{ return storage; }
95
96protected void create()
97{
98 object router;
99
100 if (object_name(this_object()) == __FILE__[0..<3])
101 {
102 set_next_reset(-1);
103 return;
104 }
105
106 trading_price::create();
107
108 SetProp( P_NAME, "Haendler" );
109 SetProp( P_GENDER, MALE );
110 SetProp( P_ROOM_TYPE, QueryProp(P_ROOM_TYPE) | RT_SHOP );
111
112 AddCmd("zeige","list");
113 AddCmd(({"kauf","kaufe","bestell","bestelle"}),"buy");
114 AddCmd(({"verkauf","verkaufe","verk"}),"sell");
115 AddCmd(({"versetz","versetze"}),"force_sell");
116 AddCmd(({"schaetz","schaetze"}),"evaluate");
117 AddCmd(({"untersuche","unt"}), "show_obj");
118
119 SetTradingData(50000,300,3);
120
121 ob_anz=([]);
122 fixed_obj=({});fixed_value=([]);fixed_ids=([]);
123
124 AddFixedObject(BOERSE, 80,({ "boerse","geldboerse"}));
125
126 if (!clonep(ME) && objectp(router=find_object(ROUTER)))
127 router->RegisterTarget(TARGET_SHOP,object_name(ME));
128}
129
130protected void create_super() {
131 set_next_reset(-1);
132}
133
134// Legacy-Version von GetShopItems() fuer die erbenden Laeden, die auf
135// die Funktion in dieser Form angewiesen sind.
136static mixed *GetList()
137{
138 object store = load_object(storage);
139 store->_register_shop(ME);
140
141 mixed *output=({});
142 if (!objectp(store))
143 return output;
144
145 mixed tmp = map(fixed_obj, #'load_object)+all_inventory(store);
146 mapping tmp2 = ([]);
147 mixed str;
148 string comp;
149 int i;
150 int s=1;
151
152 for (i=sizeof(tmp)-1 ; i>=0 ; i--)
153 {
154 str = ({ ({ sprintf("%-25.25s%7.7d",
155 (tmp[i]->short()||"???")[0..<3],
156 QueryBuyValue(tmp[i], PL)),
157 tmp[i] }) });
158 comp=str[0][0][0..25];
159 if (!tmp2[comp])
160 {
161 tmp2[comp] = s++;
162 output += str;
163 }
164 else output[tmp2[comp]-1][0] = str[0][0];
165 }
166 return output;
167}
168
169// Legacy-Version von PrintList() fuer die erbenden Laeden, die auf
170// die Funktion in dieser Form angewiesen sind.
171static int DoList(string query_fun)
172{
173 mixed* output=GetList();
174 int si = sizeof(output);
175 if (!si)
176 {
177 write("Im Moment sind wir leider VOELLIG ausverkauft!\n");
178 return 1;
179 }
180 string out="";
181 int i;
182 int indent;
183 for (i=0; i<si; i++)
184 {
185 if (call_other(ME, query_fun, output[i][1]))
186 {
187 indent = !indent;
188 out += sprintf("%3d. %s", i+1, output[i][0]);
189 if (!indent)
190 out += "\n";
191 else out += " | ";
192 }
193 }
194 if (indent)
195 out+="\n";
196 PL->More(out);
197 return 1;
198}
199
200// Liefert eine Liste der Objekte zurueck, die gerade im Storage liegen,
201// pro Blueprint jeweils eins.
202protected object* GetShopItems()
203{
204 object store = load_object(storage);
205 store->_register_shop(ME);
MG Mud User88f12472016-06-24 23:31:02 +0200206 object* all_items = all_inventory(store);
207
208 // Wir brauchen eine Liste, die von jeder Blueprint nur einen Clone
209 // enthaelt. Daher werden die Ladenamen der Objekte als Keys im Mapping
210 // <items> verwendet und jeweils der aktuelle Clone als Value zugeordnet.
211 mapping items = m_allocate(sizeof(all_items));
212 foreach(object ob: all_items)
213 {
214 items[load_name(ob)] = ob;
215 }
216 // Die Fixed Objects werden ans Ende angehaengt, damit in dem Fall, dass
217 // ein Clone eines solchen Objektes im Lager liegt, dieser zuerst verkauft
218 // wird und nicht immer wieder ein neuer erstellt wird.
219 return m_values(items) + map(fixed_obj, #'load_object);
220}
221
MG Mud User88f12472016-06-24 23:31:02 +0200222// Kuemmert sich um die Listenausgabe fuer den Befehl "zeige"
223varargs protected int PrintList(string filter_fun, int liststyle)
224{
225 // Alle Items im Lager holen. Wenn keine vorhanden, tut uns das leid.
226 object *items_in_store = GetShopItems();
227 if ( !sizeof(items_in_store) ) {
228 write("Im Moment sind wir leider VOELLIG ausverkauft!\n");
229 return 1;
230 }
231
232 // Das Listenformat ist von der Spielereingabe abhaengig. Wurde "lang"
233 // angefordert, geben wir einspaltig aus mit groesserer Spaltenbreite.
234 // Die Spaltenbreite wird dabei von dem Item mit dem laengsten Namen im
235 // gesamten Shop-Inventar bestimmt, damit nicht bei jeder Teilliste
236 // (Waffen, Ruestungen, Verschiedenes) unterschiedliche Breiten rauskommen.
237 //
238 // Der erste Parameter enthaelt die Katalognummer des Items, der zweite
239 // die Kurzbeschreibung, der dritte den Preis.
240 string listformat = "%3d. %-25.25s %6.6d";
241 if ( liststyle == LIST_LONG )
242 {
243 string* names = sort_array(
244 items_in_store->short(),
245 function int (string s1, string s2) {
246 return (sizeof(s1) < sizeof(s2));
247 });
248 // Wenn einspaltig ausgegeben wird, soll die Liste nicht beliebig breit
249 // werden. Daher wird die Short auf 65 Zeichen limitiert.
250 int len = 65;
251 if ( sizeof(names) )
252 len = min(len, sizeof(names[0]));
253 listformat = "%3d. %-"+len+"."+len+"s %6.6d";
254 }
255
256 string out="";
257 // Variablen, die innerhalb der Schleife benoetigt werden.
258 string kurz;
259 int indent, preis;
260 object item;
261 // Ueber die Liste laufen. <i> wird benoetigt, um die ausgegebene Liste
262 // konsistent numerieren zu koennen, damit kaufe <nr> funktioniert.
263 foreach(int i : sizeof(items_in_store))
264 {
265 item = items_in_store[i];
266 if ( call_other(ME, filter_fun, item) )
267 {
268 // Kurzbeschreibung und Preis ermitteln. Items ohne Short werden
269 // als "?" angezeigt.
270 kurz = (item->short() || "???")[0..<3];
271 preis = QueryBuyValue(item, PL);
272 // Beschreibung des Items anfuegen.
273 out += sprintf(listformat, i+1, kurz, preis);
274 indent = !indent;
275 // Wenn indent gesetzt ist, handelt es sich um das linke Item in der
276 // zweispaltigen Liste, dann fuegen wir einen Spaltentrenner an,
277 // ansonsten ist es das rechte, dann brechen wir um.
278 // Gilt natuerlich nur fuer kurze Listen.
279 out += ((indent && liststyle==LIST_SHORT)? " | " : "\n");
280 }
281 }
282 // Wenn die Liste eine ungerade Zahl Items enthaelt, ist in der letzten
283 // Zeile links ein Item aufgefuehrt, daher muss danach umbrochen werden.
284 // Gilt natuerlich nur fuer kurze Listen
285 if (indent && liststyle==LIST_SHORT)
286 out+="\n";
287
288 // Vor den Listen wird eine Info-Zeile ausgegeben, um gefilterte Listen
289 // kenntlich zu machen. Wird nach der Filterung des Inventars erzeugt,
290 // um eine leere Meldung ausgeben zu koennen, wenn nach Filterung nichts
291 // mehr uebrigbleibt.
292 string was;
293 switch(filter_fun)
294 {
295 case "IsArmour": was = "Ruestungen"; break;
296 case "IsWeapon": was = "Waffen"; break;
297 case "NoWeaponNoArmour":
298 was = (out==""?"sonstigen Waren":"sonstige Waren"); break;
299 default: was = "Waren"; break;
300 }
301 // <out> ist ein Leerstring, wenn keine Waren da sind, die dem Filterkri-
302 // terium entsprechen. Dann gibt's eine entsprechende Meldung.
303 if ( out == "" )
304 out = sprintf("Leider sind momentan keine %s im Angebot.\n", was);
305 else
306 out = sprintf("Folgende %s kannst Du hier kaufen:\n",was) + out;
307
308 PL->More(out);
309 return 1;
310}
311
312// Hilfsfunktionen zum Filtern des Ladeninventars fuer den "zeige"-Befehl
313static int AlwaysTrue(object ob)
314{ return 1; }
315
316static string IsWeapon(object ob)
317{ return ob->QueryProp(P_WEAPON_TYPE); }
318
319static string IsArmour(object ob)
320{ return ob->QueryProp(P_ARMOUR_TYPE); }
321
322static int NoWeaponNoArmour(object ob)
323{ return (!ob->QueryProp(P_WEAPON_TYPE) && !ob->QueryProp(P_ARMOUR_TYPE)); }
324
325
326// Diese Funktion ist oeffentlich, falls Magier abfragen wollen, ob ein laden
327// ein Objekt zerstoeren wuerde. Aber: Benutzung auf eigenes Risiko! Es wird
328// nicht garantiert, dass diese Funktion bzw. ihr Interface sich nicht
329// aendert.
330public int CheckForDestruct(object ob) // Pruefen, ob zerstoert werden soll
331{
332 string str;
333 /*
334 * P_NOBUY - Objekte auf jeden Fall zerstoeren
335 */
336 if(ob->QueryProp(P_NOBUY)) return 1;
337 /*
338 * Beschaedigte Objekte werden ebenfalls zerstoert
339 */
340 if(ob->QueryProp(P_DAMAGED)) return 1;
341 /*
342 * Ruestungen wenn sie a) eine DefendFunc definiert haben oder
343 * b) ueber der in KEEP_ARMOUR_CLASS definierten AC
344 * liegen (siehe /sys/combat.h)
345 */
346 if(str = IsArmour(ob))
347 {
348 if(objectp(ob->QueryProp(P_DEFEND_FUNC))) return 1;
349 if(ob->QueryProp(P_AC) >= KEEP_ARMOUR_CLASS[str]) return 1;
350 return 0;
351 }
352 /*
353 * Waffen wenn sie a) 1-haendig sind und eine WC > 120 besitzen oder
354 * b) 2-haendig sind und eine WC > 150 besitzen oder aber
355 * c) eine HitFunc definiert haben
356 */
357 if(str = IsWeapon(ob))
358 {
359 if(ob->QueryProp(P_NR_HANDS) > 1 && ob->QueryProp(P_WC) > 150) return 1;
360 if(ob->QueryProp(P_NR_HANDS) < 2 && ob->QueryProp(P_WC) > 120) return 1;
361 if(objectp(ob->QueryProp(P_HIT_FUNC))) return 1;
362 return 0;
363 }
364 return 0;
365}
366
367static int list(string str)
368{
369 _notify_fail(
370 "Bitte 'zeige', 'zeige waffen', 'zeige ruestungen' oder\n"
371 "'zeige verschiedenes' eingeben. Wenn Du das Schluesselwort 'lang'\n"
372 "oder '-1' anhaengst, wird die Liste einspaltig ausgegeben.\n");
373
374 if (!stringp(str) || !sizeof(str) )
375 return PrintList("AlwaysTrue");
376 if ( str == "lang" || str == "-1" )
377 return PrintList("AlwaysTrue", LIST_LONG);
378
379 string *params = explode(str," ");
380 if (sizeof(params[0])<3)
381 return 0;
382
383 int liststyle = LIST_SHORT;
384 if ( sizeof(params)>1 && params[1] == "lang" )
385 liststyle = LIST_LONG;
386
387 str=params[0][0..2];
388 if (str=="waf")
389 return PrintList("IsWeapon", liststyle);
390 if (str=="ver")
391 return PrintList("NoWeaponNoArmour", liststyle);
392 if (str=="rue")
393 return PrintList("IsArmour", liststyle);
394 return 0;
395}
396/*
397static varargs int QueryBuyValue(mixed ob, object client)
398{
399 if (objectp(ob))
400 return trading_price::QueryBuyValue(ob, client);
401 return (fixed_value[ob]*QueryBuyFact(client))/100;
402}
403*/
404
405static varargs int QueryBuyValue(object ob, object client)
406{
407 int fprice = fixed_value[load_name(ob)];
408
409 return (fprice>0) ?
410 fprice*QueryBuyFact(client)/100 :
411 trading_price::QueryBuyValue(ob, client);
412}
413
414static void UpdateCounter(object ob, int num)
415{
416 string tmp;
417
418 if (!num || !objectp(ob)) return;
419 tmp=BLUE_NAME(ob);
420 if (tmp[0..2]!="/d/" && tmp[0..8]!="/players/")
421 tmp=ob->short()+tmp;
422 ob_anz[tmp] += num;
423 if (ob_anz[tmp] <= 0)
424 m_delete(ob_anz,tmp);
425}
426
427protected object FindInStore(string|int x)
428{
429 object* list = GetShopItems();
430 if ( intp(x) && x>0 && x<=sizeof(list) ) {
431 return list[x-1];
432 }
433 if (stringp(x))
434 {
435 if ( fixed_ids[x] )
436 return load_object(fixed_ids[x]);
437 list = filter_objects(list, "id", x);
438 if ( sizeof(list) )
439 return list[0];
440 // Wenn nix im Store gefunden (das schliesst eigentlicht schon die BPs der
441 // fixen Objekte ein, aber nicht, wenn diese nicht konfiguriert sind. D.h.
442 // diese Pruefung ist fuer nicht-konfigurierte BPs), Liste der
443 // FixedObjects pruefen unde so die eventuell manuell in
444 // AddFixedObject() angegebene Liste von IDs beruecksichtigen.
445 else if ( fixed_ids[x] )
446 return load_object(fixed_ids[x]);
447 }
448 return 0;
449}
450
451static string buy_obj(mixed ob, int short)
452{ return 0; }
453
454private void really_buy(int val, object pl, object ob, int u_req)
455{
456 // Staendig verfuegbare Objekte (fixed_obj) sind daran erkennbar, dass sie
457 // nicht im Lager liegen. Daher hier einen Clone erstellen, der dann
458 // stattdessen rausgegeben wird.
459 if ( !present(ob, find_object(storage)) )
460 ob = clone_object(ob);
461
462 // In Unitobjekten U_REQ (wieder) setzen (wegen input_to (bei dem sich das
463 // Kommandoverb aendert und deswegen U_REQ geloescht wird), und wegen
464 // Kaufens von Fixed-Objekt-Unitobjekten (bei diesen muss U_REQ _nach_ dem
465 // Clonen im Clone gesetzt werden, nicht automagisch in der BP durch den
466 // Aufruf von id() weiter vorher).
467 if (u_req>0)
468 {
469 // Das QueryProp() ist nicht unnoetig. Bei der Abfrage von U_REQ wird
470 // U_REQ genullt, wenn das aktuelle query_verb() != dem letzten ist.
471 // Bei der ersten Abfrage wuerde als das hier gesetzt U_REQ wieder
472 // geloescht. Daher muss das jetzt hier als erstes einmal abgefragt
473 // werden...
474 ob->QueryProp(U_REQ);
475 ob->SetProp(U_REQ, u_req);
476 }
477
478 pl->AddMoney(-val);
479 _add_money(val);
480
481 if (ob->move(pl,M_GET) != MOVE_OK) // Kann der Spieler das Objekt tragen?
482 {
483 write(break_string("Du kannst "+ob->name(WEN,1)+" nicht mehr tragen. "
484 "Ich lege "+ob->QueryPronoun(WEN)+" hier auf den Boden.",78,
485 Name(WER)+" sagt: "));
486 ob->move(ME,M_NOCHECK); // Nein :-)
487 }
488 else
489 {
490 // Falls das Objekt sich vereinigt hat, muss jetzt wieder U_REQ
491 // restauriert werden.
492 ob->SetProp(U_REQ, u_req);
493 write(break_string("Du kaufst "+ob->name(WEN,1)+".", 78));
494 }
495
496 say(break_string(PL->Name(WER)+" kauft "+ob->name(WEN)+".",78), ({PL}));
497 UpdateCounter(ob,-1);
498}
499
500static void ask_buy(string str, int val, object pl, object ob, int u_req)
501{
502 _notify_fail(break_string("Gut, Du kannst es Dir ja jederzeit "
503 "noch anders ueberlegen.",78,Name(WER)+" sagt: "));
504
505 if(!str || !stringp(str) || str == "nein" || str == "n")
506 {
507 return;
508 }
509 if(str != "ja" && str != "j")
510 {
511 return;
512 }
513 really_buy(val, pl, ob, u_req);
514}
515
516static int buy(string str)
517{
518 int i, val, par, dex;
519 mixed ob, res;
520 string dummy;
521
522 if (!str) {
523 write("Was willst Du kaufen?\n");
524 return 1;
525 }
526
527 if (stringp(QueryProp(P_KEEPER)) && !present(QueryProp(P_KEEPER), ME)) {
528 write("Es ist niemand da, der Dich bedienen koennte.\n");
529 return 1;
530 }
531
532 _notify_fail(break_string("Das kann ich in meinem Lager nicht finden.",78,
533 Name(WER)+" sagt: "));
534
535 // Um auch Teile von Unit-Stacks kaufen zu koennen, z.B. "kaufe 5 phiolen",
536 // darf hier zusaetzlich <dummy> nur ein Leerstring sein, sonst verzweigt
537 // die Syntaxpruefung hierhin und es wird das 5. Item der Liste gekauft.
538 if (sscanf(str,"%d%s",i,dummy)>0 && i>0 && !sizeof(dummy)) {
539 ob=FindInStore(i);
540 }
541 else ob=FindInStore(str);
542
543 if (!ob) return 0;
544
545 if (str = buy_obj(ob,0))
546 {
547 write(break_string(str,78,Name(WER)+" sagt: "));
548 return 1;
549 }
550
551 val = QueryBuyValue(ob,PL);
552
553 if (PL->QueryMoney() < val)
554 {
Zesstra63aba2e2021-05-07 09:30:35 +0200555 ;
556 write(break_string(sprintf(
557 "%s wuerde%s %d Muenzen kosten, und Du hast nur %d.",
558 capitalize(ob->QueryPronoun(WER)),
559 ob->QueryProp(P_PLURAL) ? "n" : "", val, PL->QueryMoney()),
560 78, Name(WER)+" bedauert: "));
MG Mud User88f12472016-06-24 23:31:02 +0200561 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}