mirror of
git://git.psyced.org/git/psyced
synced 2024-08-15 03:25:10 +00:00
665 lines
20 KiB
C
665 lines
20 KiB
C
// vim:foldmethod=marker:syntax=lpc
|
||
// $Id: entity.c,v 1.127 2008/08/05 12:24:16 lynx Exp $
|
||
//
|
||
// entity.c: anything that has a UNI (see http://about.psyc.eu/uniform)
|
||
// this file handles some low-level issues with being an entity:
|
||
//
|
||
// * resolve UNLs and UNRs into UNIs and back, so higher level
|
||
// processing knows who they are dealing with, no matter which
|
||
// agent operated on this person's behalf
|
||
// * state: handle the inter-entity routing state variables, packet
|
||
// ids and message history
|
||
// * trust network: figure out who we trust and who we can ask to find
|
||
// out if someone is trustworthy etc etc
|
||
|
||
// local debug messages - turn them on by using psyclpc -DDentity=<level>
|
||
#ifdef Dentity
|
||
# undef DEBUG
|
||
# define DEBUG Dentity
|
||
#endif
|
||
|
||
#include <net.h>
|
||
#include <storage.h>
|
||
#include <uniform.h>
|
||
|
||
#ifdef ENTITY_STATE //{{{
|
||
# define HEADER_ONLY
|
||
# include "state.c"
|
||
# undef HEADER_ONLY
|
||
#endif //}}}
|
||
|
||
#ifndef MINIMUM_TRUST
|
||
# define MINIMUM_TRUST 5
|
||
#endif
|
||
#ifndef MAXIMUM_TRUST
|
||
# define MAXIMUM_TRUST 10
|
||
#endif
|
||
|
||
inherit NET_PATH "queue";
|
||
inherit NET_PATH "name";
|
||
inherit NET_PATH "storage";
|
||
#ifdef ENTITY_STATE //{{{
|
||
volatile mapping _state, _ostate, _cstate, _icount, _count, _memory;
|
||
volatile mapping temp, unused;
|
||
#endif //}}}
|
||
|
||
// this ifdef disables uni2unl, yet it doesn't remove all the queuing
|
||
// and async auth requests. should it?
|
||
#ifdef UNL_ROUTING //{{{
|
||
// while unl2uni in psyc/library is currently only used for local users'
|
||
// clients (and by FORK code, which in exchange doesn't use this code here)
|
||
// this is the place where general UNI to UNL mapping is made.
|
||
//
|
||
// each user has its own because a UNI may choose to tell different people
|
||
// different things.. in fact, a UNL may have reasons to propose different
|
||
// UNIs, too, but right now we're only being half consequent.. also there
|
||
// are no known applications making use of this feature anyhow ;)
|
||
volatile mapping uni2unl;
|
||
# ifdef USE_THE_RESOURCE
|
||
volatile string bare;
|
||
# endif
|
||
#endif //}}}
|
||
|
||
volatile string identification;
|
||
volatile mixed _tag, _source_tag;
|
||
|
||
#ifdef TAGGING
|
||
volatile protected mapping _tags;
|
||
|
||
/* currently, only net/jabber needs this */
|
||
void chain_callback(mixed tag, mixed chain_callback) {
|
||
if (_tags[tag])
|
||
_tags[tag] = ({ chain_callback }) + _tags[tag];
|
||
else
|
||
_tags[tag] = ({ chain_callback });
|
||
}
|
||
|
||
mixed execute_callback(mixed tag, mixed cbargs) {
|
||
mixed ret, cbchain;
|
||
// API: return 0 if there was no callback, callback return value or 1 if successful ?
|
||
unless(_tags[tag]) return 0;
|
||
ret = cbargs;
|
||
cbchain = _tags[tag];
|
||
m_delete(_tags, tag);
|
||
foreach(mixed cb : cbchain) {
|
||
ret = apply(cb, ret);
|
||
}
|
||
return ret || 1;
|
||
}
|
||
#endif
|
||
|
||
#ifdef ENTITY_STATE //{{{
|
||
inherit NET_PATH "state";
|
||
|
||
// lpc really needs an inline modifier!
|
||
# define STATE(context) ((context) ? _cstate : _state)
|
||
# define OSTATE(target) (member(_ostate, target) \
|
||
? _ostate[target] \
|
||
: _ostate[target] = ([ ]))
|
||
# define OMEMORY(target) (member(_memory, target) \
|
||
? _memory[target] \
|
||
: _memory[target] = m_allocate(0, 2))
|
||
#endif //}}}
|
||
|
||
#ifndef _flag_disable_module_trust
|
||
// first try to implement trust for remote ( means.. not a direct peer
|
||
// inside the friendsnet ) objects
|
||
volatile mapping _trust;
|
||
|
||
// returns -1 if there was no information for somebody...
|
||
// otherwise the level of trust \elem [0,10]
|
||
//
|
||
// is supposed to be overloaded by person/place for ignrores/friendships
|
||
// and banned people
|
||
int get_trust(string who, string by_whom) {
|
||
mixed t;
|
||
unless (member(_trust, who)) return -1;
|
||
|
||
if (by_whom) {
|
||
if (t = psyc_object(by_whom)) by_whom = t->qName();
|
||
|
||
unless (member(_trust[who], by_whom)) return -1;
|
||
t = get_trust(by_whom, 0);
|
||
|
||
if (t <= 0)
|
||
return 0;
|
||
return _trust[who][by_whom]* t / MAXIMUM_TRUST;
|
||
}
|
||
|
||
// should be cache the maximum.. maybe yes.. otherwise we end up
|
||
// calculating this stuff over and over again
|
||
return max(m_values(_trust[who]));
|
||
}
|
||
#endif
|
||
|
||
sendmsg(target, mc, data, vars, source, showingLog, callback) {
|
||
string t;
|
||
|
||
P3(("uni(%O): sendmsg(%O, %O, %O, %O, %O, %O)\n", ME, target, mc, data, vars, source, showingLog))
|
||
|
||
#ifdef UNL_ROUTING //{{{
|
||
if (t = uni2unl[target]) {
|
||
// maybe we should keep the _identification of the target somewhere
|
||
// in vars. but where? _target_identification ? and who needs that?
|
||
//
|
||
// great, if I ping xmpp:fippo@amessage.de this makes
|
||
// it xmpp:fippo@amessage.de/foo, but I DONT WANT THAT!
|
||
//
|
||
// also in order to allow for multiple psyc clients we need
|
||
// this if here, but i think we should rather ensure these
|
||
// candidates are not in uni2unl[].. --lynX
|
||
#if 1 //FIXME
|
||
if (!objectp(target) || (objectp(target)
|
||
&& target->vQuery("scheme") != "psyc"))
|
||
#endif
|
||
target = t;
|
||
}
|
||
#endif //}}}
|
||
if (mappingp(vars)) {
|
||
// uhm. better rename simul-efun-sendmsg and let sendmsg be defined in
|
||
// each object, doing return real_sendmsg(...); .. agree, but have to
|
||
// figure out a name for it.. maybe system_sendmsg? or submitmsg?
|
||
if (_tag && target == _source_tag &&! vars["_INTERNAL_tag_skip"]) {
|
||
unless (vars) vars = ([]);
|
||
vars["_tag_reply"] = _tag;
|
||
unless (vars["_INTERNAL_tag_again"]) _tag = 0;
|
||
}
|
||
#if 1 //DEBUG > 2
|
||
} else {
|
||
// dump trace w/out error...!?
|
||
P1(("uni:sendmsg(%O) called without vars\n", mc))
|
||
// vars = ([ ]); -- server works better with debug? no way!
|
||
//raise_error("uni:sendmsg called without vars\n");
|
||
#endif
|
||
}
|
||
#ifdef TAGGING
|
||
if (callback) {
|
||
// if the caller supplies a _tag, it is responsible for
|
||
// uniqueness within the user object
|
||
if (!vars["_tag"]) {
|
||
while(_tags[vars["_tag"] = RANDHEXSTRING]);
|
||
}
|
||
_tags[vars["_tag"]] = ({ callback });
|
||
}
|
||
#endif
|
||
return library_object()->sendmsg(target, mc, data, vars, source, showingLog);
|
||
}
|
||
|
||
#ifdef EXPERIMENTAL
|
||
// it's here, it may be nice or not.. we don't know..
|
||
// because nothing is using it
|
||
// <fippo> nothing is using it, because commiting code
|
||
// without any infrastructure is pointless.
|
||
mapping reply(mapping vars) {
|
||
mapping rvars = ([]);
|
||
|
||
if (member(vars, "_tag"))
|
||
rvars["_tag_reply"] = vars["_tag"];
|
||
if (member(vars, "_INTERNAL_source_resource"))
|
||
rvars["_INTERNAL_target_resource"] = vars["_INTERNAL_source_resource"];
|
||
if (member(vars, "_INTERNAL_target_resource"))
|
||
rvars["_INTERNAL_source_resource"] = vars["_INTERNAL_target_resource"];
|
||
|
||
rvars["_target"] = vars["_source_identification_reply"] || vars["_source_reply"] || vars["_source"];
|
||
return rvars;
|
||
}
|
||
#endif
|
||
|
||
msg(source, mc, data, vars) {
|
||
string t, sid;
|
||
|
||
P3(("uni::msg(%O, %O, %O, %O)\n", source, mc, data, vars))
|
||
#ifdef _flag_log_flow_messages
|
||
log_file("FLOW_MESSAGES", "%s %O ( %O <20> %O )\n", mc, strlen(data),
|
||
ME, source);
|
||
#endif
|
||
#ifdef UNL_ROUTING //{{{
|
||
# ifdef USE_THE_RESOURCE
|
||
// too expensive to m_delete if this is going to be re-set anytime soon
|
||
if (bare) bare = uni2unl[bare] = 0;
|
||
# endif
|
||
#endif //}}}
|
||
#ifdef CAST_STATE
|
||
// FIXME: we've got the cast state already made up by the
|
||
// cslave, but we need to route it down to the client...
|
||
// if there is any!
|
||
#endif
|
||
|
||
// deletes _tag if none is available
|
||
if (_tag = vars["_tag"]) {
|
||
P2(("tag %O for %O stored in %O\n", _tag, source, ME))
|
||
_source_tag = source;
|
||
}
|
||
#ifdef TAGGING
|
||
// allow _tag to trigger this? this is to circumvent some bug
|
||
// that doesn't provide us with _tag_reply as it should.. huh?
|
||
// but maybe it's okay anyway.. we'll see
|
||
//
|
||
// <fippo>: it is a bug on psyced.org only. As soon as you fix it,
|
||
// I will happily remove the || _tag
|
||
// <fippo>: not on psyced.org only. seems to happen if a second remote
|
||
// joins
|
||
if ((t = vars["_tag_reply"] || vars["_tag"]) && _tags[t]) {
|
||
if (execute_callback(t,
|
||
({ source, mc, data, vars }))) return 0;
|
||
}
|
||
#endif
|
||
|
||
#ifdef FORK //{{{
|
||
mixed route = vars["_context"] || source;
|
||
|
||
unless(member(_icount, route)) _icount[route] = 0;
|
||
if (member(vars, "_count")
|
||
&& vars["_count"] != to_string(_icount[route]++)) {
|
||
sendmsg(route, 0, 0, ([ "_count" : 0 ]));
|
||
if (vars["_count"] == "0") {
|
||
Reset(route, vars["_context"]);
|
||
_icount[route] = 0;
|
||
}
|
||
}
|
||
m_delete(vars, "_count");
|
||
m_delete(vars, "_target"); // TODO:: find out if there's a better solution,
|
||
// like not having target in the vars at all.
|
||
|
||
if (member(STATE(vars["_context"]), route)) {
|
||
//vars = _state[source] + vars; // overwrite by :
|
||
P3(("STATE-VARS: %O\n", STATE(vars["_context"])[route]));
|
||
foreach (mixed x, mixed y : STATE(vars["_context"])[route])
|
||
unless (member(vars, x)) vars[x] = y;
|
||
}
|
||
#else //FORK }}}
|
||
# ifndef _flag_disable_module_authentication
|
||
// person.c only calls this for stringp(source), so why check here again?
|
||
// because place/basic.c calls this for all sorts of sources. why this
|
||
// inconsistency? and what about local string sources? TODO
|
||
unless (objectp(source)) {
|
||
if (abbrev("_notice_authentication", mc)) {
|
||
string l = vars["_location"];
|
||
unless (qExists(l)) return 0;
|
||
#ifdef UNL_ROUTING //{{{
|
||
uni2unl[source] = l;
|
||
#endif //}}}
|
||
// i guess this should have been here.. let's try that
|
||
register_location(l, source, 1);
|
||
while (qSize(l))
|
||
apply(#'msg, shift(l));
|
||
qDel(l);
|
||
return 0;
|
||
} else if (abbrev("_error_invalid_authentication", mc)) {
|
||
string l = vars["_location"];
|
||
P1(("_error_invalid_authentication (%O) for %O (location does "
|
||
"%Oexist)\n", ME, l, qExists(l) ? "" : "not"))
|
||
if (qExists(l)) qDel(l);
|
||
return 0;
|
||
} else if (sid = vars["_source_identification"]) {
|
||
// && !(abbrev("_status_place_identification", mc)
|
||
// || abbrev("_status_description_person", mc))
|
||
// && !abbrev("_status", mc))
|
||
if (sid == source) {
|
||
t = 0;
|
||
// redundant _source_identification, pls fix the sender
|
||
// <fippo> I like debug code breaking...
|
||
//log_file("LUNITAR", "%O in %O (%O)\n", sid, ME, v("agent"));
|
||
// return in this case?
|
||
}
|
||
#ifdef UNL_ROUTING //{{{
|
||
else if (uni2unl[sid] == source) t = sid;
|
||
# ifdef USE_THE_RESOURCE
|
||
// our jabber gateway doesn't lie about identifications.
|
||
// just use it instead of the UNR
|
||
else if (vars["_INTERNAL_identification"]) {
|
||
// this must not get active if _nick_place is set!
|
||
if (!vars["_nick_place"]) {
|
||
bare = t = sid;
|
||
uni2unl[t] = source;
|
||
P2(("USE_THE_RESOURCE in %O: use %O instead of %O\n",
|
||
ME, t, source))
|
||
} else
|
||
t = source;
|
||
}
|
||
# endif
|
||
#endif //}}}
|
||
else {
|
||
// this will not trigger a systemwide _request_authentication
|
||
t = lookup_identification(source);
|
||
if (t == sid) {
|
||
#ifdef UNL_ROUTING //{{{
|
||
uni2unl[t] = source;
|
||
#endif //}}}
|
||
} else unless (objectp(t)) {
|
||
// we should intercept when the UNI is claimed to be on the
|
||
// same server where the location is - that's always legitimate
|
||
// and at the same time plain humbug. so we can either accept
|
||
// it or reject it. we shouldn't issue a _request_authentication
|
||
// in that case. TODO: FIXME QUICK ;)
|
||
P3((">>> qExists %O %O\n", qExists(source), source))
|
||
unless (qExists(source)) {
|
||
P1(("%O sending _request_authentication to %O for %O\n",
|
||
ME, sid, source))
|
||
library_object()->sendmsg(sid, "_request_authentication", 0,
|
||
([ "_location" : source ]));
|
||
qInit(source, 30, 5);
|
||
}
|
||
enqueue(source, ({ source, mc, data, vars }));
|
||
return 0;
|
||
}
|
||
}
|
||
if (t) {
|
||
# if 1
|
||
// we can either decide to see our own locations as source
|
||
// since that's what the code in person.c already does, we'll
|
||
// try this option first
|
||
if (t == ME) vars["_source_identification"] = ME;
|
||
else {
|
||
// is there any known reason why this was missing?
|
||
vars["_location"] = source;
|
||
m_delete(vars, "_source_identification");
|
||
source = t;
|
||
// don't trust what the client says
|
||
if (objectp(t)) vars["_nick"] = t->qName();
|
||
// user.c does something very similar - so this
|
||
// shouldn't be necessary, yet if i remove that
|
||
// line a client can propose its own _nick and
|
||
// will be successful. hm!
|
||
}
|
||
# else //{{{
|
||
// or copy them into a var, then compare everywhere
|
||
// this requires a rewrite of all of the v("locations") code
|
||
vars["_location"] = source;
|
||
m_delete(vars, "_source_identification");
|
||
source = t;
|
||
// don't trust what the client says
|
||
if (objectp(t)) vars["_nick"] = t->qName();
|
||
# endif //}}}
|
||
// do we really want to delete it also in the else case?
|
||
} else m_delete(vars, "_source_identification");
|
||
}
|
||
}
|
||
# endif // _flag_disable_module_authentication
|
||
#endif // !FORK
|
||
|
||
#ifndef _flag_disable_module_trust
|
||
// this mechanism does not work for objectp(source) because uni::msg is not
|
||
// called for objects. TODO
|
||
// this stuff works alot like _request_auth.. i still think there might be
|
||
// some code to share
|
||
if (member(vars, "_trustee") && vars["_trustee"]) {
|
||
string snicker = objectp(source) ? vars["_nick"] : source;
|
||
string trustee = vars["_trustee"];
|
||
int trustiness = get_trust(snicker, trustee);
|
||
|
||
P4(("trustee: %O -> %O\n", trustee, trustiness))
|
||
if (trustiness != -1) {
|
||
vars["_trust"] = trustiness;
|
||
} else { // do we want to check for get_trust(source) first.. maybe
|
||
// he is a friend..
|
||
string trustee_nick = trustee;
|
||
//
|
||
// TODO we dont even need to ask the trustee if we dont
|
||
// trust him anyway...
|
||
if (is_formal(trustee)) {
|
||
mixed *u = parse_uniform(trustee);
|
||
|
||
unless (u) {
|
||
return 1;
|
||
// evil, there is no trustiness
|
||
}
|
||
|
||
// an ideal psyc parser would recognize local _trustee
|
||
// and have it replaced by object.. but that's not the case
|
||
// we _could_ however do that by using _uniform_trustee
|
||
// and doing is_localhost on all _uniform's at parsing time
|
||
if (is_localhost(lower_case(u[UHost]))) trustee_nick = u[UNick];
|
||
}
|
||
if (get_trust(trustee_nick, 0) < MINIMUM_TRUST) return 1;
|
||
// entweder trustee ist ein local nick, dann kriegen wir den
|
||
// get_trust, aber sendmsg failed hier unten.. oder trustee
|
||
// ist eine uni, dann kann er zwar senden, findet die person
|
||
// aber nicht im ppl[]
|
||
|
||
unless (qExists("t"+snicker)) {
|
||
qInit("t"+snicker, 30, 5);
|
||
enqueue("t"+snicker, ({ source, mc, data, vars }));
|
||
P4(("%O no trustiness for %O in %O. Queue: %O\n",
|
||
ME, snicker, _trust, qDebug()))
|
||
// seeing this message is never useful, really
|
||
// the question is answered automatically, so a user
|
||
// would only get confused..
|
||
sendmsg(trustee, "_request_trustiness",
|
||
0 && "Do you trust [_identification] also known as [_nick]?",
|
||
([ "_identification" : source, "_nick" : MYNICK ]));
|
||
// we are lazy here.. but one trust request must be enough..
|
||
// for now
|
||
} else
|
||
enqueue("t"+snicker, ({ source, mc, data, vars }));
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
if (abbrev("_notice_trustiness", mc)) {
|
||
int trustiness = to_int(vars["_trustiness"]);
|
||
mixed l = vars["_identification"];
|
||
string s = objectp(source) ? vars["_nick"] : source;
|
||
|
||
if (objectp(l)) l = l->qName();
|
||
P4(("%O: %O got a trustiness of %d from %O.\n", ME,
|
||
l, trustiness, source))
|
||
unless (l) {
|
||
P0(("%O got %O without _identification\n", ME, mc))
|
||
return 0;
|
||
}
|
||
if (!member(_trust, l)) {
|
||
_trust[l] = ([ s : trustiness ]);
|
||
} else {
|
||
_trust[l][s] = trustiness;
|
||
}
|
||
l = "t"+l;
|
||
P4(("%O: Queue %O from %O.\n", ME, l, qDebug()))
|
||
if (qExists(l)) {
|
||
while (qSize(l))
|
||
apply(#'msg, shift(l));
|
||
qDel(l);
|
||
}
|
||
// dont drop the packet.. maybe we want to see this information
|
||
// return 0;
|
||
} else if (abbrev("_request_trustiness", mc)) {
|
||
if (get_trust(objectp(source) ? vars["_nick"]
|
||
: source, 0) < MINIMUM_TRUST) {
|
||
sendmsg(source, "_failure_trustiness",
|
||
"I dont know anything about [_identification]!", ([
|
||
"_identification" : vars["_identification"]
|
||
]));
|
||
return 0;
|
||
}
|
||
|
||
int trustiness = get_trust(objectp(vars["_identification"]) ?
|
||
vars["_identification"]->qName() : vars["_identification"], 0);
|
||
P4(("%O: %O asked me for trustiness (%d) of %O.\n", ME, source,
|
||
trustiness, vars["_identification"]))
|
||
if (-1 != trustiness) {
|
||
|
||
sendmsg(source, "_notice_trustiness", "I trust [_identification] like [_trustiness] times bigger than others.",
|
||
([ "_identification" : vars["_identification"],
|
||
"_nick" : MYNICK,
|
||
"_trustiness" : trustiness ]));
|
||
}
|
||
}
|
||
#endif // _flag_disable_module_trust
|
||
return 1;
|
||
}
|
||
|
||
create() {
|
||
// would be nice to skip this for blueprints, but at this point we have
|
||
// no way to distinguish original places from blueprints - we have no
|
||
// qName() yet - so all we could do would be to look at our object path.
|
||
//unless (clonep() && qName()) return; // do not initialize blueprints
|
||
// a different approach would be to rename this into eCreate() and have
|
||
// it called in the right places. TODO (cleanup)
|
||
#ifdef UNL_ROUTING //{{{
|
||
ASSERT("entity::create() !uni2unl", !uni2unl, uni2unl)
|
||
uni2unl = ([ ]);
|
||
#endif //}}}
|
||
#ifndef _flag_disable_module_trust
|
||
_trust = ([ ]);
|
||
#endif
|
||
#ifdef ENTITY_STATE //{{{
|
||
// wieso packen wir die counter nicht mit ins state-array. also
|
||
// mehrdimensional ?
|
||
_state = ([ ]);
|
||
_ostate = ([ ]);
|
||
_cstate = ([ ]);
|
||
_icount = ([ ]);
|
||
_count = ([ ]);
|
||
_memory = ([ ]);
|
||
temp = ([]);
|
||
#endif //}}}
|
||
qCreate();
|
||
#ifdef TAGGING
|
||
_tags = ([ ]);
|
||
#endif
|
||
}
|
||
|
||
#ifdef ENTITY_STATE //{{{
|
||
|
||
// reset the state.. which means: _count == 0
|
||
void Reset(mixed source, mixed isContext) {
|
||
|
||
PT(("Reset(%O) in %O\n", source, ME));
|
||
|
||
STATE(isContext)[source] = ([ ]);
|
||
}
|
||
|
||
void Assign(mixed source, string key, mixed value, mixed isContext) {
|
||
mapping state = STATE(isContext);
|
||
|
||
PT(("Assign(%O, %O, %O) in %O\n", source, key, value, ME));
|
||
unless (member(state, source)) {
|
||
state[source] = ([]);
|
||
}
|
||
if (value) {
|
||
state[source][key] = value;
|
||
} else m_delete(state[source], key);
|
||
}
|
||
|
||
void Augment(mixed source, string key, mixed value, mixed isContext) {
|
||
mapping state = STATE(isContext);
|
||
|
||
PT(("Augment(%O, %O, %O) in %O\n", source, key, value, ME));
|
||
unless (member(state, source)) {
|
||
state[source] = ([]);
|
||
}
|
||
_augment(state[source], key, value);
|
||
}
|
||
|
||
void Diminish(mixed source, string key, mixed value, mixed isContext) {
|
||
int i;
|
||
mapping state = STATE(isContext);
|
||
|
||
PT(("Diminish(%O, %O, %O) in %O\n", source, key, value, ME));
|
||
if (member(state, source)) {
|
||
_diminish(state[source], key, value);
|
||
}
|
||
}
|
||
|
||
int outstate(string target, string key, mixed value, int hascontext) {
|
||
mapping timemachine = OMEMORY(target);
|
||
mapping state = OSTATE(target);
|
||
mapping t = temp[target];
|
||
int mod;
|
||
|
||
if (hascontext) {
|
||
//PT(("%O sends messages in a context without using group/master.c.", ME))
|
||
return 1;
|
||
}
|
||
|
||
if (key[0] == '_') {
|
||
mod = ':';
|
||
} else {
|
||
mod = key[0];
|
||
key = key[1..];
|
||
}
|
||
|
||
unless (t) t = temp[target] = ([ ]);
|
||
|
||
unless (unused) unused = copy(state);
|
||
m_delete(unused, key);
|
||
|
||
switch(mod) {
|
||
case ':':
|
||
if (timemachine[key, 1] == -1) break;
|
||
if (member(timemachine, key)) {
|
||
if (timemachine[key] == value) {
|
||
if (timemachine[key, 1] == STATE_MAX2) {
|
||
break;
|
||
}
|
||
if (timemachine[key, 1] == STATE_MIN2) {
|
||
t["="+key] = value;
|
||
state[key] = value;
|
||
return 0;
|
||
}
|
||
timemachine[key, 1]++;
|
||
} else if (timemachine[key, 1] - 1 == STATE_MIN2) {
|
||
t["="+key] = "";
|
||
m_add(timemachine, key, value, 1);
|
||
} else if (timemachine[key, 1] <= STATE_MIN2) {
|
||
m_add(timemachine, key, value, 1);
|
||
} else timemachine[key, 1]--;
|
||
} else m_add(timemachine, key, value, 1);
|
||
break;
|
||
case '=':
|
||
if ("" == value) {
|
||
m_delete(state, key);
|
||
m_delete(timemachine, key);
|
||
break;
|
||
}
|
||
if (member(state, key) && state[key] == value) {
|
||
return 0;
|
||
}
|
||
m_add(timemachine, key, value, -1);
|
||
state[key] = value;
|
||
break;
|
||
case '+':
|
||
_augment(state, key, value);
|
||
break;
|
||
case '-':
|
||
_diminish(state, key, value);
|
||
break;
|
||
default:
|
||
raise_error("Illegal variable modifier in '" + key +
|
||
"' encountered in entity:outstate.\n");
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
mapping state(string target, int hascontext) {
|
||
mapping t, t2 = ([ ]);
|
||
mapping timemachine = OMEMORY(target);
|
||
|
||
if (hascontext) {
|
||
PT(("%O sends messages in a context without using group/master.c.", ME))
|
||
return ([]);
|
||
}
|
||
|
||
if (unused) {
|
||
foreach (string key : unused) {
|
||
// variables assigned manually are taken to be permanent.
|
||
if (timemachine[key, 1] != -1)
|
||
t2[":" + key] = "";
|
||
}
|
||
|
||
unused = 0;
|
||
}
|
||
|
||
t = temp[target] || ([ ]);
|
||
m_delete(temp, target);
|
||
|
||
// rock hard optimization
|
||
unless (sizeof(t))
|
||
return t2;
|
||
unless (sizeof(t2))
|
||
return t;
|
||
return t2 + t;
|
||
}
|
||
#endif // ENTITY_STATE }}}
|