#pragma once #ifdef CSQC #include #endif const int CBC_ORDER_FIRST = 1; const int CBC_ORDER_LAST = 2; const int CBC_ORDER_EXCLUSIVE = 3; const int CBC_ORDER_ANY = 4; bool CallbackChain_ReturnValue; // read-only field of the current return value /** * Callbacks may be added to zero or more callback chains. */ CLASS(Callback, Object) /** * a callback function is like this: * bool mycallback() * { * do something * return false; * } */ ATTRIB(Callback, cbc_func, bool()); CONSTRUCTOR(Callback, bool() func) { CONSTRUCT(Callback); this.cbc_func = func; } ENDCLASS(Callback) /** * Callback chains contain zero or more callbacks. */ CLASS(CallbackChain, Object) CLASS(CallbackNode, Object) ATTRIB(CallbackNode, cbc, Callback); ATTRIB(CallbackNode, cbc_next, CallbackNode); ATTRIB(CallbackNode, cbc_order, int, 0); CONSTRUCTOR(CallbackNode, Callback it, int order) { CONSTRUCT(CallbackNode); this.cbc = it; this.cbc_order = order; } ENDCLASS(CallbackNode) ATTRIB(CallbackChain, cbc_next, CallbackNode); ATTRIB(CallbackChain, cbc_order, int, 0); CONSTRUCTOR(CallbackChain, string _name) { CONSTRUCT(CallbackChain); this.netname = _name; } bool CallbackChain_Add(CallbackChain this, Callback cb, int order) { if (order & CBC_ORDER_FIRST) { if (order & CBC_ORDER_LAST) if (this.cbc_order & CBC_ORDER_ANY) return false; if (this.cbc_order & CBC_ORDER_FIRST) return false; } else if (order & CBC_ORDER_LAST) { if (this.cbc_order & CBC_ORDER_LAST) return false; } entity node = NEW(CallbackNode, cb, order); if (order & CBC_ORDER_FIRST) { node.cbc_next = this.cbc_next; this.cbc_next = node; } else if (order & CBC_ORDER_LAST) { CallbackNode prev = NULL, it = this.cbc_next; while (it) { prev = it, it = it.cbc_next; } if (prev) prev.cbc_next = node; else this.cbc_next = node; } else { // by default we execute last, but before a possible CBC_ORDER_LAST callback CallbackNode prev = NULL, it = this.cbc_next; while (it && !(it.cbc_order & CBC_ORDER_LAST)) { prev = it, it = it.cbc_next; } node.cbc_next = it; if (prev) prev.cbc_next = node; else this.cbc_next = node; } this.cbc_order |= (order | CBC_ORDER_ANY); return true; } int CallbackChain_Remove(CallbackChain this, Callback cb) { int n = 0, order = 0; for (Callback prev = NULL, it = this.cbc_next; it; prev = it, it = it.cbc_next) { if (it.cbc == cb) { // remove it from the chain Callback next = it.cbc_next; if (prev) prev.cbc_next = next; else this.cbc_next = next; ++n; } // it is now something we want to keep order |= (it.cbc_order & CBC_ORDER_ANY); } this.cbc_order = order; return n; } bool CallbackChain_Call(CallbackChain this) { bool r = false; for (Callback it = this.cbc_next; it; it = it.cbc_next) { CallbackChain_ReturnValue = r; r |= it.cbc.cbc_func(); } return r; // callbacks return an error status, so 0 is default return value } ENDCLASS(CallbackChain) #define _MUTATOR_HANDLE_NOP(type, id) #define _MUTATOR_HANDLE_PARAMS(type, id) , type in_##id #define _MUTATOR_HANDLE_PREPARE(type, id) id = in_##id; #define _MUTATOR_HANDLE_PUSHTMP(type, id) TC(type, id); type tmp_##id = id; #define _MUTATOR_HANDLE_PUSHOUT(type, id) type out_##id = id; #define _MUTATOR_HANDLE_POPTMP(type, id) id = tmp_##id; #define _MUTATOR_HANDLE_POPOUT(type, id) id = out_##id; void RegisterHooks() {}; void RegisterCallbacks() {}; #define MUTATOR_HOOKABLE(id, params) _MUTATOR_HOOKABLE(id, params) #define _MUTATOR_HOOKABLE(id, params) \ CallbackChain HOOK_##id; \ bool __Mutator_Send_##id(int params(_MUTATOR_HANDLE_PARAMS, _MUTATOR_HANDLE_NOP)) { \ params(_MUTATOR_HANDLE_PUSHTMP, _MUTATOR_HANDLE_NOP) \ params(_MUTATOR_HANDLE_PREPARE, _MUTATOR_HANDLE_NOP) \ bool ret = CallbackChain_Call(HOOK_##id); \ params(_MUTATOR_HANDLE_NOP, _MUTATOR_HANDLE_PUSHOUT) \ params(_MUTATOR_HANDLE_POPTMP, _MUTATOR_HANDLE_NOP) \ params(_MUTATOR_HANDLE_NOP, _MUTATOR_HANDLE_POPOUT) \ return ret; \ } \ ACCUMULATE void RegisterHooks() { HOOK_##id = NEW(CallbackChain, #id); } #define MUTATOR_CALLHOOK(id, ...) _MUTATOR_CALLHOOK(id, __VA_ARGS__) #ifdef __STDC__ #define _MUTATOR_CALLHOOK(id, ...) APPLY(__Mutator_Send_##id, 0 P99_IF_EMPTY(__VA_ARGS__)()(, __VA_ARGS__)) #else #define _MUTATOR_CALLHOOK(id, ...) APPLY(__Mutator_Send_##id, 0, ##__VA_ARGS__) #endif enum { MUTATOR_REMOVING, MUTATOR_ADDING, MUTATOR_ROLLING_BACK }; USING(mutatorfunc_t, bool(int)); CLASS(Mutator, Object) ATTRIB(Mutator, m_id, int, 0); ATTRIB(Mutator, m_name, string); ATTRIB(Mutator, mutatorfunc, mutatorfunc_t); ATTRIB(Mutator, mutatorcheck, bool()); CONSTRUCTOR(Mutator, string _name, mutatorfunc_t func) { CONSTRUCT(Mutator); this.m_name = _name; this.mutatorfunc = func; } ENDCLASS(Mutator) REGISTRY(Mutators, BITS(7)) #define Mutators_from(i) _Mutators_from(i, NULL) bool Mutator_Add(Mutator mut); void Mutator_Remove(Mutator mut); bool mutator_log = false; .bool m_added; #ifdef GAMEQC /** server mutators activate corresponding client mutators for all clients */ REGISTER_NET_LINKED(Mutator) #ifdef SVQC bool Mutator_SendEntity(entity this, entity to, int sf) { int chan = MSG_ENTITY; WriteHeader(chan, Mutator); WriteString(chan, this.registered_id); return true; } #endif #ifdef CSQC void NET_Mutator_Remove(entity this) { string s = this.netname; WITH(bool, mutator_log, true, { FOREACH(Mutators, it.registered_id == s, Mutator_Remove(it)); }); } NET_HANDLE(Mutator, bool isNew) { make_pure(this); string s = this.netname = ReadString(); return = true; if (isNew) { make_pure(this); this.entremove = NET_Mutator_Remove; int added = 0; WITH(bool, mutator_log, true, { FOREACH(Mutators, it.registered_id == s, { Mutator_Add(it); ++added; }); }); if (added > 1) LOG_WARNF("Added more than one mutator for %s", s); } } #endif #endif bool Mutator_Add(Mutator mut) { if(mut.m_added) return true; // already added mut.m_added = true; mutatorfunc_t func = mut.mutatorfunc; if (!func(MUTATOR_ADDING)) { // good if (mutator_log) LOG_TRACEF("Mutator: added %s", mut.m_name); #ifdef SVQC Net_LinkEntity(mut, false, 0, Mutator_SendEntity); #endif return true; } backtrace("WARNING: when adding mutator: adding failed, rolling back\n"); if (func(MUTATOR_ROLLING_BACK)) { // baaaaad error("WARNING: when adding mutator: rolling back failed"); } return false; } void Mutator_Remove(Mutator mut) { if(!mut.m_added) { backtrace("WARNING: removing not-added mutator\n"); return; } mut.m_added = false; mutatorfunc_t func = mut.mutatorfunc; if (func(MUTATOR_REMOVING)) { // baaaaad error("Mutator_Remove: removing mutator failed"); } if (mutator_log) LOG_TRACEF("Mutator: removed %s", mut.m_name); #ifdef SVQC Net_UnlinkEntity(mut); #endif } #define REGISTER_MUTATOR(id, dependence) \ bool MUTATORFUNCTION_##id##_hooks(int mode) { return = false; } \ bool MUTATORFUNCTION_##id(int mode) { \ return = false; \ bool ret = MUTATORFUNCTION_##id##_hooks(mode); if (ret) return ret; \ } \ bool MUTATOR_##id##_check() { return dependence; } \ REGISTER(Mutators, MUTATOR, id, m_id, NEW(Mutator, #id, MUTATORFUNCTION_##id)) \ { this.mutatorcheck = MUTATOR_##id##_check; } \ ACCUMULATE bool MUTATORFUNCTION_##id(int mode) STATIC_INIT(Mutators) { RegisterHooks(); RegisterCallbacks(); RegisterMutators(); } STATIC_INIT_LATE(Mutators) { FOREACH(Mutators, it.mutatorcheck(), Mutator_Add(it)); } #define MUTATOR_ONADD if (mode == MUTATOR_ADDING) #define MUTATOR_ONREMOVE if (mode == MUTATOR_REMOVING) #define MUTATOR_ONROLLBACK_OR_REMOVE if (mode == MUTATOR_REMOVING || mode == MUTATOR_ROLLING_BACK) #define MUTATOR_STATIC() MACRO_BEGIN { \ MUTATOR_ONADD { \ /* game loads at time 1 */ \ if (time > 1) { \ error("This is a game type and it cannot be added at runtime."); \ } \ } \ MUTATOR_ONREMOVE { \ LOG_INFO("This is a game type and it cannot be removed at runtime."); \ return -1; \ } \ } MACRO_END #define MUTATOR_ADD(name) Mutator_Add(MUTATOR_##name) #define MUTATOR_REMOVE(name) Mutator_Remove(MUTATOR_##name) #define MUTATOR_RETURNVALUE CallbackChain_ReturnValue #define _MUTATOR_CALLBACK(name, func) \ Callback CALLBACK_##name; \ bool func(); \ ACCUMULATE void RegisterCallbacks() { CALLBACK_##name = NEW(Callback, func); } #define MUTATOR_HOOKFUNCTION(...) \ EVAL_MUTATOR_HOOKFUNCTION(OVERLOAD(MUTATOR_HOOKFUNCTION, __VA_ARGS__)) #define EVAL_MUTATOR_HOOKFUNCTION(...) __VA_ARGS__ #define MUTATOR_HOOKFUNCTION_2(mut, cb) \ MUTATOR_HOOKFUNCTION_3(mut, cb, CBC_ORDER_ANY) #define MUTATOR_HOOKFUNCTION_3(mut, cb, order) \ _MUTATOR_CALLBACK(mut##_##cb, mut##_##cb) \ ACCUMULATE bool MUTATORFUNCTION_##mut##_hooks(int mode) { MUTATOR_HOOK(cb, mut##_##cb, order); } \ bool mut##_##cb() { return = false; } \ ACCUMULATE bool mut##_##cb() #define MUTATOR_HOOK(cb, func, order) MACRO_BEGIN { \ MUTATOR_ONADD { \ if (!CallbackChain_Add(HOOK_##cb, CALLBACK_##func, order)) { \ LOG_INFO("HOOK FAILED: ", #cb, ":", #func); \ return true; \ } \ } \ MUTATOR_ONROLLBACK_OR_REMOVE { \ CallbackChain_Remove(HOOK_##cb, CALLBACK_##func); \ } \ } MACRO_END #include "events.qh"