blob: 2c0dcba14c50056dcebe3cc4e4b904cd8a2d58bc [file] [log] [blame]
#pragma strong_types, save_types, rtt_checks
#pragma no_inherit, no_clone, no_shadow
#include <defines.h>
#include <properties.h>
#include <wizlevels.h>
#include <items/kraeuter/kraeuter.h>
#include <items/kraeuter/trankattribute.h>
#include <living/comm.h>
#ifndef BS
#define BS(x) break_string(x, 78)
#endif
// Weiter unten beim Parsen des Datenfiles werden SetProp() und Name()
// verwendet, daher erben wir thing.
inherit "/std/secure_thing";
// Liste aller moeglichen Zutaten, gemappt ueber einen key
// AN: irritierender Name, denn es sind nicht direkt die Zutaten enthalten
// sondern die ueber einen Key identifizierten Kraeutergruppen, die die
// Wirkungen/Funktionen festlegen.
// Format:
// ([ key : ({ attr_pos, attr_neg1, attr_neg2, Haltbark., Wirkdauer,
// Unterstuetzung, Stoerung, Haeufigkeit }) ])
// Beispiel: [( "a7":({({2,400}),({11,150}),0,172800,0,({"n*"}),0,1}) ])
// HU: War ein Mapping, bleibt ein Mapping, und neu: Die Eintraege werden
// auch Mappings!
private mapping ingredients;
// Mapping von id zum Key der Zutaten im ingredients-mapping: id2key[id]=key
// Die beiden Listen werden benoetigt, um zu den Kraut-IDs die jeweiligen
// Keys zu finden.
// HU: KEIN MAPPING. Ein Array von Strings. Aber das geht natuerlich.
private string *id2key;
// Mapping von key zu Kraeuter-ID der Zutaten.
private mapping key2id;
// Eigenschaften der Kraeuter:
// krautprops[id]=({ID, Demon, Gender, Name, Adjektiv(e), P_LONG, Raumdetail})
// krautprops[0] = 0; ID 0 darf nicht existieren
private mixed *krautprops;
// hier wird gespeichert welche Version einer Zutat fuer einen Spieler ist.
// AN/TODO: Wenn das fuer Nichtseher nutzbar sein soll, sollte hier besser
// mit getuuid() gearbeitet werden, statt getuid() zu verwenden.
// player enthaelt fuer jede Spieler-UID einen Bitstring. Gesetztes Bit
// bedeutet, dass der Spieler ueber das Kraut mit dieser ID etwas ueber
// die Verwendung und Wirkung weiss. Es gibt jedoch noch keine Lernmoeglich-
// keit und INSBESONDERE keine Funktionalitaet im Master, ueber die man
// die Bits setzen koennte.
private mapping player;
// verstuemmeltes mapping fuer den VC in service (Save-Dummy)
private mapping map_ldfied;
// rooms enthaelt die Teileintraege des map_ldfied mapping vom VC
private mapping rooms;
// Enthaelt Daten zu den Raeumen, in denen das Trocknen von Kraeutern
// moeglich ist.
private mapping drying_data = ([:4]);
string build_plantname(mixed *props);
// struct-Templat fuer Trankattribute
// Fuer das SQL-Log MUSS die Reihenfolge der Trankattribute hier genau die
// sein, wie die Spalten in der Tabelle.
/* Currently not used.
struct trank_attrib_s {
int car;
int da;
int dm;
int du;
int dn;
int flt;
int fro;
int hI;
int hP;
int hK;
int hL;
int pa;
int pm;
int pu;
int ss;
int sp;
int sd;
};
*/
#define allowed() (!process_call() && \
IS_ARCH(RPL) && IS_ARCH(this_interactive()) )
#define POTIONFILES ({ TRANKITEM })
// eigenschaften im krautprops-array
#define PROP_ID INGREDIENT_ID
#define PROP_DEMON INGREDIENT_DEMON
#define PROP_GENDER INGREDIENT_GENDER
#define PROP_NAME INGREDIENT_NAME
#define PROP_ADJ INGREDIENT_ADJ
#define PROP_LONG INGREDIENT_LONG
#define PROP_ROOMDETAIL INGREDIENT_ROOMDETAIL
// ATTR_ is immer ein array ({ attribut, ability })
#define ATTR_ARR_ATTRIB 0
#define ATTR_ARR_ABILITY 1
// maximal erlaubter Wert fuer eine Eigenschaft
#define MAX_ATTR_ABILITY 999
void debug(string str)
{
//write("debug: "+str+"\n");
if (this_player() && IS_ARCH(this_player()))
this_player()->ReceiveMsg(str,536870912);
}
protected void create()
{
seteuid(getuid());
// AN/TODO: Variablen oben direkt initialisieren. Pruefen, ob davon
// irgendwas nosave sein kann.
if (!restore_object(__DIR__"/ARCH/krautmaster"))
{
ingredients=([]);
id2key=({});
key2id=([]);
krautprops=({});
player=([]);
map_ldfied=([]);
rooms=([]);
}
if (sl_open("/log/ARCH/krauttrank.sqlite") != 1)
{
raise_error("Datenbank konnte nicht geoeffnet werden.\n");
}
sl_exec("CREATE TABLE IF NOT EXISTS traenke(id INTEGER PRIMARY KEY, "
"uid TEXT NOT NULL, rnd INTEGER, "
"time INTEGER DEFAULT CURRENT_TIMESTAMP, "
"receipe TEXT NOT NULL, "
"quality TEXT NOT NULL, "
"car INTEGER, "
"da INTEGER, "
"dm INTEGER, "
"du INTEGER, "
"dn INTEGER, "
"flt INTEGER, "
"fro INTEGER, "
"hI INTEGER, "
"hP INTEGER, "
"hK INTEGER, "
"hL INTEGER, "
"pa INTEGER, "
"pm INTEGER, "
"pu INTEGER, "
"ss INTEGER, "
"sp INTEGER, "
"sd INTEGER);"
);
sl_exec("CREATE INDEX IF NOT EXISTS idx_uid ON traenke(uid);");
sl_exec("CREATE INDEX IF NOT EXISTS idx_receipe ON traenke(receipe);");
sl_exec("CREATE TABLE IF NOT EXISTS rohdaten(id INTEGER PRIMARY KEY, "
"uid TEXT NOT NULL, rnd INTEGER, "
"time INTEGER DEFAULT CURRENT_TIMESTAMP, "
"receipe TEXT NOT NULL, "
"quality TEXT NOT NULL, "
"car INTEGER, "
"da INTEGER, "
"dm INTEGER, "
"du INTEGER, "
"dn INTEGER, "
"flt INTEGER, "
"fro INTEGER, "
"hI INTEGER, "
"hP INTEGER, "
"hK INTEGER, "
"hL INTEGER, "
"pa INTEGER, "
"pm INTEGER, "
"pu INTEGER, "
"ss INTEGER, "
"sp INTEGER, "
"sd INTEGER);"
);
sl_exec("CREATE INDEX IF NOT EXISTS idx_uid_r ON rohdaten(uid);");
}
public string QueryPlantFile(int id)
{
if (member(krautprops, id))
return build_plantname(krautprops[id]);
return 0;
}
// AN: Funktion ermittelt, ob ein Spieler pl das Kraut mit der ID id
// verwenden kann. Laut Kommentar unten muss man dafuer wohl was ueber das
// Kraut gelernt haben, von wem ist mir gerade noch nicht klar.
// AN/TODO: Es ist bisher keine Funktionalitaet vorhanden, um die IDs fuer
// den Spieler freizuschalten. Die Funktionsweise muss aus dem Beispielcode
// unten fuer das Learn-On-Demand abgelesen werden.
nomask int CanUseIngredient(object pl, int id)
{
// Ich mach mal den harten Weg. -- Humni
return 1;
// Bitstring des Spielers aus der Liste auslesen.
string str=player[getuid(pl)];
if (!stringp(str)) str="";
if (test_bit(str, id))
return 1; // make the common case fast
else {
// letztenendes habe ich mich entschlossen einzubauen, das man nur die
// Kraeuter verwenden kann, ueber die man schonmal etwas gelesen/gelernt
// hat, aus diesem Grund, ist folgender Block auskommentiert.
// Dieser Block bedeutet quasi ein "auto learning on demand" d.h.
// wird ein Kraut verwendet wird geprueft ob fuer diese Gruppe bereits
// eine ID freigeschaltet wurde - ansonsten wird eine freigeschaltet.
/*
// pruefen ob fuer den Key bereits ein Bit gesetzt ist, ggf. setzen
if (id>=sizeof(id2key)) return 0; // illegale Id
int idlist=key2id[id2key[id]];
int i;
for (i=sizeof(idlist)-1; i>=0; i--) {
if (test_bit(str, idlist[i])) return 0; // Key bereits vorhanden
}
// Zufaellig ein Bit fuer den Key setzen
i=random(sizeof(idlist));
player[getuid(pl)]=set_bit(str, idlist[i]);
save_object(object_name());
return (i==id);
*/
return 0;
}
}
// Diese Funktion wird vom Metzelorakel aufgerufen, um die Belohnungen zu
// erzeugen, die man dort fuer erfolgreich absolvierte Metzelauftraege erhaelt
#define ALLOWED_CALLER ({ "/d/ebene/arathorn/orakel/secure/schamane" })
object get_plant_by_category(int npc_class)
{
if ( member(ALLOWED_CALLER, load_name(previous_object()))<0 )
raise_error("unauthorised call to get_plant_by_category()\n");
// Uebergebene NPC-Klasse in Kraut-Kategorie umsetzen.
// Kategorie 7 wird als 7-9 interpretiert (siehe unten).
int category;
switch(npc_class) {
case 1: case 2: category=4; break;
case 3: case 4: category=5; break;
case 5: case 6: case 7: category=6; break;
default: category=7; break;
}
// Alle Kraeuter der ermittelten Kategorie raussuchen. Bei Kategorie 7
// werden auch alle aus 8 und 9 dazugenommen.
int *eligible_plant_ids=({});
foreach( string grp, mapping grpdata : ingredients ) {
if ( category == 7 && grpdata[T_ABUNDANCE]>=7 ||
category == grpdata[T_ABUNDANCE] )
eligible_plant_ids += key2id[grp];
}
// Krautnamen zu den Kraut-IDs ermitteln.
string *plantfiles=map(eligible_plant_ids, function string (int plantid) {
return build_plantname(krautprops[plantid]);});
// Ein Kraut zufaellig auswaehlen, clonen und das Objekt zurueckgeben.
object plant=clone_object(PLANTDIR+plantfiles[random(sizeof(plantfiles))]);
plant->DryPlant(80+random(11));
// Aufschreiben, wer welches Kraut mit welcher Qualitaet rausbekommt.
log_file("ARCH/plant_by_category",
sprintf("%s %-12s %-s Qual %d Kat %d Class %d\n",
strftime("%x %X",time()), getuid(PL),
object_name(plant)[sizeof(PLANTDIR)..],
plant->QueryProp(P_QUALITY), category, npc_class));
// sprintf("%24s: call from %O, player: %s (PL: %O), kategory: %d\n", ctime(),
// previous_object(), getuid(player), PL, kategory))
return plant;
}
private nosave object simul_efun, plantvc;
// fuer SIMUL_EFUN_FILE
#include <config.h>
// AN/TODO: Klaeren, warum hier eine eigene Funktion get_cloner() existiert,
// wo doch der Kraeuter-VC schon eine hat.
nomask private string get_cloner()
{
int i;
object po;
if (!simul_efun) {
if (!(simul_efun=find_object(SIMUL_EFUN_FILE)))
simul_efun=find_object(SPARE_SIMUL_EFUN_FILE);
}
// wenn sie jetzt nicht existiert - auch gut, dann gibt es halt keine
// sefuns.
if (!plantvc) plantvc=find_object(KRAEUTERVC);
for (i=0; po=previous_object(i); i++) {
if (po==master() || po==simul_efun || po==ME ||
po==previous_object() || po==plantvc)
continue;
else return object_name(po);
}
return 0;
}
// AN:
nomask string CheckPlant(int id)
{
if (id<=0 || id>=sizeof(id2key)) return 0;
if (!stringp(id2key[id])) return 0;
return get_cloner();
}
// ueber diese Funktion kann die Liste der Id's updatet werden
// es wird <filename> eingelesen und durchgeparst.
// Diese Datei muss in jeder Zeile folgendes Format einhalten und darf keine
// leerzeilen enthalten! > Id,key,Gender,Name,Adjektiv
// AN: Das ist Quatsch. Das Format muss so aussehen:
// String-ID;Kraut-ID;demon;gender;P_NAME;adjektiv;P_LONG;roomdetail;
// HU: Diese Funktion lass ich so. Harhar.
// Update: Na gut. Fast.
nomask private int LoadIdList(string filename)
{
int i, id, si, demon;
string *lines, file;
mixed *data;
file=read_file(filename);
if (!stringp(file)) raise_error("Missing File: "+filename+"\n");
lines=explode(read_file(filename), "\n");
si=sizeof(lines)-1;
// AN/TODO: Warum verwendet man hier nicht auch einfach explode()?
// Wenn "##ende##" als Trennzeichen enthalten ist, liefert explode()
// als letzten Eintrag einen Leerstring, darauf kann man pruefen.
// Allerdings muesste das vor dem explode() zur Zeilentrennung passieren.
for ( ;si>=0; si--)
{
string lili=lines[si];
if (strstr(lines[si],"##ende##")>=0) break;
}
si--;
if (si<0) raise_error("##ende## not found in id-list.\n");
id2key=allocate(si+2);
krautprops=allocate(si+2);
// AN: Fuer das Auslesen der Werte aus dem Array "data" muessen die
// Indizierungs-Defines jeweils +1 verwendet werden, weil im Datenfile
// schlauerweise ein Datenfeld *vorne* angefuegt wurde.
// AN/TODO: vielleicht sollte man besser Element 0 zuerst auslesen und
// dann das Array arr um ein Element kuerzen: arr = arr[1..];
for (i=si; i>=0; i--) {
data=explode(lines[i], ";");
if (sizeof(data)!=8) raise_error("Wrong id-list format in line "+(i+1)+"\n");
id=to_int(data[PROP_ID+1]);
data[PROP_ID+1]=id;
if (id>si+1) raise_error(sprintf("Line %d: id %d greater than array size %d.\n", i, id, si));
id2key[id]=data[0];
// AN: Ich sehe noch nicht so ganz, warum man von dem Datenfeld
// PROP_DEMON nur das letzte Zeichen als Literal pruefen sollte.
// Komplett geht doch genausogut?
switch(data[PROP_DEMON+1][<1]) {
case 'R': data[PROP_DEMON+1]=RAW; break;
case '0': data[PROP_DEMON+1]=0; break;
case '1': data[PROP_DEMON+1]=1; break;
case '2': data[PROP_DEMON+1]=2; break;
default: raise_error("Unknown demonformat '"+data[PROP_DEMON+1]+
"' in idlist line "+(i+1)+"\n");
}
switch(data[PROP_GENDER+1][<1]) {
case 'N': data[PROP_GENDER+1]=NEUTER; break;
case 'F': data[PROP_GENDER+1]=FEMALE; break;
case 'M': data[PROP_GENDER+1]=MALE; break;
default: raise_error("Unknown genderformat '"+data[PROP_GENDER+1]+
"' in idlist line "+(i+1)+"\n");
}
SetProp(P_GENDER, data[PROP_GENDER+1]);
SetProp(P_NAME, data[PROP_NAME+1]);
// AN/TODO: data[PROP_ADJ] muss man nicht unbedingt vorher auf 0 setzen
if (!sizeof(data[PROP_ADJ+1])) data[PROP_ADJ+1]=0;
if (data[PROP_ADJ+1])
SetProp(P_NAME_ADJ, ({"ganz normal", data[PROP_ADJ+1]}));
else SetProp(P_NAME_ADJ, "ganz normal");
SetProp(P_ARTICLE, data[PROP_DEMON+1]!=RAW);
demon=(data[PROP_DEMON+1]==RAW ? 0 : data[PROP_DEMON+1]);
// AN: Wenn keine Langbeschreibung hinterlegt wurde, wird der Name
// des Krautes als solche verwendet. Ebenso fuer das Raumdetail, das
// beim Betrachten des Krautes im Raum ausgegeben wird.
if (!sizeof(data[PROP_LONG+1])) {
data[PROP_LONG+1] = Name(WER, demon)+".\n";
}
else data[PROP_LONG+1] = BS(data[PROP_LONG+1]);
// Humni: Offenbar kommen am Zeilenende manchmal Zeichen dazu. Ich gehe davon
// aus, dass keine Beschreibung kuerzer als 2 Zeichen ist.
if (sizeof(data[PROP_ROOMDETAIL+1])<2) {
data[PROP_ROOMDETAIL+1] = Name(WER, demon)+".\n";
}
else data[PROP_ROOMDETAIL+1] = BS(data[PROP_ROOMDETAIL+1]);
krautprops[id]=data[1..];
}
SetProp(P_NAME, 0);
SetProp(P_NAME_ADJ, "");
// key2id-Cache neu aufbauen.
key2id=([]);
for (i=sizeof(id2key)-1; i>=0; i--) {
if (member(key2id, id2key[i]))
key2id[id2key[i]]+=({ i });
else key2id[id2key[i]]=({ i });
}
return 1;
}
// Hilfsfunktion wird zum einparsen benötigt
// wandelt z.B. den string von "h 500" in ({ 3, 500 }) um
private nomask <string|int>* buildAttrArr(string attr)
{
if (!attr) return 0;
attr=trim(attr);
<string|int>* res=explode(attr, " ")-({""});
//debug(sprintf(" build Attr %O",res));
if (sizeof(res)!=2) raise_error("Wrong attrib format!\n");
//debug(sprintf("%O",T_KRAUT_MAP));
return ({T_KRAUT_MAP[res[0]],(int)res[1]});
}
// AN: Hilfsfunktion zum Einlesen der Traenkeliste.
nomask private void LoadIndex(string filename)
{
int i, j, si;
string *lines, file;
mixed *data;
file=read_file(filename);
if (!stringp(file)) raise_error("Missing File: "+filename+"\n");
// AN/TODO: Auch hier waere wieder die Verwendung von explode() zu
// erwaegen.
lines=explode(file, "\n")-({""});
si=sizeof(lines);
for (i=0; i<si; i++) if (lines[i]=="##start##") break;
i++;
if (i>=si) raise_error("missing ##start## in Index.\n");
ingredients=([]);
for (; i<si; i++) { // alle zeilen durchlaufen...
// AN/TODO: Tabulatoren als Trennzeichen finde ich irgendwie bloed.
// HU: Darum nun Semikolons
// debug("Zeile "+lines[i]);
data=old_explode(lines[i], ";"); // an Semikolons trennen...
// debug(sprintf("%O",data));
if (sizeof(data)!=9)
{
//debug(sprintf("%O",data));
raise_error("Wrong indexlist format in line "+(i+1)+"\n");
}
for (j=8; j>=0; j--) {
// AN/TODO: if(data[j]=="" || data[j]=="-") data[j]=0;
// es sind ohnehin ueberall Strings enthalten.
// Wir machen aus "-" oder "0" eine echte int-Null.
if (sizeof(data[j])==0 || data[j][0]=='-') data[j]=0;
}
// HU: Ab hier bau ich mal neu. Den Rest pack ich auskommentiert darunter, wenn jemand den alten Code
// nachschauen will.
// Ich will ein Mapping von dieser Kraeutergruppe.
mapping mk=([]);
// Dieses Mapping soll die Eintraege nun enthalten.
mk[T_EFFECT_DURATION]=to_int(data[5]);
mk[T_ABUNDANCE]=to_int(data[8]);
// positive Effekte aufteilen
//debug(sprintf("Vorposis %O - %O",mk,data));
if (stringp(data[1]))
{
string* posis=explode(data[1],",");
//debug(sprintf("%O - %O",mk,posis));
foreach (string q:posis) {
//debug(q);
<string|int>* arr=buildAttrArr(q);
//debug(sprintf("%O",arr));
mk[arr[0]]=mk[arr[0]]+arr[1];
}
}
//debug(sprintf("%O",mk));
// Erster Negativer Effekt
if (data[2]!=0)
{
<string|int>* arr=buildAttrArr(data[2]);
mk[arr[0]]=mk[arr[0]]-arr[1];
}
//debug(sprintf("vorneg %O",mk));
// Zeiter negativer Effekt
if (data[3]!=0)
{
<string|int>* arr=buildAttrArr(data[3]);
mk[arr[0]]=mk[arr[0]]-arr[1];
}
// Haltbarkeit wird umgerechnet
string* sti=explode(data[4]," ")-({""});
//debug("haltbar "+sti[0]);
string stt=trim(sti[0]);
int dur;
if (stt[<1..]=="d") // Tage
{
//debug("Tage");
// Der erste Teil ist die Dauer in Tagen.
dur=to_int(sti[0][..<2]);
dur=dur*24*60*60; // Sekunden
mk[T_EXPIRE]=dur;
}
if (stt[<1..]=="h") // Stunden
{
//debug("Stunden");
// Sonst ist es halt die Dauer in Stunden.
dur=to_int(sti[0][..<2]);
dur=dur*60*60;
mk[T_EXPIRE]=dur;
}
//debug("ergibt "+dur);
// Nun die lustigen Unterstuetzungen. Dazu benutzen wir unseren lustigen Glueckshasen.
// Und ein Reh.
string* glueckshase;
string reh;
// Alle Leerzeichen raus!
//debug("Rehe");
//debug(sprintf("Data: %O",data));
if (stringp(data[6]))
{
reh=(explode(data[6]," ")-({""}))[0];
glueckshase=explode(reh,",")-({});
mk[T_SUPPORTER]=glueckshase;
}
else
{
mk[T_SUPPORTER]=0;
}
// Nun machen wir genauso die Blockaden.
// Das tolle ist: Reh und Glueckshase koennen wir wiederverwenden! Das freut.
if (stringp(data[7]))
{
reh=(explode(data[7]," ")-({""}))[0];
glueckshase=explode(reh,",")-({});
mk[T_BLOCKING]=glueckshase;
}
else
{
mk[T_BLOCKING]=0;
}
ingredients[trim(data[0])]=mk;
}
//debug("Wuff");
//debug(sprintf("%O",ingredients));
}
nomask private void save()
{
save_object(__DIR__"/ARCH/krautmaster");
}
// AN: erzeugt aus Namen + Adjektiv der Pflanzendaten den Objektnamen,
// z.B. waldrebe_gemein oder ackerklee_gelb, wobei Bindestriche auch
// durch Unterstriche ersetzt werden (acker_rettich).
string build_plantname(mixed *props)
{
string key;
// AN/TODO: erst PROP_NAME in key schreiben, dann ggf. PROP_ADJ dazu
if (sizeof(props[PROP_ADJ])>0)
key=lowerstring(props[PROP_NAME]+"_"+props[PROP_ADJ]);
else key=lowerstring(props[PROP_NAME]);
// AN/TODO: ersetzen durch regreplace();
key=implode(old_explode(key, " "), "_");
key=implode(old_explode(key, "-"), "_");
return key;
}
public void UpdateVC()
{
KRAEUTERVC->update(map_ldfied);
}
// AN: Daten neu parsen
// Nach dem Schreiben des Savefiles mittels save() wird auch das
// Kraeuter-Headerfile vom Kraeuter-VC neu geschrieben.
int _refresh()
{
int i;
string key;
if (extern_call() && !allowed())
return 0;
LoadIdList(__DIR__"ARCH/kraeuterliste.dump");
LoadIndex(__DIR__"ARCH/kraeuterindex.dump");
map_ldfied=([]);
for (i=sizeof(krautprops)-1; i>=0; i--)
{
if (sizeof(krautprops[i])<=PROP_ROOMDETAIL) continue;
key = build_plantname(krautprops[i]);
map_ldfied[key]=({ krautprops[i], rooms[key]||([]) });
}
save();
UpdateVC();
// Update Headerfile mit Kraeuterliste
string *keys = sort_array(m_indices(map_ldfied), #'<);
string headerfile =
"// Automatisch generiertes File, nicht von Hand editieren!\n"
"// Erzeugendes File: "+object_name()+"\n\n"
"#define PLANTCOUNT "+to_string(sizeof(keys))+"\n\n"
+"#define PLANT(x) \"/items/kraeuter/\"+x\n\n";
foreach(key: keys)
{
headerfile += sprintf("#define %-30s PLANT(\"%s\")\n",
upperstring(key), key);
}
write_file(KRAEUTERLISTE, headerfile, 1);
write("Inputfiles parsed. Save & Headerfiles updated!\n");
return 1;
}
int _cloneplant(string str)
{
if (allowed())
{
if (to_string(to_int(str)) == str)
str = QueryPlantFile(to_int(str));
clone_object(PLANTDIR+str)->move(this_player(), M_NOCHECK);
write("Kraut " + str + " geclont.\n");
return 1;
}
return 0;
}
#define MAX_ROOMS 10 /* kein Kraut ist in mehr als 10 Raeumen */
// AN: Ausgabe der Kategorienliste ueber das Planttool.
int _showplant(string str)
{
int i, si, kat, secure;
string *list, key;
mixed *res, *arr;
mapping props;
secure=allowed();
notify_fail("Es gibt folgende Kraeuterkategorien:\n"
+" 0 - haeufig und an vielen Stellen im Mud anzufinden\n"
+" 1 - etwas seltener zu finden, aber immer noch leicht\n"
+" 2 - an wenigen gut versteckten Stellen in abgelegenen Gebieten\n"
+" 3 - dito, jedoch muss das Kraut durch einen NPC (XP >= 500000) bewacht sein.\n"
+" 4 - aeusserst selten und XP >= 1 mio\n"
+" 5 - aeusserst selten und XP >= 2 mio\n"
+" 6 - aeusserst selten und NPC bringt >= 5 Stupse\n"
+" 7 - aeusserst selten und NPC bringt >= 10 Stupse\n"
+" 8 - aeusserst selten und NPC bringt >= 20 Stupse\n"
+" 9 - aeusserst selten und NPC bringt >= 50 Stupse\n"
+"\nSyntax: showplant <kategorie>.\n");
kat=to_int(str);
if (kat<0 || kat>9) return 0;
if (to_string(kat)!=str) return 0;
list=m_indices(map_ldfied);
// AN: *grummel*
// res = allocate(MAX_ROOMS, ({}));
res=map(allocate(MAX_ROOMS), #'allocate); // ({ ({}) ... ({}) })
for (i=sizeof(list)-1; i>=0; i--) {
arr=map_ldfied[list[i]];
if (sizeof(arr)!=2) raise_error("Wrong map_ldfied-Format by "+list[i]+"\n");
key=id2key[arr[0][PROP_ID]];
if (!key) raise_error("Missing Key for id "+arr[0][PROP_ID]+"\n");
props=ingredients[key];
//if (!pointerp(props)) continue; // noch nicht eingetragen
//if (sizeof(props)!=8)
// printf("Warning: Wrong ingredient-content by "+key+"\n");
//debug(sprintf("%O",props));
if (props==0)
{
debug("Falscher Key: "+key);
}
else
{
if (props[T_ABUNDANCE]==kat)
{
si=sizeof(arr[1]);
if (si<MAX_ROOMS) {
if (stringp(arr[0][PROP_ADJ])) {
SetProp(P_ARTICLE, 0);
SetProp(P_NAME, arr[0][PROP_NAME]);
SetProp(P_NAME_ADJ, arr[0][PROP_ADJ]);
SetProp(P_GENDER, arr[0][PROP_GENDER]);
key=Name(WER);
}
else key=arr[0][PROP_NAME];
if (secure)
res[si]+=({ sprintf("%3d %-40s: %d\n", arr[0][PROP_ID], key, si) });
else res[si]+=({ sprintf("%-40s: %d\n", key, si) });
}
}
}
}
for (i=0; i<MAX_ROOMS; i++) {
sort_array(res[i], #'>);
filter(res[i], #'write);
}
return 1;
}
// AN: Ausgabe der Raeume, in denen die Kraeuter zu finden sind.
// angesteuert ueber das Planttool.
int _showrooms(string str)
{
int i, j, id;
string *list, dummy;
mixed *arr;
if (!allowed()) return 0;
notify_fail("Syntax: showrooms <id> oder showrooms all\n");
if (str!="all") {
id=to_int(str);
if (to_string(id)!=str) return 0;
}
else id=-1;
list=m_indices(map_ldfied);
for (i=sizeof(list)-1; i>=0; i--) {
arr=map_ldfied[list[i]];
if (sizeof(arr)!=2) raise_error("Wrong map_ldfied-Format by "+list[i]+"\n");
if (arr[0][PROP_ID]==id || id<0) {
if (!sizeof(m_indices(arr[1]))) {
if (id>=0) write("Fuer diese Id sind keine Raeume eingetragen.\n");
}
else if (id>=0) {
write("Folgende Raeume sind fuer "+arr[0][PROP_ID]+" eingetragen.\n");
filter(map(m_indices(arr[1]), #'+, "\n"), #'write);
return 1;
}
else filter(map(m_indices(arr[1]), #'+, ": "+arr[0][PROP_ID]+", "+arr[0][PROP_NAME]+"\n"), #'write);
if (id>=0) return 1;
}
}
write("Fuer diese Id sind bisher keine Kraeuter eingetragen.\n");
return 1;
}
// Nutzung der Kraeuter in Gebieten liefert nur dann gueltige Kraeuter,
// wenn der Raum eingetragen ist.
int _addroom(string str)
{
int id, i;
string *list, vc;
if (!allowed()) {
write("Fuer das Eintragen der Raeume wende Dich doch bitte "
"an einen EM.\n");
return 1;
}
notify_fail("Syntax: addroom <krautnummer> <filename>\n");
str=PL->_unparsed_args();
if (!str || sscanf(str, "%d %s", id, str)!=2) return 0;
if (str=="hier" || str=="here")
{
if (!this_player())
{
notify_fail("Kein Spielerobjekt, kann "
"Raum nicht identifizieren.\n");
return 0;
}
str=to_string(environment(this_player()));
}
if (str[<2..]==".c") str=str[0..<3]; // .c abschneiden
if (file_size(str+".c")<=0) {
list=explode(str, "/");
vc=implode(list[0..<2], "/")+"/virtual_compiler.c";
if (file_size(vc)<=0 || !call_other(vc, "Validate", list[<1])) {
write("No such file \""+str+"\".\n");
return 1;
}
}
if (id<=0 || id>=sizeof(id2key)) {
write("Illegal plantid "+id+".\n");
return 1;
}
list=m_indices(map_ldfied);
for (i=sizeof(list)-1; i>=0; i--) {
if (map_ldfied[list[i]][0][PROP_ID]==id) {
if (!member(map_ldfied[list[i]][1], str)) {
map_ldfied[list[i]][1]+=([ str ]);
rooms[list[i]]=(rooms[list[i]]||([]))+([ str ]);
write("Raum Erfolgreich eingetragen!\n");
}
else write("Raum bereits eingetragen.\n");
save();
_refresh();
return 1;
}
}
write("Kraut mit id "+id+" nicht gefunden.\n");
return 1;
}
int _delroom(string str)
{
int i, done;
string *list;
if (!allowed()) {
write("Fuer das Loeschen von Raeumen wende Dich doch bitte "
"an einen EM.\n");
return 1;
}
notify_fail("Syntax: delroom <filename>.\n");
str=PL->_unparsed_args();
if (!str) return 0;
if (str[<2..]==".c") str=str[0..<3];
list=m_indices(map_ldfied); done=0;
for (i=sizeof(list)-1; i>=0; i--)
{
if (member(map_ldfied[list[i]][1], str)) {
m_delete(map_ldfied[list[i]][1], str);
m_delete(rooms[list[i]], str);
write("Raum bei id "+map_ldfied[list[i]][0][PROP_ID]
+" ausgetragen.\n");
done=1;
}
}
if (!done) {
if (file_size(str+".c")<0)
write("No such file \""+str+"\".\n");
else write("Fuer "+str+" sind keine Kraeuter eingetragen!\n");
}
else {
save();
_refresh();
}
return 1;
}
// Veranlasst den Kraeuter-VC, eine phys. Datei aus den Kraeuterdaten eines
// Krautes zu erzeugen, falls man diese ausbeschreiben will.
int _createfile(string str)
{
int id;
if (!allowed()) {
write("Diese Funktion wurde fuer Dich nicht freigegeben!\n");
return 1;
}
id=to_int(str);
if (to_string(id)!=str || id<=0 || id>=sizeof(id2key)) {
write("Illegal plantid '"+str+"'.\n");
return 1;
}
notify_fail("Unknown Function im kraeuterVC: _createfile()\n");
return call_other(KRAEUTERVC, "_createfile", build_plantname(krautprops[id]));
}
// AN: Hilfsfunktionen, derzeit ebenfalls deaktiviert.
// i = 0..7, Position des Krautes, fuer das der Aufruf erfolgt, in der
// Liste der in den Kessel einfuellten Kraeuter.
// keyLst ist die Liste der Kraeutergruppen, der die Kraeuter zugeordnet
// sind.
// An dieser Stelle kann also die Wirkung von Kraeutergruppen abgehaengt
// werden. Unklar ist mir aktuell nur, warum diese Funktion den Parameter
// "i" benoetigen wuerde.
// Idee: Es soll die Entscheidung davon abhaengig gemacht werden koennen,
// wie die Gesamtkombination aussieht, und zusaetzlich davon, aus welcher
// Gruppe das einzelne Kraut stammt.
nomask private int IsBlocked(int i, string *keyLst)
{
return 0;
}
// AN: Diese Funktion muesste nach dem Code in make_potion() zu urteilen
// 0 oder 1 zurueckgeben, dann wird der Eigenschaftswert der Kraeutergruppe
// um den Faktor 1.5 verstaerkt.
nomask private int IsBoosted(int i, string *keyLst)
{
return 0;
}
#define PRNG "/std/util/rand-glfsr"
// Individuelle Boni/Mali fuer Spieler. ploffset soll via Referenz uebergeben
// werden und wird von der Funktion gesetzt.
int calculate_mod(int krautindex, string plname, int ploffset)
{
// Startoffset zufaellig ermittelt, aber immer gleich
// fuer jeden Spielernamen
PRNG->InitWithUUID(plname);
ploffset = PRNG->random(16);
// Jedes Kraut hat auch einen iOffset (der konstant bleibt und sich nicht
// aendert). Der wird auch addiert. Bei Ueberschreiten von 30 wird nach 0
// gewrappt.
// Der Offset ist dann (spieleroffset + krautindex) % 16, d.h. alle Modifikatoren werden
// der Reihe nach durchlaufen. So kriegt jeder Spieler - fast egal, bei welchem
// Offset er startet - auch der Reihe nach alle Boni+Mali.
int offset = ((ploffset + krautindex) % 16) * 2;
// Am Ende wird das ganze noch nach 85 bis 115 verlegt.
return offset + 85;
}
#define ZWEITIES "/secure/zweities"
mapping calculate_potion(int* plantids, int* qualities, string familie)
{
// Man sollte ohne die Kraeuter nicht so einfach Wirkungen beliebig
// berechnen koennen.
if (extern_call() && !ARCH_SECURITY)
return 0;
// Hier speichern wir unser Ergebnis bzw. unser Zwischenergebnis.
mapping attrib;
// Hier speichern wir die Wirkgruppen, die aus den Plants gezogen werden.
mapping* wirkungen=({});
// Hier speichern wir gleich schon beim Erzeugen die wichtigsten Blockaden.
string* unterstuetzungen=({});
string* blockaden=({});
int zufall;
// Die Sortierung nach PlantID ist nur fuer das Tranklog wichtig.
plantids = sort_array(plantids, #'<=);
// PASS 1: Pflanzen durch Wirkungen ersetzen, dabei Unterstuetzer
// und Blocker merken.
foreach (int id, int qual : mkmapping(plantids,qualities))
{
//debug(sprintf("Gehe durch Plant: %d",id));
string key=id2key[id];
// Wirkungen dieses Krauts kopieren
mapping ing=copy(ingredients[key]);
//debug(sprintf("%O",ing));
// Zu den Wirkungen noch den Key hinzufuegen.
ing["key"]=key;
// Die Qualitaet des Krautes wird noch mit dem spielerindividuellen
// Modifikator skaliert.
ing["quality"]=(qual * calculate_mod(id, familie, &zufall)) / 100;
wirkungen+=({ing});
if (pointerp(ing[T_SUPPORTER]))
{
foreach (string pi:ing[T_SUPPORTER])
{
unterstuetzungen+=({pi});
}
}
if (pointerp(ing[T_BLOCKING]))
{
foreach (string pi:ing[T_BLOCKING])
{
blockaden+=({pi});
}
}
debug(sprintf("Kraut %s ergibt Werte %O.",key,wirkungen));
}
// PASS 2: Jetzt die Unterstuetzungen auswerten
foreach (mapping mar:wirkungen)
{
foreach (string pi:unterstuetzungen)
{
// Haben wir eine Unterstuetzung?
if (mar["key"]==pi || ((pi[1]=='*') && (pi[0]==mar["key"][0])))
{
debug (sprintf("mar=%O, pi=%O",mar,pi));
// ALLE ZAHLEN mal 1.5 nehmen. Mir ist klar, dass das nun auch
// mit irgendwelchen Haeufigkeiten passiert, aber mal ehrlich,
// das ist zur Berechnung egal.
foreach (string kk, mixed val : &mar)
{
if (intp(val))
{
val=15*val/10;
}
}
}
}
}
// PASS 3: Jetzt die Blockaden auswerten
foreach (mapping mar:wirkungen)
{
foreach (string pi:blockaden)
{
// Haben wir eine Blockade?
if (mar["key"]==pi || ((pi[1]=='*') && (pi[0]==mar["key"][0])))
{
debug (sprintf("mar=%O, pi=%O",mar,pi));
// Hier werden alle Zahlen auf Null gesetzt.
foreach (string kk, mixed val : &mar)
{
if (intp(val))
{
val=0;
}
}
}
}
}
// PASS 3.5: Qualitaet der Kraeuter skalieren.
foreach (mapping mar:wirkungen)
{
foreach (string kk, mixed val : &mar)
{
if (intp(val) && kk!="quality")
{
val=val*mar["quality"]/100;
}
}
}
// PASS 4: Nun addieren wir alles auf in das Mapping attrib.
attrib=([]);
foreach (mapping mar:wirkungen)
{
foreach (string kk:mar)
{
if (intp(mar[kk]))
{
attrib[kk]=attrib[kk]+mar[kk];
}
}
}
// Die Wirkungsdauer ist der Durchschnitt der Wirkungsdauern
attrib[T_EFFECT_DURATION] /= sizeof(plantids);
debug(sprintf("Duration: %d\n",attrib[T_EFFECT_DURATION]));
// Die Haltbarkeit des Tranks ist die Haltbarkeit des kleinsten Krautes.
int dur=10000000;
foreach (mapping mar:wirkungen)
{
if (mar[T_EXPIRE]>0 && dur>mar[T_EXPIRE])
{
dur=mar[T_EXPIRE];
}
}
if (dur==10000000)
{
dur=0;
}
attrib[T_EXPIRE]=dur;
debug(sprintf("Expire: %d\n",dur));
int maximum=0;
// Effekte rausrechnen, die nicht maximal sind
foreach (string kk, mixed val:attrib)
{
if (member(T_KRAUT_EFFECTS,kk)>=0)
{
if (val>0 && maximum<val)
{
maximum=val;
}
}
}
// Logeintrag erstellen, wenn nicht direkt von aussen (durch EM) gerufen.
if (!extern_call())
{
sl_exec("INSERT INTO rohdaten(uid, rnd, receipe, quality, car, da, dm, du, "
"dn, flt, fro, hI, hP, hK, hL, pa, pm, pu, ss, sp, sd) "
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11, ?12, ?13, ?14, "
"?15, ?16, ?17, ?18, ?19, ?20, ?21);",
this_player() ? getuid(this_player()) : "<unknown>",
zufall,
implode(map(plantids, #'to_string), ", "),
implode(map(qualities, #'to_string), ", "),
attrib[T_CARRY], attrib[T_DAMAGE_ANIMALS],
attrib[T_DAMAGE_MAGIC], attrib[T_DAMAGE_UNDEAD],
attrib[T_EFFECT_DURATION], attrib[T_FLEE_TPORT],
attrib[T_FROG], attrib[T_HEAL_DISEASE],
attrib[T_HEAL_POISON], attrib[T_HEAL_SP],
attrib[T_HEAL_LP], attrib[T_PROTECTION_ANIMALS],
attrib[T_PROTECTION_MAGIC], attrib[T_PROTECTION_UNDEAD],
attrib[T_SA_SPEED], attrib[T_SA_SPELL_PENETRATION],
attrib[T_SA_DURATION]);
}
// Maximal zwei positive Effekte.
int cteff=0;
foreach (string kk, mixed val : &attrib)
{
if (member(T_KRAUT_EFFECTS,kk)>=0)
{
// Nur die 2 staerksten positiven Wirkungen bleiben ueber (dazu
// muessen sie wirklich den gleichen Zahlenwert haben, kann bei
// Heilungen vorkommen, sonst eher unwahrscheinlich).
if (val>0 && maximum>val)
{
val=0;
}
// Thresholds. Zu zu grosse Wirkungen haben die Grenze als
// Auswirkung. Negative Wirkungen, die -T_MINIMUM_THRESHOLD nicht
// ueberschreiben, fallen weg. Bei den positiven bleibt ja ohnehin nur
// die staerkste Wirkung ueber, da gibt es erstmal keine
// Mindestgroesse mehr.
if (val>T_MAXIMUM_THRESHOLD)
{
val=T_MAXIMUM_THRESHOLD;
}
if (val < 0 && val > -T_MINIMUM_THRESHOLD)
{
val=0;
}
if (maximum==val && val>0)
{
cteff++;
// Voellig willkuerlich, was hier getroffen wird. Ob reproduzierbar,
// vermutlich ja, aber haengt mit der Mappingstruktur zusammen.
// Harhar.
if (cteff>2)
{
val=0;
}
}
}
}
debug(sprintf(" TRANKERGEBNIS: %O",attrib));
// Logeintrag erstellen.
if (!extern_call())
{
sl_exec("INSERT INTO traenke(uid, rnd, receipe, quality, car, da, dm, "
"du, dn, flt, fro, hI, hP, hK, hL, pa, pm, pu, ss, sp, sd) "
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11, ?12, ?13, ?14, "
"?15, ?16, ?17, ?18, ?19, ?20, ?21);",
this_player() ? getuid(this_player()) : "<unknown>",
zufall,
implode(map(plantids, #'to_string), ", "),
implode(map(qualities, #'to_string), ", "),
attrib[T_CARRY], attrib[T_DAMAGE_ANIMALS],
attrib[T_DAMAGE_MAGIC], attrib[T_DAMAGE_UNDEAD],
attrib[T_EFFECT_DURATION], attrib[T_FLEE_TPORT],
attrib[T_FROG], attrib[T_HEAL_DISEASE],
attrib[T_HEAL_POISON], attrib[T_HEAL_SP],
attrib[T_HEAL_LP], attrib[T_PROTECTION_ANIMALS],
attrib[T_PROTECTION_MAGIC], attrib[T_PROTECTION_UNDEAD],
attrib[T_SA_SPEED], attrib[T_SA_SPELL_PENETRATION],
attrib[T_SA_DURATION]);
}
return attrib;
}
mapping make_potion(object* plants)
{
// -> mappt nicht-Objekt zu 0, aber 0 ist auch ne gueltige PlantID. Daher
// müssen zerstoerten Objekte vorher raus.
// TODO: drauf verlassen, dass nur intakte Objekt enthalten sind?
if (member(plants, 0) >= 0)
raise_error(sprintf("make_potion() got invalid object in plant array "
"%.50O\n",plants));
int* plantids = plants->QueryPlantId();
int* qualities = plants->QueryProp(P_QUALITY);
return calculate_potion(plantids, qualities,
ZWEITIES->QueryFamilie(this_player()));
}
// AN: Sucht alle Pflanzen raus, in deren Namen der Suchbegriff "str"
// vorkommt und listet diese auf. Laeuft allerdings momentan noch in einen
// Fehler "index out of bounds", aber man muesste hier (TODO) sowieso mal
// von explode() auf strstr() umbauen, denke ich.
string _findplant(string str) {
int i, k;
string *ind, *tmp;
if(!str) return "";
write("Suche nach '"+str+"':\n\n");
ind = m_indices(map_ldfied);
for(i=0;i<sizeof(ind);i++) {
tmp = map_ldfied[ind[i]][0];
if( stringp(tmp[3]) &&
old_explode(lower_case(tmp[3]),str)[0] != lower_case(tmp[3])
||
stringp(tmp[4]) &&
old_explode(lower_case(tmp[4]),str)[0] != lower_case(tmp[4]))
write(" - "+tmp[3]+
(stringp(tmp[4])?" ("+tmp[4]+")":"")+" - "+tmp[0]+"\n");
}
return "";
}
// AN: Funktion liefert das Ergebnisarray aus make_potion() fuer eine Liste
// von Kraeutern, die als ", "-getrennte Kraut-IDs uebergeben werden muessen.
mixed _checkTrank(string str)
{
if (extern_call() && !allowed())
return 0;
string *ind, *args, name;
object *objs;
int k, l;
objs = ({});
if(!str) return "Keine Kraeuter uebergeben!";
ind = old_explode(str,",");
// ind = ({"180","11","53"});
for(int i=0;i<sizeof(ind);i++)
{
name = build_plantname(krautprops[to_int(ind[i])]);
write("Input: '"+name+"' ("+ind[i]+")\n");
objs += ({clone_object(PLANTDIR+name)});
}
mapping ragtest = make_potion(objs);
objs->remove();
/* name="";
for(int i=0;i<sizeof(ragtest);i++)
name = name + ragtest[i]+",";
write("Result: ({ "+name+" })\n");*/
return sprintf("%O\n",ragtest);
}
#define QUAL_BASE 0
#define QUAL_RND 1
#define DELAY_BASE 2
#define DELAY_RND 3
#define ALLOWED_DRIER "/items/kraeuter/trockner"
// Liefert die Trocknungsdaten eines Raumes aus, mit denen der Kraeuter-
// trockner dann das Kraut bearbeiten kann.
int *QueryDryingData() {
// Es muss allerdings das aufrufende Objekt ein Trockner-Clone sein,
// der in einem der zugelassenen Raeume stehen muss.
// Wenn das nicht der Fall ist, wird der Trockner das leere Array, das
// zurueckgegeben wird, als Indiz werten, dass er im falschen Raum steht.
if ( objectp(previous_object()) &&
load_name(previous_object()) == ALLOWED_DRIER &&
member(drying_data, load_name(environment(previous_object()))) &&
clonep(previous_object()) )
{
// Raum ermitteln, Delay/Quali errechnen, Ergebnisarray zurueckgeben.
string where = load_name(environment(previous_object()));
int delay = drying_data[where,DELAY_BASE]+
random(drying_data[where,DELAY_RND]);
int qual = drying_data[where,QUAL_BASE]+
random(drying_data[where,QUAL_RND]);
return ({ delay, qual });
}
return ({});
}
// Modifizieren der Trocknungsdaten.
// <room> muss der volle Dateiname des Raumes sein, ohne .c am Ende.
// <values> enthaelt die vier Parameter zu dem Raum in folgender Reihenfolge:
// ({ Quali-Basis, Quali-Zufallsanteil, Delay-Basis, Delay-Zufallsanteil })
// Wenn <values> nicht angeben wird oder 0 ist, werden die Daten zu <room>
// geloescht.
int|mapping SetDryingData(string room, int* values)
{
// keine Zugriffsrechte
if ( !allowed() )
return -1;
// <values> wurde nicht uebergeben? Dann Daten loeschen.
if ( !values )
{
m_delete(drying_data, room);
return 1;
}
// Ansonsten muessen 4 Integer-Werte als <values> uebergeben werden.
if ( sizeof(values) != 4 )
return -2;
if ( room[<2..<1] == ".c" )
room = room[..<3];
// Uebergebene Daten aendern direkt das Mapping der Trocknungsdaten.
m_add(drying_data, room, values...);
save();
return ([ room : drying_data[room,0]; drying_data[room,1];
drying_data[room,2]; drying_data[room,3]]);
}
varargs mapping QueryDrying()
{
return (allowed() ? drying_data : ([]) );
}
varargs int remove(int silent)
{
save();
return ::remove(silent);
}
/*
#define DRYINGDATA "/secure/ARCH/kraeutertrocknungsdaten"
private void ReadDryingData()
{
mixed data = explode(read_file(DRYINGDATA), "\n")-({""});
foreach(string line : data)
{
if ( line[0] == '#' )
continue;
string *fields = explode(line,";");
fields[1..] = map(fields[1..], #'to_int);
m_add(tmp_drying_data, fields...);
}
}*/