blob: 11961291a55c56b06dd79da9e508f5550fec4c38 [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 {
Zesstra63aba2e2021-05-07 09:30:35 +0200558 ;
559 write(break_string(sprintf(
560 "%s wuerde%s %d Muenzen kosten, und Du hast nur %d.",
561 capitalize(ob->QueryPronoun(WER)),
562 ob->QueryProp(P_PLURAL) ? "n" : "", val, PL->QueryMoney()),
563 78, Name(WER)+" bedauert: "));
MG Mud User88f12472016-06-24 23:31:02 +0200564 return 1;
565 }
566
567 // Anzahl der angeforderten Einheiten vor dem Bewegen zwischenspeichern.
568 // Weil dabei im Fall von Units eine Vereinigung mit bereits im Inventar
569 // befindlichen Einheiten stattfindet, muss das ggf. nach Bewegen
570 // zurueckgesetzt werden.
571 int u_req = ob->QueryProp(U_REQ);
572
573 if ((res = ob->QueryProp(P_RESTRICTIONS)) && mappingp(res) &&
574 (res = call_other("/std/restriction_checker",
575 "check_restrictions",PL,res)) &&
576 stringp(res))
577 {
578 _notify_fail(break_string("Du koenntest "+ob->name(WEN,2)+" nicht "
579 "verwenden. Grund: "+res+"Moechtest Du "+ob->QueryPronoun(WEN)+
580 " dennoch kaufen?",78,Name(WER)+" sagt: "));
581
582 input_to("ask_buy",INPUT_PROMPT, "(ja/nein) ", val,PL,ob,u_req);
583 return 0;
584 }
585
Arathorn26e02d32019-12-02 23:55:09 +0100586 par = ob->QueryProp(P_PARRY);
587 dex = PL->QueryAttribute(A_DEX);
MG Mud User88f12472016-06-24 23:31:02 +0200588
589 if ((((par < PARRY_ONLY) && ((dex + 8) * 10) < ob->QueryProp(P_WC)) ||
590 ((par > PARRY_NOT) && ((dex + 5) * 2) < ob->QueryProp(P_AC))) &&
591 VALID_WEAPON_TYPE(ob))
592 {
593 _notify_fail(break_string("Du koenntest "+ob->name(WEN,2)+" nicht "
594 "zuecken, da Deine Geschicklichkeit dafuer nicht ausreicht. Moechtest "
595 "Du "+ob->QueryPronoun(WEN)+" dennoch kaufen?",78,
596 Name(WER)+" sagt: "));
597
598 input_to("ask_buy",INPUT_PROMPT, "(ja/nein) ", val,PL,ob,u_req);
599 return 0;
600 }
601
602 really_buy(val, PL, ob, u_req);
603
604 return 1;
605}
606
607private void give_money(int value)
608// Geld gutschreiben...
609{
610 if (!value) return;
611 write(break_string(Name(WER, 1)+" zahlt Dir "+value+" Goldstueck"
612 +(value==1?".":"e."), 78));
613 if ((PL->AddMoney(value))<=0) {
614 object mon;
615
616 write("Du kannst das Geld nicht mehr tragen!\n");
617 mon=clone_object(GELD);
618 mon->SetProp(P_AMOUNT,value);
619 mon->move(ME,M_MOVE_ALL|M_NOCHECK);
620 }
621}
622
623static int make_to_money(object ob, int value)
624// Interne Funktion, die ob versucht in das Lager zu ueberfuehren und das
625// Geld das dabei fuer den Spieler abfaellt zurueckliefert.
626{
MG Mud User88f12472016-06-24 23:31:02 +0200627 int ret;
628
629 if (!objectp(ob) || environment(ob)==find_object(storage)) {
630 write(break_string(Name(WER, 1)+" wundert sich ueber Dich.", 78));
631 return 0;
632 }
633 if (value>QueryProp(P_CURRENT_MONEY)) {
634 write(break_string("Ich hab das Geld leider nicht mehr.", 78,
635 Name(WER, 1)+" sagt: "));
636 return 0;
637 }
638 // U_REQ merken, falls sich Objekte vereinigen. Sonst stimmt nicht nur der
639 // Name, sondern es werden ggf. auch die falsche Anzahl zerstoert.
640 //TOOO: Oder Units entsorgen und als Feature deklarieren?
641 int req = ob->QueryProp(U_REQ);
642 if (CheckForDestruct(ob) > 0) // soll ob zerstoert werden?
643 {
644 ret = ob->move(storage,M_PUT|M_GET);
645 // Falls das Objekt sich vereinigt hat (Units), muessen die gewuenschten
646 // Einheiten restauriert werden.
647 // Problem: falls das verkaufte Objekt Units hat, beschaedigt ist und sich
648 // vereinigt hat, sind jetzt leider alle Einheiten im Lager beschaedigt.
649 // Das ist unschoen - aber mir jetzt zuviel AUfwand, das korrekt zu bauen,
650 // weil es nur sehr selten vorkommt. (Hint: separater Muellraum)
651 ob->SetProp(U_REQ, req);
652 if (ret > 0) // Sonst werden auch Sachen zerstoert, die man nicht
653 { // weglegen kann
654 say(break_string(PL->Name()+" verkauft "+ob->name(WEN)+".", 78));
655 if(ob->QueryProp(P_DAMAGED)) // Andere Meldung bei beschaedigten
656 { // Objekten ...
657 write(break_string(Name(WER,1)+" findet zwar keinen Gefallen an "
658 +ob->name(WEM,1)+", nimmt "+ob->QueryPronoun(WEN)+" Dir zuliebe "
659 "aber trotzdem.",78));
660 }
661 else
662 {
663 write(break_string(Name(WER, 1)+" findet Gefallen an "
664 +ob->name(WEM, 1) + " und legt "+ob->QueryPronoun(WEN)
665 +" zu "+(QueryProp(P_GENDER)==FEMALE?"ihren":"seinen")
666 +" Privatsachen.", 78));
667 }
668 /* Er zahlt Dir "+value+" Muenze"+(value==1?"":"n")+" dafuer.", 78)); */
669 _add_money(-value);
670 _add_money(value*QueryProp(P_SHOP_PERCENT_LEFT)/100); // Wegen Zerstoerung des Objektes
671 UpdateCounter(ob,1);
672 ob->remove(1);
673 return value;
674 }
Arathorna1fc9352019-12-03 00:06:39 +0100675 else if (ret == ME_CANT_BE_DROPPED)
676 {
677 string|int nodrop = ob->QueryProp(P_NODROP);
678 if (stringp(nodrop) && sizeof(nodrop))
679 write(nodrop);
680 else
681 write(break_string("Du kannst "+ob->name(WEN,1)+" nicht verkaufen!",
682 78));
MG Mud User88f12472016-06-24 23:31:02 +0200683 return 0;
684 }
685 else
686 write(break_string(ob->Name(WER)+" interessiert mich nicht.", 78,
687 Name(WER, 1)+" sagt: "));
688 }
689 else if ((ret=(ob->move(storage,M_PUT|M_GET)))>0)
690 {
691 // Falls das Objekt sich vereinigt hat (Units), muessen die gewuenschten
692 // Einheiten restauriert werden.
693 ob->SetProp(U_REQ, req);
694 say(break_string(PL->Name(WER)+" verkauft "+ob->name(WEN)+".", 78));
695 _add_money(-value);
696 UpdateCounter(ob,1);
697 return value;
698 }
Arathorna1fc9352019-12-03 00:06:39 +0100699 else if (ret == ME_CANT_BE_DROPPED)
700 {
701 string|int nodrop = ob->QueryProp(P_NODROP);
702 if (stringp(nodrop) && sizeof(nodrop))
703 write(nodrop);
704 else
705 write(break_string("Du kannst "+ob->name(WEN,1)+" nicht verkaufen!",
706 78));
707 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200708 }
Arathorna1fc9352019-12-03 00:06:39 +0100709 else
710 write(break_string("Du kannst "+ob->name(WEN,1)+" nicht verkaufen!",
711 78));
MG Mud User88f12472016-06-24 23:31:02 +0200712 return 0;
713}
714
715static void ask_sell(string str, object ob, int val, int u_req)
716// Wenn ein einzelnen Stueck unter Wert verkauft werden soll, wird nachgefragt
717// u_req ist bei Unitobjekten die Anzahl an zu verkaufenden Einheiten. Bei
718// normalen Objekten ist u_req 0.
719{
720 str=lower_case(str||"");
721 if (str=="ja"||str=="j")
722 {
723 // In Unitobjekten U_REQ (wieder) setzen.
724 if (u_req>0)
725 {
726 // Das QueryProp() ist nicht unnoetig. Bei der Abfrage von U_REQ wird
727 // U_REQ genullt, wenn das aktuelle query_verb() != dem letzten ist.
728 // Bei der ersten Abfrage wuerde als das hier gesetzt U_REQ wieder
729 // geloescht. Daher muss das jetzt hier als erstes einmal abgefragt
730 // werden...
731 ob->QueryProp(U_REQ);
732 ob->SetProp(U_REQ, u_req);
733 }
734 give_money(make_to_money(ob,val));
735 }
736 else
737 write(break_string("Ok, dann behalts!", 78,
738 Name(WER, 1)+" sagt: "));
739}
740
741static string sell_obj(object ob, int short)
742// Ist der Haendler bereit ob zu kaufen? wenn nein, Rueckgabe einer Meldung,
743// die der Haendler sagen soll.
744{ mixed nosell;
745
746 if (BLUE_NAME(ob)==GELD)
747 return "Das waere ja wohl Unsinn, oder ...?";
748 if (nosell=ob->QueryProp(P_NOSELL))
749 {
750 if (stringp(nosell))
751 return nosell;
752 return ("Du kannst "+ob->name(WEN,1)+" nicht verkaufen!");
753 }
754 if (ob->QueryProp(P_CURSED))
755 return ob->Name(WER,1)
756 +" ist mir irgendwie ungeheuer! Das kannst Du nicht verkaufen!";
757 // man sollte keine COntainer mit Inhalt verkaufen koennen, ggf. kauft sie
758 // dann ein anderer Spieler.
759 if (first_inventory(ob))
760 {
761 return ob->Name(WER, 1) + " ist nicht leer!";
762 }
763 return 0;
764}
765
766static varargs int sell(string str, int f)
767{
768 int i, val, oval, tmp;
769 object *obs;
770
771 if (stringp(QueryProp(P_KEEPER)) && !present(QueryProp(P_KEEPER),ME)) {
772 write("Es ist niemand da, der Dich bedienen koennte.\n");
773 return 1;
774 }
775
776 if (!str) {
777 notify_fail("Was moechtest Du denn verkaufen?\n");
778 return 0;
779 }
780
781 /* Ergebnis von find_obs() sollte unifiziert werden, damit ein mehrfach
782 gefundenes Objekt nicht mehrfach versucht wird zu verkaufen.
783 Beispiel: Objekt hat P_NOBUY gesetzt und mehrere IDs gesetzt. Wenn
784 ein Spieler es mit "verkaufe ID1 und ID2" versucht zu verkaufen,
785 wuerde das einen Bug ausloesen. Derselbe Bug entsteht, wenn man mit
786 "verkaufe ID1 und ID1" verkauft. */
787 obs = PL->find_obs(str, PUT_GET_DROP);
788 /* Erst im Inventar schauen, dann im Environment. find_obs() ohne 2.
789 Parameter macht das standardmaessig andersherum.
790 TODO: Aenderung ueberpruefen, sobald das neue put_and_get.c
791 eingebaut wurde. */
792 if ( !sizeof(obs) )
793 obs = PL->find_obs(str, PUT_GET_TAKE) || ({});
794 obs = m_indices(mkmapping(obs));
795 if (!i=sizeof(obs)) {
796 notify_fail("Was moechtest Du denn verkaufen?\n");
797 return 0;
798 }
799 call_other(storage, "_register_shop", ME);
800 if (i==1) {
801 if (str=sell_obj(obs[0], 0)) {
802 write(break_string(str, 78, Name(WER, 1)+" sagt: "));
803 return 1;
804 }
805 if ((oval=obs[0]->QueryProp(P_VALUE))<=0) {
806 write(break_string(obs[0]->Name(WER)
807 +(obs[0]->QueryProp(P_PLURAL) ? " haben" : " hat")
808 +" keinen materiellen Wert, tut mir leid.", 78,
809 Name(WER, 1)+" sagt: "));
810 return 1;
811 }
812 val=QuerySellValue(obs[0], PL);
813 if (!val) {
814 write(break_string("Ich bin absolut pleite. Tut mir aufrichtig leid.",
815 78, Name(WER, 1)+" sagt: "));
816 return 1;
817 }
818 if (val==oval || f) {
819 give_money(make_to_money(obs[0], val));
820 return 1;
821 }
Arathornc7237e72020-03-09 22:32:27 +0100822 string|int nodrop = obs[0]->QueryProp(P_NODROP);
823 if (nodrop) {
824 if (stringp(nodrop))
825 write(nodrop);
826 else
827 write(break_string(
828 "Du kannst "+obs[0]->name(WEN,1)+" nicht verkaufen!", 78));
MG Mud User88f12472016-06-24 23:31:02 +0200829 return 1;
830 }
831
832 if (obs[0]->QueryProp(P_DAMAGED)) // Bei beschaedigten Objekten gibt
833 { // es auch hier eine andere Meldung
834 write(break_string("Da "+obs[0]->name(WER)+" beschaedigt "
835 +(obs[0]->QueryProp(P_PLURAL)?"sind":"ist")+", kann ich Dir "
836 "nur "+val+" Muenze"+(val == 1?"":"n")+" dafuer bieten. Und "
837 "damit mache ich noch Verlust! Nimmst Du mein Angebot an? "
838 "(ja/nein)",78,Name(WER,1)+" sagt: "));
839 }
840 else // Default
841 {
842 write(break_string(Name(WER, 1)+" sagt: "
843 +"Nach der aktuellen Marktlage kann ich Dir dafuer nur "
844 +val+" Muenze"+(val==1?"":"n")+" bieten, obwohl "
845 +obs[0]->name(WER)+" eigentlich "+oval+" Muenze"
846 +(oval==1?"":"n")+" wert waere. Willst Du "
847 +(QueryProp(P_PLURAL) ? "sie" : "es")
848 +" mir dafuer ueberlassen?", 78));
849 }
850 // in ask_sell() gibt es das query_verb() nicht mehr, weswegen U_REQ in
851 // Unitobjekten zurueckgesetzt wird. Damit geht die info verloren,
852 // wieviele Objekte der Spieler angegeben hat. Diese muss gerettet und
853 // via ask_sell() in make_to_money() ankommen. In normalen Objekten ist
854 // U_REQ 0.
855 input_to("ask_sell",INPUT_PROMPT, "(ja/nein) ",obs[0], val,
856 (obs[0])->QueryProp(U_REQ) );
857 return 1;
858 }
859 for (--i; i>=0 && get_eval_cost()>50000; i--) {
860 if (oval=obs[i]->QueryProp(P_VALUE)) {
861 if (obs[i]->QueryProp(P_KEEP_ON_SELL)==getuid(PL)
862 || obs[i]->QueryProp(P_WORN) || obs[i]->QueryProp(P_WIELDED))
863 write(break_string(obs[i]->Name(WER)+": Du behaeltst "
864 +obs[i]->name(WEN)+".", 78));
865 else if (str=sell_obj(obs[i], 1))
866 write(break_string(obs[i]->Name(WER)+": "+str, 78));
867 else {
868 tmp=QuerySellValue(obs[i], PL);
869 if (!tmp) {
870 write(break_string(
871 "Ich bin absolut pleite. Tut mir aufrichtig leid.", 78,
872 Name(WER, 1)+" sagt: "));
873 break;
874 }
875 else if (!f && tmp*10<oval)
876 write(break_string(obs[i]->Name(WER)+": "+Name(WER, 1)
877 +" bietet Dir aber nur "+tmp+" Goldstueck"
878 +(tmp>1 ? "e" : "")+" dafuer.", 78));
879 else {
880 str=(obs[i]->Name(WER));
881 if (tmp=make_to_money(obs[i], tmp)) {
882 write(break_string(str+": "+Name(WER, 1)
883 +" gibt Dir dafuer "+tmp+" Goldstueck"
884 +(tmp==1?".":"e."), 78));
885 val+=tmp;
886 }
887 }
888 }
889 }
890 }
891 if (!val)
892 write(break_string("Hmmm, Du hast aber nicht besonders viel zu bieten...",
893 78, Name(WER)+" sagt: "));
894 else give_money(val);
895 return 1;
896}
897
898static int force_sell(string str)
899{ return sell(str, 1); }
900
901static int evaluate(string str)
902{
903 object ob;
904 int val,rval;
905
906 if (!str) return 0;
907 if(stringp(QueryProp(P_KEEPER)) && !present(QueryProp(P_KEEPER), ME)) {
908 write("Es ist niemand da, der Dich bedienen koennte.\n");
909 return 1;
910 }
911
912 ob=present(str, ME);
913 if (!ob) ob=deep_present(str,PL);
914 if (!ob) {
915 write("Hm? "+capitalize(str)+"? Wovon redest Du?\n");
916 return 1;
917 }
918 if (living(ob)) {
919 _notify_fail("Nanu, seit wann werden hier Lebewesen verkauft?\n");
920 return 0;
921 }
922 if (str=sell_obj(ob, 0)) {
923 write(break_string(str, 78, Name(WER)+" sagt: "));
924 return 1;
925 }
926 rval=ob->QueryProp(P_VALUE);
927 if (rval) {
928 val=QuerySellValue(ob, PL);
929 if (rval==val) {
930 tell_object(this_player(),break_string(
931 "Naja, ich denke, " +val+ " Muenze"
932 + (val==1 ? "" : "n")
933 + " waere"+(ob->QueryProp(P_AMOUNT)>1?"n ":" ")
934 + (ob->QueryPronoun(WER))+" schon wert.\n",78));
935 }
936 else if (val) {
937 tell_object(this_player(),break_string(
938 "Oh, nach der aktuellen Marktlage kann ich nur "+val+" Muenze"+
939 (val==1?"":"n")+" bezahlen, obwohl "
940 + (QueryProp(P_PLURAL) ? "sie" : "es")
941 + " eigentlich "+rval
942 + " Muenze"+(rval==1?"":"n")+" wert ist.\n",78));
943 }
944 else write("Ich bin vollkommen pleite. Tut mir leid.\n");
945 }
946 else write("Das ist voellig wertlos.\n");
947 return 1;
948}
949
950static int show_obj(string str)
951{
952 int i;
953 string was;
954 mixed ob;
955
956 if (!str) return 0;
957 if (sscanf(str,"%s im laden",was)>0 || sscanf(str,"%s in laden",was)>0) {
958 _notify_fail("Das kann ich im Lager nicht finden.\n");
959 ob=FindInStore(was);
960 } else if (sscanf(str,"%d",i)>0 && i>0) {
961 _notify_fail("Das kann ich im Lager nicht finden.\n");
962 ob=FindInStore(i);
963 }
964 if (!ob) return 0;
965 write(ob->Name(WER)+":\n"+ob->long()+capitalize(ob->QueryPronoun())
966 +" kostet "+QueryBuyValue(ob,PL)+" Muenzen.\n");
967 return 1;
968}
969
970// benutzt von trading_price::QueryValue(object, int, object)
971static int ObjectCount(object ob)
972{
973 string tmp;
974
975 if (!objectp(ob)) return 0;
976 tmp = BLUE_NAME(ob);
977 if (tmp[0..2]!="/d/" && tmp[0..8]!="/players/") tmp=ob->short()+tmp;
978 return ob_anz[tmp];
979}
980
981// benutzt von trading_price::QuerySellValue(object, object)
982static varargs int QueryValue(object ob, int value, object client)
983{
984 int new_value, mymoney;
985
986 if (!objectp(ob)) return 0;
987 if (Query("laden::compat")) {
988 new_value=(value>1000?1000:value);
989 mymoney = QueryProp(P_CURRENT_MONEY);
990 if (new_value>mymoney)
991 return (mymoney>0?mymoney:0);
992 else return new_value;
993 }
994 return ::QueryValue(ob, value, client);
995}
996
997void reset()
998{
999 mixed *keys;
1000 int i;
1001
1002 trading_price::reset();
1003
1004 if (!mappingp(ob_anz))
1005 return;
1006 keys=m_indices(ob_anz);
1007 for (i=sizeof(keys)-1;i>=0;i--) {
1008 ob_anz[keys[i]]=ob_anz[keys[i]]*7/8;
1009 if (!ob_anz[keys[i]])
1010 m_delete(ob_anz,keys[i]);
1011 }
1012}
1013
1014varargs int CheckFindRestrictions(object ob, mixed restr, closure qp) {
1015 return 0;
1016}
1017
1018int EvalWeapon(object ob, closure qp) {
1019 int wc,val;
1020
1021 wc=funcall(qp,P_WC);
1022 val=funcall(qp,P_EFFECTIVE_WC);
1023 if (val>wc) wc=val;
1024 return wc;
1025}
1026
1027varargs object FindBestWeapon(mixed type, int maxmon, int maxw, int hands,
1028 int bestwc, mixed restr) {
1029 object bestob,ob;
1030 string otype;
1031 int wc,bestval,val,w,bestw;
1032 closure qp;
1033
1034 if (!stringp(storage) || !objectp(ob=find_object(storage))) return 0;
1035 if (!maxmon) maxmon=100000;
1036 if (!maxw) maxw=75000;
1037 if (!hands) hands=2;
1038 if (val=QueryBuyFact()) maxmon=(maxmon*100)/val;
1039 if (type && !pointerp(type) && !mappingp(type)) type=({type});
1040
1041 for (ob=first_inventory(ob);ob;ob=next_inventory(ob)) {
1042 qp=symbol_function("QueryProp",ob);
1043 if (!otype=funcall(qp,P_WEAPON_TYPE)) continue;
1044 if (type && member(type,otype)<0) continue;
1045 wc=EvalWeapon(ob,qp);
1046 if (wc<bestwc) continue;
1047 if (funcall(qp,P_NR_HANDS)>hands) continue;
1048 w=funcall(qp,P_WEIGHT);
1049 if (w>maxw) continue;
1050 val=funcall(qp,P_VALUE);
1051 if (val>maxmon) continue;
1052 if (bestob && wc<=bestwc) {
1053 if (val>bestval) continue;
1054 else if (val==bestval && w>bestw) continue;
1055 }
1056 if (val>bestval && bestob && wc<=bestwc) continue;
1057 if (CheckFindRestrictions(ob,restr,qp)) continue;
1058 bestob=ob;
1059 bestwc=wc;
1060 bestval=val;
1061 bestw=w;
1062 }
1063 return bestob;
1064}
1065
1066int EvalArmour(object ob,closure qp) {
1067 int ac,val;
1068
1069 ac=funcall(qp,P_AC);
1070 val=funcall(qp,P_EFFECTIVE_AC);
1071 if (val>ac) ac=val;
1072 return ac;
1073}
1074
1075varargs mapping FindBestArmoursT(mixed type, int maxmon, int maxw,
1076 mapping bestac, mixed restr) {
1077 object ob;
1078 string otype;
1079 int ac,val,sum,w,wsum;
1080 mapping bestob,bestval,bestw;
1081 closure qp;
1082
1083 if (!stringp(storage) || !objectp(ob=find_object(storage))) return ([]);
1084 if (!maxmon) maxmon=100000;
1085 if (!maxw) maxw=75000;
1086 if (val=QueryBuyFact()) maxmon=(maxmon*100)/val;
1087 if (type && !pointerp(type) && !mappingp(type)) type=({type});
1088 if (!mappingp(bestac)) bestac=([]);
1089 bestob=([]);bestval=([]);bestw=([]);
1090
1091 for (ob=first_inventory(ob);ob;ob=next_inventory(ob)) {
1092 qp=symbol_function("QueryProp",ob);
1093 if (!otype=funcall(qp,P_ARMOUR_TYPE)) continue;
1094 if (type && member(type,otype)<0) continue;
1095 ac=EvalArmour(ob,qp);
1096 if (ac<bestac[otype]) continue;
1097 w=funcall(qp,P_WEIGHT);
1098 if (wsum-bestw[otype]+w>maxw) continue;
1099 val=funcall(qp,P_VALUE);
1100 if (sum-bestval[otype]+val>maxmon) continue;
1101 if (bestob[otype] && ac<=bestac[otype]) {
1102 if (val>bestval[otype]) continue;
1103 else if (val==bestval[otype] && w>bestw[otype]) continue;
1104 }
1105 if (CheckFindRestrictions(ob,restr,qp)) continue;
1106 sum=sum-bestval[otype]+val;
1107 wsum=wsum-bestw[otype]+w;
1108 bestob[otype]=ob;
1109 bestac[otype]=ac;
1110 bestval[otype]=val;
1111 bestw[otype]=w;
1112 }
1113 return bestob;
1114}
1115
1116varargs object *FindBestArmours(mixed type, int maxmon, int maxw,
1117 mapping bestac, mixed restr) {
1118 return m_values(FindBestArmoursT(type,maxmon,maxw,bestac,restr));
1119}