blob: 47bfb0eb650e219c4cc89f229bf3a32525da5ad9 [file] [log] [blame]
// MorgenGrauen MUDlib
//
// thing/description.c -- description handling for standard objects
//
// $Id: description.c 9561 2016-05-25 19:33:22Z Zesstra $
#pragma strict_types
#pragma save_types
#pragma range_check
#pragma no_clone
#pragma pedantic
#include <tls.h>
#include <thing/description.h>
#include <thing/material.h>
#include <thing/lighttypes.h>
#include <exploration.h> // wegen EPMASTER
#include <class.h>
#define NEED_PROTOTYPES
#include <thing/properties.h>
#include <thing/language.h>
#undef NEED_PROTOTYPES
#include <properties.h>
// Variable um den FP abzuspeichern
private nosave mixed *explore;
// Prototypen
public string short();
public varargs string long(int mode);
// #####################
//######################## System-Funktionen ############################
// #####################
// Objekt erzeugen
protected void create()
{
string poid, tpid;
object tp;
SetProp( P_NAME, "Ding" );
SetProp( P_SHORT, "Nichts besonderes" );
SetProp( P_LONG, 0 );
Set( P_ADJECTIVES, ({}) );
Set( P_NAME_ADJ, ({}) );
Set( P_IDS, ({}) );
Set( P_CLASS, ({}) );
Set( P_READ_DETAILS, ([]), F_VALUE);
Set( P_READ_DETAILS, SECURED|NOSETMETHOD, F_MODE_AS);
Set( P_DETAILS, ([]), F_VALUE);
Set( P_DETAILS, SECURED|NOSETMETHOD, F_MODE_AS );
Set( P_SMELLS, ([]), F_VALUE);
Set( P_SMELLS, SECURED|NOSETMETHOD, F_MODE_AS );
Set( P_SOUNDS, ([]), F_VALUE);
Set( P_SOUNDS, SECURED|NOSETMETHOD, F_MODE_AS );
Set( P_TOUCH_DETAILS, ([]), F_VALUE);
Set( P_TOUCH_DETAILS, SECURED|NOSETMETHOD, F_MODE_AS );
// Aenderungen an dieser Prop sind tabu.
Set( P_CLONE_TIME, NOSETMETHOD|SECURED, F_MODE_AS );
// Id des Cloners und des Besitzers kommen nach P_CLONER
if (objectp( tp=this_interactive()||this_player() ))
{
tpid=geteuid(tp);
if (!(tpid=geteuid(tp))) tpid=getuid(tp);
}
else
tpid="UNKNOWN";
if (previous_object())
{
if (!(poid = geteuid(previous_object())))
poid = getuid(previous_object());
}
else
poid="UNKNOWN";
Set( P_CLONER, (poid != tpid ? poid+":"+tpid: tpid) );
Set( P_CLONER, NOSETMETHOD|SECURED, F_MODE_AS );
// Gibt es FPs ?
explore = (mixed *)EPMASTER->QueryExplore();
return;
}
protected void create_super() {
set_next_reset(-1);
}
// ##################
//######################### Forscherpunkte ##############################
// ##################
// FP vergeben
static void GiveEP( int type, string key )
{
//Abbruch, wenn vergebendes Objekt schon zerstoert ist. ACHTUNG: Auch
//diese Abfrage wuerde kein FP vergeben werden, wenn sich das Objekt im
//vergebenden Kommando zerstoert, da der Driver call_other() von zerstoerten
//Objekten ignoriert!
if (!objectp(this_object())) return;
if (this_player()) this_player()->countCmds( type, key );
if (explore&&!extern_call()&&
(explore[0] == type) && (member(explore[1], key) >= 0) )
EPMASTER->GiveExplorationPoint(key);
return;
}
// Manche Objekte koennen mit rename_object einen neuen Filenamen bekommen.
// Danach sollte der EPMASTER neu nach den Details befragt werden.
void __reload_explore()
{
explore = (mixed *)EPMASTER->QueryExplore();
return;
}
// #################
//########################## ID-Management ##############################
// #################
// Gibt eine ID zurueck, die den Ladenamen, die aktuelle Kurzbeschreibung und
// die aktuelle Langbeschreibung beruecksichtigt. Diese ID ist ein Hashwert.
public string description_id() {
return hash(TLS_HASH_MD5, load_name() + short() + long());
}
/* Ids muessen uebergeben werden, da unit machmal mit plural-Ids, */
/* also anderen als den normalen arbeiten muss. */
int match_item( string str, string *ids )
{
string *obj,*ads;
int len, i;
// Parameter falsch? Passt nicht ...
if(!str) return 0;
if(!pointerp(ids)) return 0;
if(!sizeof(ids)) return 0;
// Ist schon so dabei? Na Klasse :-)
if(member(ids,str)>-1) return 1;
// Keine Adjektive vorhanden ... passt nicht.
if (!(ads=QueryProp(P_ADJECTIVES))) return 0;
if (!sizeof(ads)) return 0;
// Nur ein Wort? Dann passt es nicht
obj=explode(str," ");
if (!(len=sizeof(obj)-1)) return 0;
// Adjektive stehen am Anfang. Sobald es nicht mehr passt,
// muss es das Objektiv sein.
while(i<len&&member(ads,obj[i])>-1) i++;
return (member(ids,implode(obj[i..len]," "))>-1);
}
// Wird vom Gamedriver aufgerufen (present)
// Hat dieser Gegenstand die ID str?
// lvl wird ignoriert
varargs int id( string str, int lvl )
{
string str2, tmp;
int count;
string|string* ids;
// Kein Argument? Dann passt es nicht ...
if (!stringp(str)) return 0;
// Keine IDs? Auch nicht gut ...
if (!pointerp(ids=QueryProp(P_IDS))) return 0;
if (!sizeof(ids)) return 0;
ids += ({ ("\n" + object_name()),
("\n" + explode(object_name(),"#")[0]) });
// Id passt? Alles klar :-)
if (match_item( str, ids )) return 1;
// Die id hat eine Zahl drin. Wenn Zahl die Rohid passt,
// dann gucken, ob man selber das nte Element ist.
if (sscanf( str, "%s %d%s", str2, count, tmp)<2) return 0;
if (count<1) return 0;
if (sizeof(tmp)) return 0;
if (!match_item( str2, ids )) return 0;
if (!environment()) return 0;
return present(str2,count,environment())==this_object();
}
// Gleich eine ganze Liste von ids testen
int match_ids(string *list)
{
string *ids;
// Ungueltige Parameter? Weg hier ...
if (!pointerp(list)) return 0;
if (!pointerp(ids=QueryProp(P_IDS))) return 0;
ids += ({ ("\n" + object_name()),
("\n" + explode(object_name(),"#")[0]) });
return sizeof( list & ids );
}
// ID hinzufuegen
void AddId( string|string* str )
{
if (stringp(str)) str = ({ str });
if (pointerp(str))
// Doppelte eliminieren
Set( P_IDS, Query(P_IDS, F_VALUE)-str+str, F_VALUE);
return;
}
// ID entfernen
void RemoveId(string|string* str)
{
if (stringp(str)) str = ({ str });
if (pointerp(str))
Set(P_IDS,Query(P_IDS, F_VALUE)-str, F_VALUE);
return;
}
// Alle Ids auf einmal setzen
static string* _set_ids( string* ids )
{
Set( P_IDS,({}));
AddId(ids);
return Query(P_IDS, F_VALUE);
}
// Adjektiv hinzufuegen
void AddAdjective(string|string* str)
{
if (stringp(str)) str = ({ str });
if (pointerp(str))
// Doppelte eliminieren
Set( P_ADJECTIVES, Query(P_ADJECTIVES, F_VALUE)-str+str, F_VALUE );
return;
}
// Adjektiv entfernen
void RemoveAdjective(string|string* str)
{
if (stringp(str)) str = ({ str });
if (pointerp(str))
Set( P_ADJECTIVES, Query(P_ADJECTIVES, F_VALUE) - str, F_VALUE );
return;
}
// Alle Adjektive auf einmal setzen
static string* _set_adjectives(string* adjectives)
{
Set( P_ADJECTIVES,({}), F_VALUE);
AddAdjective(adjectives);
return Query(P_ADJECTIVES, F_VALUE);
}
// ################
//########################## Namensgebung ###############################
// ################
// Im Fall von mehreren Adjektiven muessen diese mit komma
// zusamengebaut werden, dazu muss ich das leerzeichen aber erstmal
// abschneiden und so weiter ...
private string depointer_adj( string* adj, int casus, int demon ) {
string msg;
int start;
string res,a;
adj = map( adj, #'DeclAdj, casus, demon );
start = 1;
res = "";
foreach( a: adj ) {
res += (start ? "" : ", ") + a[0..<2];
start = 0;
}
return res + " ";
}
// Wie lautet der Name des Objekts?
varargs string name(int casus,int demon)
{
mixed sh, adj;
int art, plural;
art = QueryProp(P_ARTICLE);
// RAW: direkt zurueckgeben ohne Verarbeitung
if (casus == RAW )
{
if(pointerp(QueryProp(P_NAME)))
return QueryProp(P_NAME)[WER];
return QueryProp(P_NAME);
}
// Unsichtbar: Etwas
if (QueryProp(P_INVIS))
return ({ "etwas", "von etwas", "etwas", "etwas" })[casus];
// Kein Name? Schade ...
if (!(sh=QueryProp(P_NAME)) ||
(stringp(sh) && !sizeof(sh))) return 0;
// P_NAME pruefen.
if (pointerp(sh) && sizeof(sh) != 4)
raise_error(sprintf("Ungueltige Arraygroesse in P_NAME: %d\n",
sizeof(sh)));
// Plural .. Namen verwursten
if (plural = QueryProp(P_PLURAL))
{
// Selber Artikel suchen ist nicht ...
if (demon==2||!art) demon = 0;
// falls P_NAME ein Array mit Faellen enthaelt, den richtigen
// extrahieren...
if (pointerp(sh)) {
sh = sh[casus];
}
else {
// sonst versuchen, zu deklinieren.
int last = sh[<1];
if (casus == WEM&&last!='s'&&last!='n') sh = sh + "n";
}
// Sind Adjektive vorhanden?
if ( pointerp(adj = QueryProp(P_NAME_ADJ)) && sizeof(adj))
adj = depointer_adj(adj,casus,demon);
if (!stringp(adj)) adj = "";
return sprintf("%s%s%s%s",QueryArticle(casus,demon,0),adj,
(plural < 2 ? "":(plural < 8 ?
({ "zwei ", "drei ", "vier ", "fuenf ", "sechs ",
"sieben " })[plural-2] : to_string(plural)+" ")),sh);
}
// Name ist Pointer: Einfach den richtigen auswaehlen
if (pointerp(sh))
sh = sh[casus];
// Ansonsten doch wieder verwursten ...
else if (stringp(sh))
{
int last = sh[<1];
switch(casus)
{
case WEM:
case WEN:
if ( art && last=='e'&&QueryProp(P_GENDER) == MALE)
sh = (string)sh + "n";
break;
case WESSEN:
if( !art )
{
switch(last)
{
case 'x':
case 's':
case 'z':
sh = (string)sh + "'";
break;
default:
sh = (string)sh + "s";
}
}
else
{
switch(last)
{
default:
if (QueryProp(P_GENDER)!=FEMALE)
sh=(string)sh+"s";
break;
case 'e':
if (QueryProp(P_GENDER)==MALE)
sh=(string)sh+"n";
case 'x':
case 's':
case 'z':
break;
} /* switch (last) */
} /* if( !art ) else */
} /* switch( casus ) */
} /* pointerp(sh) */
// RAW? Dann mal zurueck
if (demon == RAW) return (string)sh;
// Selber Artikel suchen ...
if (demon==2)
{
if (art)
demon = SuggestArticle();
else
demon=0; // Kein Artikel: egal (SuggestArticle ist zeitaufwendig)
}
if (pointerp(adj = QueryProp(P_NAME_ADJ)) && sizeof(adj))
adj = depointer_adj(adj,casus,demon);
if (!stringp(adj)) adj = "";
return QueryArticle( casus, demon )+adj+sh;
}
// Grossgeschriebenen Namen zurueckgeben
varargs string Name( int casus, int demon )
{
return capitalize(name( casus, demon )||"");
}
// Langbeschreibung anzeigen
public varargs string long(int mode)
{
return process_string( QueryProp(P_LONG) );
}
// Kurzbeschreibung anzeigen, falls nicht unsichtbar
public string short()
{
string sh;
// Unsichtbar? Dann gibts nichts zu sehen ...
if (QueryProp(P_INVIS)||!(sh=QueryProp(P_SHORT)))
return 0;
return process_string(sh)+".\n";
}
// Namens-Adjektive setzen
static string* _set_name_adj(string|string* adjectives)
{
if (!adjectives)
adjectives=({});
// In Array umwandeln
else if ( !pointerp(adjectives))
adjectives = ({ to_string(adjectives) });
return Set( P_NAME_ADJ, adjectives );
}
// ############################
//#################### Details, Gerueche, Laerm #########################
// ############################
// Low-level Funktion zum Ergaenzen von Details, wird von den div. Add...()
// gerufen, die das richtige Mapping uebergeben.
// Aendert das Mapping <details> direkt.
private void _add_details(string|string* keys,
string|string*|mapping|closure descr,
mapping details )
{
if (stringp(keys))
details[keys]=descr;
else if (pointerp(keys))
{
foreach(string key : keys)
details[lower_case(key)]=descr;
}
else
raise_error("Wrong type to argument 1, expected string|string*.\n");
}
// Low-level Funktion zum Entfernen von Details, wird von den div. Remove...()
// gerufen, die das richtige Mapping uebergeben.
// Aendert das Mapping <details> direkt.
private void _remove_details(string|string* keys, mapping details )
{
if (stringp(keys))
details -= ([keys]);
else if (pointerp(keys))
details -= mkmapping(keys);
else
raise_error("Wrong type to argument 1, expected string|string*.\n");
}
// Detail(s) hinzufuegen
void AddDetail(string|string* keys, string|string*|mapping|closure descr)
{
int i;
mapping details;
details = Query(P_DETAILS, F_VALUE);
// _add_details() aendern das Mapping direkt, Set etc. nicht noetig.
return _add_details(keys, descr, details);
}
// Detail(s) entfernen
varargs void RemoveDetail(string|string* keys )
{
// Alle loeschen geht direkt ...
if (!keys )
Set(P_DETAILS, ([]), F_VALUE);
else
// _remove_details() aendern das Mapping direkt, Set etc. nicht noetig.
_remove_details(keys, Query(P_DETAILS, F_VALUE));
}
// SpecialDetail hinzufuegen
void AddSpecialDetail(string|string* keys, string functionname )
{
closure cl;
// Absichern! Sonst koennte jeder interne Funktionen aufrufen
if (extern_call() &&
(geteuid(previous_object()) != geteuid() || process_call()) &&
!(object_name(previous_object()) == "/obj/doormaster" &&
functionname == "special_detail_doors") )
raise_error( "Illegal use of AddSpecialDetail!\n" );
// Closure generieren
if ( !stringp(functionname)||
!(cl = symbol_function( functionname, this_object())) )
return;
// Detail hinzufuegen
AddDetail( keys, cl );
return;
}
// SpecialDetail(s) entfernen
void RemoveSpecialDetail(string|string* keys )
{
// RemoveSpecialDetail(0) wuerde sonst ALLE Details (auch die
// 'normalen') loeschen
if (pointerp(keys)||stringp(keys))
RemoveDetail(keys);
return;
}
// Lesbares Detail einfuegen
void AddReadDetail(string|string* keys,
string|string*|mapping|closure descr )
{
// _add_details() aendern das Mapping direkt, Set etc. nicht noetig.
return _add_details(keys, descr, Query(P_READ_DETAILS, F_VALUE));
}
// Lesbare(s) Detail(s) entfernen
varargs void RemoveReadDetail(string|string* keys )
{
// Alle loeschen geht direkt ...
if (!keys )
Set(P_READ_DETAILS, ([]), F_VALUE);
else
// _remove_details() aendern das Mapping direkt, Set etc. nicht noetig.
_remove_details(keys, Query(P_READ_DETAILS, F_VALUE));
}
// Geraeusch(e) dazufuegen
void AddSounds(string|string* keys,
string|string*|mapping|closure descr )
{
// _add_details() aendern das Mapping direkt, Set etc. nicht noetig.
return _add_details(keys, descr, Query(P_SOUNDS, F_VALUE));
}
// Geraeusch(e) entfernen
varargs void RemoveSounds(string|string* keys )
{
// Alle loeschen geht direkt ...
if (!keys )
Set(P_SOUNDS, ([]), F_VALUE);
else
// _remove_details() aendern das Mapping direkt, Set etc. nicht noetig.
_remove_details(keys, Query(P_SOUNDS, F_VALUE));
}
// Geru(e)ch(e) hinzufuegen
void AddSmells(string|string* keys,
string|string*|mapping|closure descr )
{
// _add_details() aendern das Mapping direkt, Set etc. nicht noetig.
return _add_details(keys, descr, Query(P_SMELLS, F_VALUE));
}
// Geru(e)ch(e) entfernen
varargs void RemoveSmells(string|string* keys )
{
// Alle loeschen geht direkt ...
if (!keys )
Set(P_SMELLS, ([]), F_VALUE);
else
// _remove_details() aendern das Mapping direkt, Set etc. nicht noetig.
_remove_details(keys, Query(P_SMELLS, F_VALUE));
}
// Tastbare(s) Detail(s) hinzufuegen
void AddTouchDetail(string|string* keys,
string|string*|mapping|closure descr )
{
// _add_details() aendern das Mapping direkt, Set etc. nicht noetig.
return _add_details(keys, descr, Query(P_TOUCH_DETAILS, F_VALUE));
}
// Tastbare(s) Detail(s) entfernen
varargs void RemoveTouchDetails(string|string* keys )
{
// Alle loeschen geht direkt ...
if (!keys )
Set(P_TOUCH_DETAILS, ([]), F_VALUE);
else
// _remove_details() aendern das Mapping direkt, Set etc. nicht noetig.
_remove_details(keys, Query(P_TOUCH_DETAILS, F_VALUE));
}
// Detailinfos fuer Detail key, Spieler hat die Rasse race
// und benutzt seinen Sinn sense
varargs string GetDetail(string key, string race, int sense)
{
string|string*|mapping|closure detail;
if (stringp(race)) race = lower_case(race);
switch(sense)
{
case SENSE_SMELL: detail=Query(P_SMELLS, F_VALUE)[key];
sense=EP_SMELL; break;
case SENSE_SOUND: detail=Query(P_SOUNDS, F_VALUE)[key];
sense=EP_SOUND; break;
case SENSE_TOUCH: detail=Query(P_TOUCH_DETAILS, F_VALUE)[key];
sense=EP_TOUCH; break;
case SENSE_READ: detail=Query(P_READ_DETAILS, F_VALUE)[key];
sense=EP_RDET;
break;
default: detail=Query(P_DETAILS, F_VALUE)[key];
sense=EP_DETAIL; break;
}
if (!stringp(detail))
{
if (closurep(detail))
detail = (string)funcall(detail,key);
else if (mappingp(detail))
detail = (string)(detail[race]||detail[0]);
else if (pointerp(detail))
detail = (string)(detail[random(sizeof(detail))]);
}
// FP vergeben (so vorhanden ;-) )
if (detail) GiveEP(sense,key);
return detail;
}
// TODO: OBSOLET (Libgrep notwendig)
void read( string str ) {
raise_error("Diese Funktion existiert nicht mehr.\n");
}
// ######################
//####################### Zugriffsfunktionen ############################
// ######################
// Dienen dazu, die direkte Manipulation der Props von aussen zu erschweren.
// Filter, um Specialdetails zu eliminieren
// erstellt ausserdem ne Kopie vom Mapping. (Wichtig!)
// Wird vor allem benoetigt, um P_DETAILS in P_DETAILS und
// P_SPECIAL_DETAILS zu treffen.
private int _closures(string x, mapping details, int yes )
{
return yes ? closurep(details[x]) : !closurep(details[x]);
}
static mapping _query_details()
{
return filter_indices(Query(P_DETAILS, F_VALUE), #'_closures,
Query(P_DETAILS, F_VALUE),0);
}
static mapping _query_special_details()
{
return filter_indices(Query(P_DETAILS, F_VALUE),#'_closures,
Query(P_DETAILS, F_VALUE),1);
}
static mapping _query_read_details() {
return deep_copy(Query(P_READ_DETAILS, F_VALUE));
}
static mapping _query_sound_details() {
return deep_copy(Query(P_SOUNDS, F_VALUE));
}
static mapping _query_smell_details() {
return deep_copy(Query(P_SMELLS, F_VALUE));
}
// ##########################
//##################### Klassen-Mitgliedschaft ##########################
// ##########################
// Klasse hinzufuegen
public void AddClass(string|string* str)
{
if (stringp(str))
str = ({ str });
// Aliase aufloesen und implizite Klassen addieren.
str = (string*)CLASSDB->AddImplicitClasses(str);
// Summe mit alten Klassen bilden und Doppelte eliminieren
str = str + Query(P_CLASS, F_VALUE);
Set( P_CLASS, m_indices(mkmapping(str)), F_VALUE);
return;
}
// Klasse entfernen
void RemoveClass(string|string* str)
{
if (stringp(str))
str = ({ str });
// Aliase aufloesen und implizite Klassen addieren.
str = (string*)CLASSDB->AddImplicitClasses(str);
// Und alle - inklusive impliziter Klassen - entfernen
// TODO: Pruefen, ob dies die richtige Entscheidung ist.
Set( P_CLASS, Query(P_CLASS, F_VALUE)-str, F_VALUE);
return;
}
// Ist das Objekt Mitglied der Klasse str?
int is_class_member(string|string* str)
{
// Keine Klasse, keine Mitgliedschaft ...
if (!str || str=="")
return 0;
// Es sollte schon ein Array sein
if (stringp(str))
str = ({ str });
// Klassen und Ids ins Array
string *classes=QueryProp(P_CLASS);
if (!pointerp(classes))
return 0;
// .. und testen
foreach(string class : str)
if (member(classes,class) > -1 ) return 1;
return 0;
}
// Klasse direkt setzen abfangen
static string* _set_class(string* classes )
{
Set( P_CLASS, ({}), F_VALUE );
AddClass(classes);
return QueryProp(P_CLASS);
}
// #####################
//######################## Material-Handling ############################
// #####################
// Material setzen
static mapping _set_material(mapping|string|string* mat )
{
mapping mats = ([]);
if (mappingp(mat))
{
if( !sizeof(mat) || !widthof(mat) )
raise_error(sprintf("P_MATERIAL: expected mapping with at least one "
"key and one value, got %.50O\n",mat));
else
mats = mat;
}
else if (stringp(mat))
mats[mat]=100;
else
{
int sz = sizeof(mat);
// Kommt dann vor, wenn <mat> 0 oder ({}) ist.
if ( !sz )
raise_error(sprintf("P_MATERIAL: expected string or non-empty "
"mapping|string*, got %.50O.\n", mat));
mats = mkmapping(mat, allocate(sz, 100/sz));
}
return Set( P_MATERIAL, mats, F_VALUE );
}
// Woraus besteht das Objekt?
static mapping _query_material()
{
mixed res;
if ( !mappingp(res = Query(P_MATERIAL, F_VALUE)) )
return ([MAT_MISC:100]);
return res;
}
// Anteil von mat am Objekt?
int QueryMaterial( string mat )
{
mapping mats;
if ( !mappingp(mats = QueryProp(P_MATERIAL)) )
return 0;
return mats[mat];
}
// Anteil der Gruppe am Objekt
int QueryMaterialGroup( string matgroup )
{
return (int)call_other( MATERIALDB, "MaterialGroup",
QueryProp(P_MATERIAL), matgroup );
}
string MaterialList( int casus, mixed idinf )
{
return (string)call_other( MATERIALDB, "ConvMaterialList",
QueryProp(P_MATERIAL), casus, idinf );
}
static int _set_size(int sz) {
//Groesse muss > 0 sein, alles andere ist unsinnig! (0 und neg. Groessen
//haben keine phys. Relevanz und machen u.U. Probleme mit Objekten, die
//Schaden in Abhaengigkeit der Groesse machen)
if (sz>0)
Set(P_SIZE,sz,F_VALUE);
return(Query(P_SIZE,F_VALUE));
}
// P_CLONE_TIME
static int _query_clone_time() { return object_time(); }