]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/base.qh
Merge branch 'master' into cloudwalk9/mgburstfix
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mutators / base.qh
1 #pragma once
2
3 #ifdef CSQC
4 #include <client/main.qh>
5 #endif
6
7 const int CBC_ORDER_FIRST = 1;
8 const int CBC_ORDER_LAST = 2;
9 const int CBC_ORDER_EXCLUSIVE = 3;
10 const int CBC_ORDER_ANY = 4;
11
12 bool CallbackChain_ReturnValue; // read-only field of the current return value
13
14 /**
15  * Callbacks may be added to zero or more callback chains.
16  */
17 CLASS(Callback, Object)
18     /**
19      * a callback function is like this:
20      * bool mycallback()
21      * {
22      *     do something
23      *     return false;
24      * }
25      */
26     ATTRIB(Callback, cbc_func, bool());
27     CONSTRUCTOR(Callback, bool() func) {
28         CONSTRUCT(Callback);
29         this.cbc_func = func;
30     }
31 ENDCLASS(Callback)
32
33 /**
34  * Callback chains contain zero or more callbacks.
35  */
36 CLASS(CallbackChain, Object)
37     CLASS(CallbackNode, Object)
38         ATTRIB(CallbackNode, cbc, Callback);
39         ATTRIB(CallbackNode, cbc_next, CallbackNode);
40         ATTRIB(CallbackNode, cbc_order, int, 0);
41         CONSTRUCTOR(CallbackNode, Callback it, int order) {
42             CONSTRUCT(CallbackNode);
43             this.cbc = it;
44             this.cbc_order = order;
45         }
46     ENDCLASS(CallbackNode)
47
48     ATTRIB(CallbackChain, cbc_next, CallbackNode);
49     ATTRIB(CallbackChain, cbc_order, int, 0);
50     CONSTRUCTOR(CallbackChain, string _name) {
51         CONSTRUCT(CallbackChain);
52         this.netname = _name;
53     }
54
55     bool CallbackChain_Add(CallbackChain this, Callback cb, int order)
56     {
57         if (order & CBC_ORDER_FIRST) {
58             if (order & CBC_ORDER_LAST)
59                 if (this.cbc_order & CBC_ORDER_ANY)
60                     return false;
61             if (this.cbc_order & CBC_ORDER_FIRST)
62                 return false;
63         } else if (order & CBC_ORDER_LAST) {
64             if (this.cbc_order & CBC_ORDER_LAST)
65                 return false;
66         }
67         entity node = NEW(CallbackNode, cb, order);
68         if (order & CBC_ORDER_FIRST) {
69             node.cbc_next = this.cbc_next;
70             this.cbc_next = node;
71         } else if (order & CBC_ORDER_LAST) {
72             CallbackNode prev = NULL, it = this.cbc_next;
73             while (it) { prev = it, it = it.cbc_next; }
74             if (prev) prev.cbc_next = node;
75             else this.cbc_next = node;
76         } else {
77             // by default we execute last, but before a possible CBC_ORDER_LAST callback
78             CallbackNode prev = NULL, it = this.cbc_next;
79             while (it && !(it.cbc_order & CBC_ORDER_LAST)) { prev = it, it = it.cbc_next; }
80             node.cbc_next = it;
81             if (prev) prev.cbc_next = node;
82             else this.cbc_next = node;
83         }
84         this.cbc_order |= (order | CBC_ORDER_ANY);
85         return true;
86     }
87     int CallbackChain_Remove(CallbackChain this, Callback cb)
88     {
89         int n = 0, order = 0;
90         for (Callback prev = NULL, it = this.cbc_next; it; prev = it, it = it.cbc_next) {
91             if (it.cbc == cb) {
92                 // remove it from the chain
93                 Callback next = it.cbc_next;
94                 if (prev) prev.cbc_next = next;
95                 else this.cbc_next = next;
96                 ++n;
97             }
98             // it is now something we want to keep
99             order |= (it.cbc_order & CBC_ORDER_ANY);
100         }
101         this.cbc_order = order;
102         return n;
103     }
104     bool CallbackChain_Call(CallbackChain this)
105     {
106         bool r = false;
107         for (Callback it = this.cbc_next; it; it = it.cbc_next) {
108             CallbackChain_ReturnValue = r;
109             r |= it.cbc.cbc_func();
110         }
111         return r; // callbacks return an error status, so 0 is default return value
112     }
113 ENDCLASS(CallbackChain)
114
115 #define _MUTATOR_HANDLE_NOP(type, id)
116 #define _MUTATOR_HANDLE_PARAMS(type, id) , type in_##id
117 #define _MUTATOR_HANDLE_PREPARE(type, id) id = in_##id;
118 #define _MUTATOR_HANDLE_PUSHTMP(type, id) TC(type, id); type tmp_##id = id;
119 #define _MUTATOR_HANDLE_PUSHOUT(type, id) type out_##id = id;
120 #define _MUTATOR_HANDLE_POPTMP(type, id) id = tmp_##id;
121 #define _MUTATOR_HANDLE_POPOUT(type, id) id = out_##id;
122
123 void RegisterHooks() {};
124 void RegisterCallbacks() {};
125
126 #define MUTATOR_HOOKABLE(id, params) _MUTATOR_HOOKABLE(id, params)
127 #define _MUTATOR_HOOKABLE(id, params) \
128     CallbackChain HOOK_##id;  \
129     bool __Mutator_Send_##id(int params(_MUTATOR_HANDLE_PARAMS, _MUTATOR_HANDLE_NOP)) { \
130         params(_MUTATOR_HANDLE_PUSHTMP, _MUTATOR_HANDLE_NOP) \
131         params(_MUTATOR_HANDLE_PREPARE, _MUTATOR_HANDLE_NOP) \
132         bool ret = CallbackChain_Call(HOOK_##id); \
133         params(_MUTATOR_HANDLE_NOP,     _MUTATOR_HANDLE_PUSHOUT) \
134         params(_MUTATOR_HANDLE_POPTMP,  _MUTATOR_HANDLE_NOP) \
135         params(_MUTATOR_HANDLE_NOP,     _MUTATOR_HANDLE_POPOUT) \
136         return ret; \
137     } \
138     ACCUMULATE void RegisterHooks() { HOOK_##id = NEW(CallbackChain, #id); }
139
140 #define MUTATOR_CALLHOOK(id, ...) _MUTATOR_CALLHOOK(id, __VA_ARGS__)
141 #ifdef __STDC__
142     #define _MUTATOR_CALLHOOK(id, ...) APPLY(__Mutator_Send_##id, 0 P99_IF_EMPTY(__VA_ARGS__)()(, __VA_ARGS__))
143 #else
144     #define _MUTATOR_CALLHOOK(id, ...) APPLY(__Mutator_Send_##id, 0, ##__VA_ARGS__)
145 #endif
146
147 enum {
148     MUTATOR_REMOVING,
149     MUTATOR_ADDING,
150     MUTATOR_ROLLING_BACK
151 };
152
153 USING(mutatorfunc_t, bool(int));
154
155 CLASS(Mutator, Object)
156     ATTRIB(Mutator, m_id, int, 0);
157     ATTRIB(Mutator, m_name, string);
158     ATTRIB(Mutator, mutatorfunc, mutatorfunc_t);
159     ATTRIB(Mutator, mutatorcheck, bool());
160     CONSTRUCTOR(Mutator, string _name, mutatorfunc_t func) {
161         CONSTRUCT(Mutator);
162         this.m_name = _name;
163         this.mutatorfunc = func;
164     }
165 ENDCLASS(Mutator)
166
167 REGISTRY(Mutators, BITS(7))
168 #define Mutators_from(i) _Mutators_from(i, NULL)
169 bool Mutator_Add(Mutator mut);
170 void Mutator_Remove(Mutator mut);
171 bool mutator_log = false;
172 .bool m_added;
173
174 #define _MUTATOR_IS_ENABLED(this) this.mutatorcheck()
175 #define MUTATOR_IS_ENABLED(this) _MUTATOR_IS_ENABLED(MUTATOR_##this)
176
177 #ifdef GAMEQC
178 /** server mutators activate corresponding client mutators for all clients */
179 REGISTER_NET_LINKED(Mutator)
180
181 #ifdef SVQC
182 bool Mutator_SendEntity(entity this, entity to, int sf)
183 {
184     int chan = MSG_ENTITY;
185     WriteHeader(chan, Mutator);
186     WriteString(chan, this.registered_id);
187     return true;
188 }
189 #endif
190
191 #ifdef CSQC
192 void NET_Mutator_Remove(entity this)
193 {
194     string s = this.netname;
195     WITH(bool, mutator_log, true, {
196         FOREACH(Mutators, it.registered_id == s, Mutator_Remove(it));
197     });
198 }
199 NET_HANDLE(Mutator, bool isNew)
200 {
201     make_pure(this);
202     string s = this.netname = ReadString();
203     return = true;
204     if (isNew)
205     {
206         make_pure(this);
207         this.entremove = NET_Mutator_Remove;
208         int added = 0;
209         WITH(bool, mutator_log, true, {
210             FOREACH(Mutators, it.registered_id == s, { Mutator_Add(it); ++added; });
211         });
212         if (added > 1) LOG_WARNF("Added more than one mutator for %s", s);
213     }
214 }
215 #endif
216
217 #endif
218
219 bool Mutator_Add(Mutator mut)
220 {
221     if(mut.m_added)
222         return true; // already added
223
224     mut.m_added = true;
225     mutatorfunc_t func = mut.mutatorfunc;
226     if (!func(MUTATOR_ADDING)) {
227         // good
228         if (mutator_log) LOG_TRACEF("Mutator: added %s", mut.m_name);
229 #ifdef SVQC
230         Net_LinkEntity(mut, false, 0, Mutator_SendEntity);
231 #endif
232         return true;
233     }
234     backtrace("WARNING: when adding mutator: adding failed, rolling back\n");
235     if (func(MUTATOR_ROLLING_BACK)) {
236         // baaaaad
237         error("WARNING: when adding mutator: rolling back failed");
238     }
239     return false;
240 }
241
242 void Mutator_Remove(Mutator mut)
243 {
244     if(!mut.m_added)
245     {
246         backtrace("WARNING: removing not-added mutator\n");
247         return;
248     }
249
250     mut.m_added = false;
251     mutatorfunc_t func = mut.mutatorfunc;
252     if (func(MUTATOR_REMOVING)) {
253         // baaaaad
254         error("Mutator_Remove: removing mutator failed");
255     }
256     if (mutator_log) LOG_TRACEF("Mutator: removed %s", mut.m_name);
257 #ifdef SVQC
258     Net_UnlinkEntity(mut);
259 #endif
260 }
261
262 #define REGISTER_MUTATOR(id, dependence) \
263     bool MUTATORFUNC_##id##_hooks(int mode) { return = false; } \
264     bool MUTATORFUNC_##id(int mode) { \
265         return = false; \
266         bool ret = MUTATORFUNC_##id##_hooks(mode); if (ret) return ret; \
267     } \
268     bool MUTATOR_##id##_check() { return dependence; } \
269     REGISTER(Mutators, MUTATOR, id, m_id, NEW(Mutator, #id, MUTATORFUNC_##id)) \
270     { this.mutatorcheck = MUTATOR_##id##_check; } \
271     ACCUMULATE bool MUTATORFUNC_##id(int mode)
272
273 STATIC_INIT(Mutators) {
274     RegisterHooks();
275     RegisterCallbacks();
276     RegisterMutators();
277 }
278
279 STATIC_INIT_LATE(Mutators) {
280     FOREACH(Mutators, _MUTATOR_IS_ENABLED(it), Mutator_Add(it));
281 }
282
283 #define MUTATOR_ONADD                   if (mode == MUTATOR_ADDING)
284 #define MUTATOR_ONREMOVE                if (mode == MUTATOR_REMOVING)
285 #define MUTATOR_ONROLLBACK_OR_REMOVE    if (mode == MUTATOR_REMOVING || mode == MUTATOR_ROLLING_BACK)
286
287 #define MUTATOR_STATIC() MACRO_BEGIN \
288     MUTATOR_ONADD { \
289         /* game loads at time 1 */ \
290         if (time > 1) { \
291             error("This is a game type and it cannot be added at runtime."); \
292         } \
293     } \
294         MUTATOR_ONREMOVE { \
295                 LOG_INFO("This is a game type and it cannot be removed at runtime."); \
296                 return -1; \
297         } \
298 MACRO_END
299
300 #define MUTATOR_ADD(name)               Mutator_Add(MUTATOR_##name)
301 #define MUTATOR_REMOVE(name)            Mutator_Remove(MUTATOR_##name)
302 #define MUTATOR_RETURNVALUE             CallbackChain_ReturnValue
303
304 #define _MUTATOR_CALLBACK(name, func) \
305     Callback CB_##name; \
306     bool func(); \
307     ACCUMULATE void RegisterCallbacks() { CB_##name = NEW(Callback, func); }
308
309 #define MUTATOR_HOOKFUNCTION(...) \
310     EVAL_MUTATOR_HOOKFUNCTION(OVERLOAD(MUTATOR_HOOKFUNCTION, __VA_ARGS__))
311 #define EVAL_MUTATOR_HOOKFUNCTION(...) __VA_ARGS__
312
313 #define MUTATOR_HOOKFUNCTION_2(mut, cb) \
314     MUTATOR_HOOKFUNCTION_3(mut, cb, CBC_ORDER_ANY)
315
316 #define MUTATOR_HOOKFUNCTION_3(mut, cb, order) \
317     _MUTATOR_CALLBACK(mut##_##cb, mut##_##cb) \
318     ACCUMULATE bool MUTATORFUNC_##mut##_hooks(int mode) { MUTATOR_HOOK(cb, mut##_##cb, order); } \
319     bool mut##_##cb() { return = false; } \
320     ACCUMULATE bool mut##_##cb()
321
322 void _mutPrintFail(string cb, string func)
323 {
324         // this is inside a function to avoid expanding it on compilation everytime
325         LOG_INFO("HOOK FAILED: ", cb, ":", func);
326 }
327
328 #define MUTATOR_HOOK(cb, func, order) MACRO_BEGIN \
329     MUTATOR_ONADD { \
330         if (!CallbackChain_Add(HOOK_##cb, CB_##func, order)) { \
331             _mutPrintFail(#cb, #func); \
332             return true; \
333         } \
334     } \
335     MUTATOR_ONROLLBACK_OR_REMOVE { \
336         CallbackChain_Remove(HOOK_##cb, CB_##func); \
337     } \
338 MACRO_END
339
340 #include "events.qh"