1 /** @file p_xgline.c Extended generalized line types.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2005-2013 Daniel Swanson <danij@dengine.net>
5 * @authors Copyright © 2007 Jamie Jones <jamie_jones_au@yahoo.com.au>
6 *
7 * @par License
8 * GPL: http://www.gnu.org/licenses/gpl.html
9 *
10 * <small>This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by the
12 * Free Software Foundation; either version 2 of the License, or (at your
13 * option) any later version. This program is distributed in the hope that it
14 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16 * Public License for more details. You should have received a copy of the GNU
17 * General Public License along with this program; if not, write to the Free
18 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19 * 02110-1301 USA</small>
20 */
21
22 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
23
24 #include <time.h>
25 #include <math.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <string.h>
29
30 #include "common.h"
31
32 #include "d_net.h"
33 #include "dmu_lib.h"
34 #include "gamesession.h"
35 #include "p_mapsetup.h"
36 #include "p_xgline.h"
37 #include "p_xgsec.h"
38 #include "p_actor.h"
39 #include "player.h"
40 #include "p_map.h"
41 #include "p_mapspec.h"
42 #include "p_terraintype.h"
43 #include "p_tick.h"
44 #include "p_sound.h"
45 #include "p_switch.h"
46
47 using namespace de;
48
49 #define XLTIMER_STOPPED 1 // Timer stopped.
50
51 #define EVTYPESTR(evtype) (evtype == XLE_CHAIN? "CHAIN" \
52 : evtype == XLE_CROSS? "CROSS" \
53 : evtype == XLE_USE? "USE" \
54 : evtype == XLE_SHOOT? "SHOOT" \
55 : evtype == XLE_HIT? "HIT" \
56 : evtype == XLE_TICKER? "TICKER" \
57 : evtype == XLE_AUTO? "AUTO" \
58 : evtype == XLE_FORCED? "FORCED" \
59 : evtype == XLE_FUNC? "FUNCTION" : "???")
60
61 #define LREFTYPESTR(reftype) (reftype == LREF_NONE? "NONE" \
62 : reftype == LREF_SELF? "SELF" \
63 : reftype == LREF_TAGGED? "TAGGED LINES" \
64 : reftype == LREF_LINE_TAGGED? "LINE TAGGED LINES" \
65 : reftype == LREF_ACT_TAGGED? "ACT TAGGED LINES" \
66 : reftype == LREF_INDEX? "INDEXED LINE" \
67 : reftype == LREF_ALL? "ALL LINES" : "???")
68
69 #define LPREFTYPESTR(reftype) (reftype == LPREF_NONE? "NONE" \
70 : reftype == LPREF_MY_FLOOR? "MY FLOOR" \
71 : reftype == LPREF_TAGGED_FLOORS? "TAGGED FLOORS" \
72 : reftype == LPREF_LINE_TAGGED_FLOORS? "LINE TAGGED FLOORS" \
73 : reftype == LPREF_ACT_TAGGED_FLOORS? "ACT TAGGED FLOORS" \
74 : reftype == LPREF_INDEX_FLOOR? "INDEXED FLOOR" \
75 : reftype == LPREF_ALL_FLOORS? "ALL FLOORS" \
76 : reftype == LPREF_MY_CEILING? "MY CEILING" \
77 : reftype == LPREF_TAGGED_CEILINGS? "TAGGED CEILINGS" \
78 : reftype == LPREF_LINE_TAGGED_CEILINGS? "LINE TAGGED CEILINGS" \
79 : reftype == LPREF_ACT_TAGGED_CEILINGS? "ACT TAGGED CEILINGS" \
80 : reftype == LPREF_INDEX_CEILING? "INDEXED CEILING" \
81 : reftype == LPREF_ALL_CEILINGS? "ALL CEILINGS" \
82 : reftype == LPREF_SPECIAL? "SPECIAL" \
83 : reftype == LPREF_BACK_FLOOR? "BACK FLOOR" \
84 : reftype == LPREF_BACK_CEILING? "BACK CEILING" \
85 : reftype == LPREF_THING_EXIST_FLOORS? "SECTORS WITH THING - FLOOR" \
86 : reftype == LPREF_THING_EXIST_CEILINGS? "SECTORS WITH THING - CEILING" \
87 : reftype == LPREF_THING_NOEXIST_FLOORS? "SECTORS WITHOUT THING - FLOOR" \
88 : reftype == LPREF_THING_NOEXIST_CEILINGS? "SECTORS WITHOUT THING - CEILING" : "???")
89
90 #define LSREFTYPESTR(reftype) (reftype == LSREF_NONE? "NONE" \
91 : reftype == LSREF_MY? "MY SECTOR" \
92 : reftype == LSREF_TAGGED? "TAGGED SECTORS" \
93 : reftype == LSREF_LINE_TAGGED? "LINE TAGGED SECTORS" \
94 : reftype == LSREF_ACT_TAGGED? "ACT TAGGED SECTORS" \
95 : reftype == LSREF_INDEX? "INDEXED SECTOR" \
96 : reftype == LSREF_ALL? "ALL SECTORS" \
97 : reftype == LSREF_BACK? "BACK SECTOR" \
98 : reftype == LSREF_THING_EXIST? "SECTORS WITH THING" \
99 : reftype == LSREF_THING_NOEXIST? "SECTORS WITHOUT THING" : "???")
100
101 #define TO_DMU_TOP_COLOR(x) ((x) == 0? DMU_TOP_COLOR_RED \
102 : (x) == 1? DMU_TOP_COLOR_GREEN \
103 : DMU_TOP_COLOR_BLUE)
104
105 #define TO_DMU_MIDDLE_COLOR(x) ((x) == 0? DMU_MIDDLE_COLOR_RED \
106 : (x) == 1? DMU_MIDDLE_COLOR_GREEN \
107 : (x) == 2? DMU_MIDDLE_COLOR_BLUE \
108 : DMU_MIDDLE_ALPHA)
109
110 #define TO_DMU_BOTTOM_COLOR(x) ((x) == 0? DMU_BOTTOM_COLOR_RED \
111 : (x) == 1? DMU_BOTTOM_COLOR_GREEN \
112 : DMU_BOTTOM_COLOR_BLUE)
113
114 void XL_ChangeMaterial(Line *line, int sidenum, int section, world_Material *mat,
115 blendmode_t blend = BM_NORMAL, Vector4f const &tintColor = Vector4f(), int flags = 0);
116
117 int XL_DoChainSequence(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
118 int XL_DoDamage(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
119 int XL_DoPower(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
120 int XL_DoKey(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
121 int XL_DoExplode(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
122 int XL_DoCommand(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
123
124 int XLTrav_ChangeLineType(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
125 int XLTrav_Activate(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
126 int XLTrav_Music(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
127 int XLTrav_LineCount(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
128 int XLTrav_LeaveMap(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
129 int XLTrav_DisableLine(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
130 int XLTrav_EnableLine(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
131 int XLTrav_ChangeWallMaterial(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
132 int XLTrav_LineTeleport(Line *line, dd_bool ceiling, void *context, void *context2, mobj_t *activator);
133
134 int xgDev = 0; // Print dev messages.
135
136 static linetype_t typebuffer;
137 static char msgbuf[80];
138 ThinkerT<mobj_s> dummyThing;
139
XG_DummyThing()140 struct mobj_s *XG_DummyThing()
141 {
142 return dummyThing;
143 }
144
145 /* ADD NEW XG CLASSES TO THE END - ORIGINAL INDICES MUST STAY THE SAME!!! */
146 xgclass_t xgClasses[NUMXGCLASSES] =
147 {
148 { NULL, NULL, TRAV_NONE, 0, 1, 0, "None",
149 // Dummy class (has no functions but enables use of secondary actions) (no params)
150 {{XGPF_INT, "", "", -1},
151 {XGPF_INT, "", "", -1},
152 {XGPF_INT, "", "", -1},
153 {XGPF_INT, "", "", -1},
154 {XGPF_INT, "", "", -1},
155 {XGPF_INT, "", "", -1},
156 {XGPF_INT, "", "", -1},
157 {XGPF_INT, "", "", -1},
158 {XGPF_INT, "", "", -1},
159 {XGPF_INT, "", "", -1},
160 {XGPF_INT, "", "", -1},
161 {XGPF_INT, "", "", -1},
162 {XGPF_INT, "", "", -1},
163 {XGPF_INT, "", "", -1},
164 {XGPF_INT, "", "", -1},
165 {XGPF_INT, "", "", -1},
166 {XGPF_INT, "", "", -1},
167 {XGPF_INT, "", "", -1},
168 {XGPF_INT, "", "", -1},
169 {XGPF_INT, "", "", -1},
170 }
171 },
172 { de::function_cast<int (*)()>(XL_DoChainSequence), NULL, TRAV_NONE, 0, 1, 0, "Chain Sequence",
173 // Execute a chain of other XG line types (a zero ends the list)
174 {{XGPF_INT, "Chain Flags", "chsf_", 0}, // ip0: (chsf_) chain sequence flags
175 {XGPF_INT, "Line Type 0", "", -1}, // ip1: Type to execute
176 {XGPF_INT, "Line Type 1", "", -1}, // ip2: "" "" ""
177 {XGPF_INT, "Line Type 2", "", -1}, // ip3: "" "" ""
178 {XGPF_INT, "Line Type 3", "", -1}, // ip4: "" "" ""
179 {XGPF_INT, "Line Type 4", "", -1}, // ip5: "" "" ""
180 {XGPF_INT, "Line Type 5", "", -1}, // ip6: "" "" ""
181 {XGPF_INT, "Line Type 6", "", -1}, // ip7: "" "" ""
182 {XGPF_INT, "Line Type 7", "", -1}, // ip8: "" "" ""
183 {XGPF_INT, "Line Type 8", "", -1}, // ip9: "" "" ""
184 {XGPF_INT, "Line Type 9", "", -1}, // ip10: "" "" ""
185 {XGPF_INT, "Line Type 10", "", -1}, // ip11: "" "" ""
186 {XGPF_INT, "Line Type 11", "", -1}, // ip12: "" "" ""
187 {XGPF_INT, "Line Type 12", "", -1}, // ip13: "" "" ""
188 {XGPF_INT, "Line Type 13", "", -1}, // ip14: "" "" ""
189 {XGPF_INT, "Line Type 14", "", -1}, // ip15: "" "" ""
190 {XGPF_INT, "Line Type 15", "", -1}, // ip16: "" "" ""
191 {XGPF_INT, "Line Type 16", "", -1}, // ip17: "" "" ""
192 {XGPF_INT, "Line Type 17", "", -1}, // ip18: "" "" ""
193 {XGPF_INT, "Line Type 18", "", -1} // ip19: "" "" ""
194 }
195 },
196 { de::function_cast<int (*)()>(XSTrav_MovePlane), XS_InitMovePlane, TRAV_PLANES, 0, 1, 0, "Move Plane",
197 // Move one or more planes. Optionaly change textures/types on start/end
198 {{XGPF_INT, "Target Ref", "lpref_", 0}, // ip0: (plane ref) plane(s) to move.
199 {XGPF_INT, "Target Num", "", -1}, // ip1:
200 {XGPF_INT, "Destination Ref", "spref_", 2}, // ip2: destination height type (zero, relative to current, surrounding highest/lowest floor/ceiling)
201 {XGPF_INT, "Move Flags", "pmf_", 3}, // ip3: flags (PMF_*)
202 {XGPF_INT, "Start Sound", "", 4 | MAP_SND}, // ip4: start sound
203 {XGPF_INT, "End Sound", "", 5 | MAP_SND}, // ip5: end sound
204 {XGPF_INT, "Move Sound", "", 6 | MAP_SND}, // ip6: move sound
205 {XGPF_INT, "Start Material Ref", "spref_", 7}, // ip7: start texture origin (uses same ids as i2) (spec: use ip8 as tex num)
206 {XGPF_INT, "Start Material Num", "", 8 | MAP_MATERIAL}, // ip8: data component or number/name of flat
207 {XGPF_INT, "End Material Ref", "spref_", 9}, // ip9: end texture origin (uses same ids as i2) (spec: use ip10 as tex num)
208 {XGPF_INT, "End Material Num", "", 10 | MAP_MATERIAL}, // ip10: data component or number/name of flat
209 {XGPF_INT, "Start Type Ref", "lpref_", 11}, // ip11: (plane ref) start sector type (spec: use i12 as type ID)
210 {XGPF_INT, "Start Type Num", "", -1}, // ip12: data component or type ID
211 {XGPF_INT, "End Type Ref", "lpref_", 13}, // ip13: (plane ref) end sector type (spec: use i14 as type ID)
212 {XGPF_INT, "End Type Num", "", -1}, // ip14: data component or type ID
213 {XGPF_INT, "", "", -1},
214 {XGPF_INT, "", "", -1},
215 {XGPF_INT, "", "", -1},
216 {XGPF_INT, "", "", -1},
217 {XGPF_INT, "", "", -1}
218 }
219 },
220 { de::function_cast<int (*)()>(XSTrav_BuildStairs), XS_InitStairBuilder, TRAV_PLANES, 0, 1, 0, "Build Stairs",
221 // Moves one or more planes, incrementing their height with each move
222 {{XGPF_INT, "Target Ref", "lpref_", 0}, // ip0: (plane ref) plane to start from
223 {XGPF_INT, "Target Num", "", -1}, // ip1:
224 {XGPF_INT, "Spread Material", "", -1}, // ip2: (true/false) stop when texture changes
225 {XGPF_INT, "Spread Build", "", -1}, // ip3: (true/false) spread build?
226 {XGPF_INT, "Start Sound", "", 4 | MAP_SND}, // ip4: start build sound (doesn't wait)
227 {XGPF_INT, "Step Start Sound", "", 5 | MAP_SND}, // ip5: step start sound
228 {XGPF_INT, "Step End Sound", "", 6 | MAP_SND}, // ip6: step end sound
229 {XGPF_INT, "Step Move Sound", "", 7 | MAP_SND}, // ip7: step move sound
230 {XGPF_INT, "", "", -1},
231 {XGPF_INT, "", "", -1},
232 {XGPF_INT, "", "", -1},
233 {XGPF_INT, "", "", -1},
234 {XGPF_INT, "", "", -1},
235 {XGPF_INT, "", "", -1},
236 {XGPF_INT, "", "", -1},
237 {XGPF_INT, "", "", -1},
238 {XGPF_INT, "", "", -1},
239 {XGPF_INT, "", "", -1},
240 {XGPF_INT, "", "", -1},
241 {XGPF_INT, "", "", -1}
242 }
243 },
244 { de::function_cast<int (*)()>(XL_DoDamage), NULL, TRAV_NONE, 0, 1, 0, "Damage",
245 // Deals health damage to the activator
246 {{XGPF_INT, "Min Delta", "", -1}, // ip0: min damage delta
247 {XGPF_INT, "Max Delta", "", -1}, // ip1: max damage delta
248 {XGPF_INT, "Min Limit", "", -1}, // ip2: min limit (wont damage if health bellow)
249 {XGPF_INT, "Max Limit", "", -1}, // ip3: max limit (wont damage if health above)
250 {XGPF_INT, "", "", -1},
251 {XGPF_INT, "", "", -1},
252 {XGPF_INT, "", "", -1},
253 {XGPF_INT, "", "", -1},
254 {XGPF_INT, "", "", -1},
255 {XGPF_INT, "", "", -1},
256 {XGPF_INT, "", "", -1},
257 {XGPF_INT, "", "", -1},
258 {XGPF_INT, "", "", -1},
259 {XGPF_INT, "", "", -1},
260 {XGPF_INT, "", "", -1},
261 {XGPF_INT, "", "", -1},
262 {XGPF_INT, "", "", -1},
263 {XGPF_INT, "", "", -1},
264 {XGPF_INT, "", "", -1},
265 {XGPF_INT, "", "", -1},
266 }
267 },
268 { de::function_cast<int (*)()>(XL_DoPower), NULL, TRAV_NONE, 0, 1, 0, "Power",
269 // Deals armor damage to the activator (must be a player)
270 {{XGPF_INT, "Min Delta", "", -1}, // ip0: min power delta
271 {XGPF_INT, "Max Delta", "", -1}, // ip1: max power delta
272 {XGPF_INT, "Min Limit", "", -1}, // ip2: min limit
273 {XGPF_INT, "Max Limit", "", -1}, // ip3: max limit
274 {XGPF_INT, "", "", -1},
275 {XGPF_INT, "", "", -1},
276 {XGPF_INT, "", "", -1},
277 {XGPF_INT, "", "", -1},
278 {XGPF_INT, "", "", -1},
279 {XGPF_INT, "", "", -1},
280 {XGPF_INT, "", "", -1},
281 {XGPF_INT, "", "", -1},
282 {XGPF_INT, "", "", -1},
283 {XGPF_INT, "", "", -1},
284 {XGPF_INT, "", "", -1},
285 {XGPF_INT, "", "", -1},
286 {XGPF_INT, "", "", -1},
287 {XGPF_INT, "", "", -1},
288 {XGPF_INT, "", "", -1},
289 {XGPF_INT, "", "", -1}
290 }
291 },
292 { de::function_cast<int (*)()>(XLTrav_ChangeLineType), NULL, TRAV_LINES, 0, 1, 0, "Line Type",
293 // Changes a line's type (must be an XG type)
294 {{XGPF_INT, "Target Ref", "lref_", 0}, // ip0: (line ref) line(s) to change
295 {XGPF_INT, "Target Num", "", -1}, // ip1:
296 {XGPF_INT, "Line Type", "", -1}, // ip2: new type (must be an XG line type)
297 {XGPF_INT, "", "", -1},
298 {XGPF_INT, "", "", -1},
299 {XGPF_INT, "", "", -1},
300 {XGPF_INT, "", "", -1},
301 {XGPF_INT, "", "", -1},
302 {XGPF_INT, "", "", -1},
303 {XGPF_INT, "", "", -1},
304 {XGPF_INT, "", "", -1},
305 {XGPF_INT, "", "", -1},
306 {XGPF_INT, "", "", -1},
307 {XGPF_INT, "", "", -1},
308 {XGPF_INT, "", "", -1},
309 {XGPF_INT, "", "", -1},
310 {XGPF_INT, "", "", -1},
311 {XGPF_INT, "", "", -1},
312 {XGPF_INT, "", "", -1},
313 {XGPF_INT, "", "", -1}
314 }
315 },
316 { de::function_cast<int (*)()>(XSTrav_SectorType), NULL, TRAV_SECTORS, 0, 1, 0, "Sector Type",
317 // Changes a sector's type (must be an XG type)
318 {{XGPF_INT, "Target Ref", "lsref_", 0}, // ip0: (sector ref) sector(s) to change
319 {XGPF_INT, "Target Num", "", -1}, // ip1:
320 {XGPF_INT, "Sector Type", "", -1}, // ip2: new type (zero or an XG sector type)
321 {XGPF_INT, "", "", -1},
322 {XGPF_INT, "", "", -1},
323 {XGPF_INT, "", "", -1},
324 {XGPF_INT, "", "", -1},
325 {XGPF_INT, "", "", -1},
326 {XGPF_INT, "", "", -1},
327 {XGPF_INT, "", "", -1},
328 {XGPF_INT, "", "", -1},
329 {XGPF_INT, "", "", -1},
330 {XGPF_INT, "", "", -1},
331 {XGPF_INT, "", "", -1},
332 {XGPF_INT, "", "", -1},
333 {XGPF_INT, "", "", -1},
334 {XGPF_INT, "", "", -1},
335 {XGPF_INT, "", "", -1},
336 {XGPF_INT, "", "", -1},
337 {XGPF_INT, "", "", -1}
338 }
339 },
340 { de::function_cast<int (*)()>(XSTrav_SectorLight), NULL, TRAV_SECTORS, 0, 1, 0, "Sector Light",
341 // Change the light level and/or color of the target sector(s).
342 {{XGPF_INT, "Target Ref", "lsref_", 0}, // ip0: (sector ref) sector(s) to change
343 {XGPF_INT, "Target Num", "", -1}, // ip1:
344 {XGPF_INT, "Change Light", "", -1}, // ip2: if non-zero light level will be changed
345 {XGPF_INT, "Change Color", "", -1}, // ip3: if non-zero colour will be changed
346 {XGPF_INT, "Light Ref", "lightref_", 4}, // ip4: (light ref) sector to get the initial light delta from.
347 // lightref_none makes ip5 an absolute value
348 {XGPF_INT, "Light Delta", "", -1}, // ip5: offset to the delta or absolute value
349 {XGPF_INT, "Color Ref", "lightref_", 6}, // ip6: (light ref) sector to get the initial colour deltas from.
350 // lightref_none makes ip7-9 absolute values
351 {XGPF_INT, "Red Delta", "", -1}, // ip7: offset to red delta
352 {XGPF_INT, "Green Delta", "", -1}, // ip8: offset to green delta
353 {XGPF_INT, "Blue Delta", "", -1}, // ip9: offset to blue delta
354 {XGPF_INT, "", "", -1},
355 {XGPF_INT, "", "", -1},
356 {XGPF_INT, "", "", -1},
357 {XGPF_INT, "", "", -1},
358 {XGPF_INT, "", "", -1},
359 {XGPF_INT, "", "", -1},
360 {XGPF_INT, "", "", -1},
361 {XGPF_INT, "", "", -1},
362 {XGPF_INT, "", "", -1},
363 {XGPF_INT, "", "", -1}
364 }
365 },
366 { de::function_cast<int (*)()>(XLTrav_Activate), NULL, TRAV_LINES, 0, 1, 0, "Activate",
367 // Sends a chain event to all the referenced lines
368 {{XGPF_INT, "Target Ref", "lref_", 0}, // ip0: (line ref) line(s) to activate
369 {XGPF_INT, "Target Num", "", -1}, // ip1:
370 {XGPF_INT, "", "", -1},
371 {XGPF_INT, "", "", -1},
372 {XGPF_INT, "", "", -1},
373 {XGPF_INT, "", "", -1},
374 {XGPF_INT, "", "", -1},
375 {XGPF_INT, "", "", -1},
376 {XGPF_INT, "", "", -1},
377 {XGPF_INT, "", "", -1},
378 {XGPF_INT, "", "", -1},
379 {XGPF_INT, "", "", -1},
380 {XGPF_INT, "", "", -1},
381 {XGPF_INT, "", "", -1},
382 {XGPF_INT, "", "", -1},
383 {XGPF_INT, "", "", -1},
384 {XGPF_INT, "", "", -1},
385 {XGPF_INT, "", "", -1},
386 {XGPF_INT, "", "", -1},
387 {XGPF_INT, "", "", -1}
388 }
389 },
390 { de::function_cast<int (*)()>(XL_DoKey), NULL, TRAV_NONE, 0, 1, 0, "Key",
391 // Gives/takes keys to/from the activator (must be a player)
392 // Params are bitfields! Bit 1 (0x1) corresponds key 1, bit 2 (0x2) key 2, etc.
393 {{XGPF_INT, "Give Keys", "", -1}, // ip0: keys to give
394 {XGPF_INT, "Take Keys", "", -1}, // ip1: keys to take away.
395 {XGPF_INT, "", "", -1},
396 {XGPF_INT, "", "", -1},
397 {XGPF_INT, "", "", -1},
398 {XGPF_INT, "", "", -1},
399 {XGPF_INT, "", "", -1},
400 {XGPF_INT, "", "", -1},
401 {XGPF_INT, "", "", -1},
402 {XGPF_INT, "", "", -1},
403 {XGPF_INT, "", "", -1},
404 {XGPF_INT, "", "", -1},
405 {XGPF_INT, "", "", -1},
406 {XGPF_INT, "", "", -1},
407 {XGPF_INT, "", "", -1},
408 {XGPF_INT, "", "", -1},
409 {XGPF_INT, "", "", -1},
410 {XGPF_INT, "", "", -1},
411 {XGPF_INT, "", "", -1},
412 {XGPF_INT, "", "", -1}
413 }
414 },
415 { de::function_cast<int (*)()>(XLTrav_Music), NULL, TRAV_LINES, 2, 3, 0, "Music",
416 // Changes the music track being played
417 {{XGPF_INT, "Song ID", "ldref_", 0 | MAP_MUS}, // ip0: song id/name or (line data ref from ip2)
418 {XGPF_INT, "Play Looped", "", -1}, // ip1: non-zero means play looped
419 {XGPF_INT, "Data Ref", "lref_", 2}, // ip2: (line ref) used with line data ref eg set music track to line-tag
420 {XGPF_INT, "Data Num", "", -1}, // ip3:
421 {XGPF_INT, "", "", -1},
422 {XGPF_INT, "", "", -1},
423 {XGPF_INT, "", "", -1},
424 {XGPF_INT, "", "", -1},
425 {XGPF_INT, "", "", -1},
426 {XGPF_INT, "", "", -1},
427 {XGPF_INT, "", "", -1},
428 {XGPF_INT, "", "", -1},
429 {XGPF_INT, "", "", -1},
430 {XGPF_INT, "", "", -1},
431 {XGPF_INT, "", "", -1},
432 {XGPF_INT, "", "", -1},
433 {XGPF_INT, "", "", -1},
434 {XGPF_INT, "", "", -1},
435 {XGPF_INT, "", "", -1},
436 {XGPF_INT, "", "", -1}
437 }
438 },
439 { de::function_cast<int (*)()>(XLTrav_LineCount), NULL, TRAV_LINES, 0, 1, 0, "Line Count",
440 // Changes the XG line(s)' internal activation counter
441 {{XGPF_INT, "Target Ref", "lref_", 0}, // ip0: (line ref) line(s) to change
442 {XGPF_INT, "Target Num", "", -1}, // ip1:
443 {XGPF_INT, "Set Absolute", "", -1}, // ip2: non-zero makes ip3 absolute
444 {XGPF_INT, "Count Delta", "", -1}, // ip3: count delta or absolute
445 {XGPF_INT, "", "", -1},
446 {XGPF_INT, "", "", -1},
447 {XGPF_INT, "", "", -1},
448 {XGPF_INT, "", "", -1},
449 {XGPF_INT, "", "", -1},
450 {XGPF_INT, "", "", -1},
451 {XGPF_INT, "", "", -1},
452 {XGPF_INT, "", "", -1},
453 {XGPF_INT, "", "", -1},
454 {XGPF_INT, "", "", -1},
455 {XGPF_INT, "", "", -1},
456 {XGPF_INT, "", "", -1},
457 {XGPF_INT, "", "", -1},
458 {XGPF_INT, "", "", -1},
459 {XGPF_INT, "", "", -1},
460 {XGPF_INT, "", "", -1}
461 }
462 },
463 { de::function_cast<int (*)()>(XLTrav_LeaveMap), NULL, TRAV_LINES, 1, 2, 0, "Leave Map",
464 // Exits the current map
465 {{XGPF_INT, "Secret Exit", "", -1}, // ip0: non-zero goto secret map
466 {XGPF_INT, "Data Ref", "lref_", 1}, // ip1: (line ref) line to acquire (line data ref) from
467 {XGPF_INT, "Data Num", "", -1}, // ip2:
468 {XGPF_INT, "Goto Map", "ldref_", 3}, // ip3: map ID or (line data ref from ip1)
469 {XGPF_INT, "", "", -1},
470 {XGPF_INT, "", "", -1},
471 {XGPF_INT, "", "", -1},
472 {XGPF_INT, "", "", -1},
473 {XGPF_INT, "", "", -1},
474 {XGPF_INT, "", "", -1},
475 {XGPF_INT, "", "", -1},
476 {XGPF_INT, "", "", -1},
477 {XGPF_INT, "", "", -1},
478 {XGPF_INT, "", "", -1},
479 {XGPF_INT, "", "", -1},
480 {XGPF_INT, "", "", -1},
481 {XGPF_INT, "", "", -1},
482 {XGPF_INT, "", "", -1},
483 {XGPF_INT, "", "", -1},
484 {XGPF_INT, "", "", -1}
485 }
486 },
487 { de::function_cast<int (*)()>(XLTrav_DisableLine), NULL, TRAV_LINES, 0, 1, 0, "Disable Line",
488 // Disables the referenced line(s) if active
489 {{XGPF_INT, "Target Ref", "lref_", 0}, // ip0: (line ref) line(s) to disable
490 {XGPF_INT, "Target Num", "", -1}, // ip1:
491 {XGPF_INT, "", "", -1},
492 {XGPF_INT, "", "", -1},
493 {XGPF_INT, "", "", -1},
494 {XGPF_INT, "", "", -1},
495 {XGPF_INT, "", "", -1},
496 {XGPF_INT, "", "", -1},
497 {XGPF_INT, "", "", -1},
498 {XGPF_INT, "", "", -1},
499 {XGPF_INT, "", "", -1},
500 {XGPF_INT, "", "", -1},
501 {XGPF_INT, "", "", -1},
502 {XGPF_INT, "", "", -1},
503 {XGPF_INT, "", "", -1},
504 {XGPF_INT, "", "", -1},
505 {XGPF_INT, "", "", -1},
506 {XGPF_INT, "", "", -1},
507 {XGPF_INT, "", "", -1},
508 {XGPF_INT, "", "", -1}
509 }
510 },
511 { de::function_cast<int (*)()>(XLTrav_EnableLine), NULL, TRAV_LINES, 0, 1, 0, "Enable Line",
512 // Enables the referenced line(s) if active.
513 {{XGPF_INT, "Target Ref", "lref_", 0}, // ip0: (line ref) line(s) to enable
514 {XGPF_INT, "Target Num", "", -1}, // ip1:
515 {XGPF_INT, "", "", -1},
516 {XGPF_INT, "", "", -1},
517 {XGPF_INT, "", "", -1},
518 {XGPF_INT, "", "", -1},
519 {XGPF_INT, "", "", -1},
520 {XGPF_INT, "", "", -1},
521 {XGPF_INT, "", "", -1},
522 {XGPF_INT, "", "", -1},
523 {XGPF_INT, "", "", -1},
524 {XGPF_INT, "", "", -1},
525 {XGPF_INT, "", "", -1},
526 {XGPF_INT, "", "", -1},
527 {XGPF_INT, "", "", -1},
528 {XGPF_INT, "", "", -1},
529 {XGPF_INT, "", "", -1},
530 {XGPF_INT, "", "", -1},
531 {XGPF_INT, "", "", -1},
532 {XGPF_INT, "", "", -1}
533 }
534 },
535 { de::function_cast<int (*)()>(XL_DoExplode), NULL, TRAV_NONE, 0, 1, 0, "Explode",
536 // Explodes the activator (no params).
537 {{XGPF_INT, "", "", -1},
538 {XGPF_INT, "", "", -1},
539 {XGPF_INT, "", "", -1},
540 {XGPF_INT, "", "", -1},
541 {XGPF_INT, "", "", -1},
542 {XGPF_INT, "", "", -1},
543 {XGPF_INT, "", "", -1},
544 {XGPF_INT, "", "", -1},
545 {XGPF_INT, "", "", -1},
546 {XGPF_INT, "", "", -1},
547 {XGPF_INT, "", "", -1},
548 {XGPF_INT, "", "", -1},
549 {XGPF_INT, "", "", -1},
550 {XGPF_INT, "", "", -1},
551 {XGPF_INT, "", "", -1},
552 {XGPF_INT, "", "", -1},
553 {XGPF_INT, "", "", -1},
554 {XGPF_INT, "", "", -1},
555 {XGPF_INT, "", "", -1},
556 {XGPF_INT, "", "", -1},
557 }
558 },
559 { de::function_cast<int (*)()>(XSTrav_PlaneMaterial), NULL, TRAV_PLANES, 0, 1, 0, "Plane Material",
560 // Change the material and/or surface color of a plane.
561 {{XGPF_INT, "Target Ref", "lpref_", 0}, // ip0 : (plane ref) plane(s) to change
562 {XGPF_INT, "Target Num", "", -1}, // ip1 : ref data
563 {XGPF_INT, "Material Ref", "spref_", 2}, // ip2 : Texture ref
564 {XGPF_INT, "Material Num", "", 3 | MAP_MATERIAL}, // ip3 : texture number (flat), used with SPREF_NONE
565 {XGPF_INT, "Red Delta", "", -1}, // ip4 : plane surface color (red)
566 {XGPF_INT, "Green Delta", "", -1}, // ip5 : "" (green)
567 {XGPF_INT, "Blue Delta", "", -1}, // ip6 : "" (blue)
568 {XGPF_INT, "Change Color", "", -1}, // ip7: if non-zero tint color will be changed
569 {XGPF_INT, "", "", -1},
570 {XGPF_INT, "", "", -1},
571 {XGPF_INT, "", "", -1},
572 {XGPF_INT, "", "", -1},
573 {XGPF_INT, "", "", -1},
574 {XGPF_INT, "", "", -1},
575 {XGPF_INT, "", "", -1},
576 {XGPF_INT, "", "", -1},
577 {XGPF_INT, "", "", -1},
578 {XGPF_INT, "", "", -1},
579 {XGPF_INT, "", "", -1},
580 {XGPF_INT, "", "", -1}
581 }
582 },
583 { de::function_cast<int (*)()>(XLTrav_ChangeWallMaterial), NULL, TRAV_LINES, 0, 1, 0, "Wall Material",
584 // Changes material(s) on the referenced line(s).
585 // Changes surface colour(s), alpha, mid textue blendmode and side flags
586 {{XGPF_INT, "Target Ref", "lref_", 0}, // ip0: (line ref) line(s) to change
587 {XGPF_INT, "Target Num", "", -1}, // ip1:
588 {XGPF_INT, "Side Num", "", -1}, // ip2: non-zero change the back side
589 {XGPF_INT, "Top Material", "", 3 | MAP_MATERIAL}, // ip3: top texture to change to (blank indicates no change)
590 {XGPF_INT, "Middle Material", "", 4 | MAP_MATERIAL}, // ip4: middle texture to change to (blank indicates no change)
591 {XGPF_INT, "Bottom Material", "", 5 | MAP_MATERIAL}, // ip5: bottom texture to change to (blank indicates no change)
592 {XGPF_INT, "Set Mid If None", "", -1}, // ip6: set mid texture even if previously zero
593 {XGPF_INT, "Sidedef Flags", "sdf_", 7}, // ip7: (sdf_) side flags (used with surface colour blending, fullbright etc)
594 {XGPF_INT, "Middle Blendmode", "bm_", 8}, // ip8: (bm_) middle texture blendmode
595 {XGPF_INT, "Top Red Delta", "", -1}, // ip9:
596 {XGPF_INT, "Top Green Delta", "", -1}, // ip10:
597 {XGPF_INT, "Top Blue Delta", "", -1}, // ip11:
598 {XGPF_INT, "Middle Red Delta", "", -1}, // ip12:
599 {XGPF_INT, "Middle Green Delta", "", -1}, // ip13:
600 {XGPF_INT, "Middle Blue Delta", "", -1}, // ip14:
601 {XGPF_INT, "Middle Alpha Delta", "", -1}, // ip15:
602 {XGPF_INT, "Bottom Red Delta", "", -1}, // ip16:
603 {XGPF_INT, "Bottom Green Delta", "", -1}, // ip17:
604 {XGPF_INT, "Bottom Blue Delta", "", -1}, // ip18:
605 {XGPF_INT, "", "", -1}
606 }
607 },
608 { de::function_cast<int (*)()>(XL_DoCommand), NULL, TRAV_NONE, 0, 1, 0, "Command",
609 // Executes a console command (CCmd)
610 {{XGPF_INT, "", "", -1},
611 {XGPF_INT, "", "", -1},
612 {XGPF_INT, "", "", -1},
613 {XGPF_INT, "", "", -1},
614 {XGPF_INT, "", "", -1},
615 {XGPF_INT, "", "", -1},
616 {XGPF_INT, "", "", -1},
617 {XGPF_INT, "", "", -1},
618 {XGPF_INT, "", "", -1},
619 {XGPF_INT, "", "", -1},
620 {XGPF_INT, "", "", -1},
621 {XGPF_INT, "", "", -1},
622 {XGPF_INT, "", "", -1},
623 {XGPF_INT, "", "", -1},
624 {XGPF_INT, "", "", -1},
625 {XGPF_INT, "", "", -1},
626 {XGPF_INT, "", "", -1},
627 {XGPF_INT, "", "", -1},
628 {XGPF_INT, "", "", -1},
629 {XGPF_INT, "", "", -1},
630 }
631 },
632 { de::function_cast<int (*)()>(XSTrav_SectorSound), NULL, TRAV_SECTORS, 0, 1, 0, "Sector Sound",
633 // Plays a sound in sector(s)
634 {{XGPF_INT, "Target Ref", "lsref_", 0}, // ip0: (sector ref) sector(s) to play the sound in
635 {XGPF_INT, "Target Num", "", -1}, // ip1:
636 {XGPF_INT, "Sound ID", "", 2 | MAP_SND}, // ip2: sound name/id to play
637 {XGPF_INT, "Origin", "", -1}, // ip3: non-zero = play from a specific origin (1=floor, 2=ceiling) else 0=center
638 {XGPF_INT, "", "", -1},
639 {XGPF_INT, "", "", -1},
640 {XGPF_INT, "", "", -1},
641 {XGPF_INT, "", "", -1},
642 {XGPF_INT, "", "", -1},
643 {XGPF_INT, "", "", -1},
644 {XGPF_INT, "", "", -1},
645 {XGPF_INT, "", "", -1},
646 {XGPF_INT, "", "", -1},
647 {XGPF_INT, "", "", -1},
648 {XGPF_INT, "", "", -1},
649 {XGPF_INT, "", "", -1},
650 {XGPF_INT, "", "", -1},
651 {XGPF_INT, "", "", -1},
652 {XGPF_INT, "", "", -1},
653 {XGPF_INT, "", "", -1}
654 }
655 },
656 { de::function_cast<int (*)()>(XSTrav_MimicSector), NULL, TRAV_SECTORS, 0, 1, 0, "Mimic Sector",
657 // Copies all properties from target sector to destination sector(s)
658 {{XGPF_INT, "Target Ref", "lsref_", 0}, // ip0: (sector ref) sector(s) to change
659 {XGPF_INT, "Target Num", "", -1}, // ip1:
660 {XGPF_INT, "Mimic Ref", "spref_", 2}, // ip2: (spref) sector to mimic
661 {XGPF_INT, "Mimic Num", "", -1}, // ip3:
662 {XGPF_INT, "", "", -1},
663 {XGPF_INT, "", "", -1},
664 {XGPF_INT, "", "", -1},
665 {XGPF_INT, "", "", -1},
666 {XGPF_INT, "", "", -1},
667 {XGPF_INT, "", "", -1},
668 {XGPF_INT, "", "", -1},
669 {XGPF_INT, "", "", -1},
670 {XGPF_INT, "", "", -1},
671 {XGPF_INT, "", "", -1},
672 {XGPF_INT, "", "", -1},
673 {XGPF_INT, "", "", -1},
674 {XGPF_INT, "", "", -1},
675 {XGPF_INT, "", "", -1},
676 {XGPF_INT, "", "", -1},
677 {XGPF_INT, "", "", -1}
678 }
679 },
680 { de::function_cast<int (*)()>(XSTrav_Teleport), NULL, TRAV_SECTORS, 0, 1, 0, "Teleport",
681 // Teleports the activator to the first teleport exit in the target sector
682 {{XGPF_INT, "Target Ref", "lsref_", 0}, // ip0: (sector ref) sector(s) to teleport to (first acceptable target is used)
683 {XGPF_INT, "Target Num", "", -1}, // ip1:
684 {XGPF_INT, "No Flash", "", -1}, // ip2: non-zero = no flash (or sound)
685 {XGPF_INT, "No Sound", "", -1}, // ip3: non-zero = no sound
686 {XGPF_INT, "Always Stomp", "", -1}, // ip4: non-zero = Always telefrag
687 {XGPF_INT, "", "", -1},
688 {XGPF_INT, "", "", -1},
689 {XGPF_INT, "", "", -1},
690 {XGPF_INT, "", "", -1},
691 {XGPF_INT, "", "", -1},
692 {XGPF_INT, "", "", -1},
693 {XGPF_INT, "", "", -1},
694 {XGPF_INT, "", "", -1},
695 {XGPF_INT, "", "", -1},
696 {XGPF_INT, "", "", -1},
697 {XGPF_INT, "", "", -1},
698 {XGPF_INT, "", "", -1},
699 {XGPF_INT, "", "", -1},
700 {XGPF_INT, "", "", -1},
701 {XGPF_INT, "", "", -1}
702 }
703 },
704 { de::function_cast<int (*)()>(XLTrav_LineTeleport), NULL, TRAV_LINES, 0, 1, 1 | XLE_CROSS, "Line Teleport",
705 // Teleports the activator to the referenced line
706 {{XGPF_INT, "Target Ref", "lref_", 0}, // ip0: (line ref) teleport destination
707 {XGPF_INT, "Target Num", "", -1}, // ip1:
708 {XGPF_INT, "No Flash", "", -1}, // ip2: non-zero = spawn MT_TFOG
709 {XGPF_INT, "Teleport Sound", "", 3 | MAP_SND}, // ip3: sound ID/name to play (or silent)
710 {XGPF_INT, "Exit Side", "", -1}, // ip4: non-zero = exit from the back of the target line
711 {XGPF_INT, "Always Stomp", "", -1}, // ip5: non-zero = Always telefrag
712 {XGPF_INT, "", "", -1},
713 {XGPF_INT, "", "", -1},
714 {XGPF_INT, "", "", -1},
715 {XGPF_INT, "", "", -1},
716 {XGPF_INT, "", "", -1},
717 {XGPF_INT, "", "", -1},
718 {XGPF_INT, "", "", -1},
719 {XGPF_INT, "", "", -1},
720 {XGPF_INT, "", "", -1},
721 {XGPF_INT, "", "", -1},
722 {XGPF_INT, "", "", -1},
723 {XGPF_INT, "", "", -1},
724 {XGPF_INT, "", "", -1},
725 {XGPF_INT, "", "", -1}
726 }
727 }
728 };
729
XG_Register()730 void XG_Register()
731 {
732 C_VAR_INT("xg-dev", &xgDev, CVF_NO_ARCHIVE, 0, 1);
733
734 C_CMD("movefloor", 0, MovePlane);
735 C_CMD("moveceil", 0, MovePlane);
736 C_CMD("movesec", 0, MovePlane);
737 }
738
739 /**
740 * Init XG data for the map.
741 */
XG_Init(void)742 void XG_Init(void)
743 {
744 XL_Init(); // Init lines.
745 XS_Init(); // Init sectors.
746 }
747
XG_Ticker(void)748 void XG_Ticker(void)
749 {
750 // Nothing to do.
751 }
752
753 /**
754 * This is called during an engine reset. Disables all XG functionality!
755 */
XG_Update(void)756 void XG_Update(void)
757 {
758 // Clients rely on the server, they don't do XG themselves.
759 if(IS_CLIENT)
760 return;
761
762 XG_ReadTypes();
763 XS_Update();
764 XL_Update();
765 }
766
767 /**
768 * Adds the given binary format line type to the generated types array
769 */
XL_AddAutoGenType(linetype_t *)770 int XL_AddAutoGenType(linetype_t * /*newtype*/)
771 {
772 return true;
773 }
774
775 /**
776 * Converts a line ID number to a line type (BOOM support).
777 */
XL_AutoGenType(int,linetype_t *)778 int XL_AutoGenType(int /*id*/, linetype_t * /*outptr*/)
779 {
780 return false; // Cos we don't work yet.
781
782 #if 0
783 linetype_t *l;
784
785 // Do the magic
786 // is the ID in the range 12160 -> 32768
787 if(!(id >= 12160 && id <= 32768))
788 return false;
789
790 // Have we already generated a line of this type?
791 if(1)
792 {
793 // Then return a ptr to it
794 outptr = l;
795 return true;
796 }
797
798 // Which bitfield format are we generating from?
799 if(1) //XG format
800 {
801 /*
802 // Generate the new line type
803 l->id = id;
804 l->flags = def->flags[0];
805 l->flags2 = def->flags[1];
806 l->flags3 = def->flags[2];
807 l->lineClass = def->lineClass;
808 l->actType = def->actType;
809 l->actCount = def->actCount;
810 l->actTime = def->actTime;
811 // l->actTag = def->actTag;
812 for(i = 0; i < 10; i++)
813 {
814 if(i == 9)
815 l->aparm[i] = Def_GetMobjNum(def->aparm9);
816 else
817 l->aparm[i] = def->aparm[i];
818 }
819 // l->tickerStart = def->tickerStart;
820 // l->tickerEnd = def->tickerEnd;
821 // l->tickerInterval = def->tickerInterval;
822 l->actSound = Friendly(Def_GetSoundNum(def->actSound));
823 l->deactSound = Friendly(Def_GetSoundNum(def->deactSound));
824 l->evChain = def->evChain;
825 l->actChain = def->actChain;
826 l->deactChain = def->deactChain;
827 l->actLineType = def->actLineType;
828 l->deactLineType = def->deactLineType;
829 l->wallSection = def->wallSection;
830 l->actTex = Friendly(R_CheckTextureNumForName(def->actTex));
831 l->deactTex = Friendly(R_CheckTextureNumForName(def->deactTex));
832 l->actMsg = def->actMsg;
833 l->deactMsg = def->deactMsg;
834 l->texMoveAngle = def->texMoveAngle;
835 l->texMoveSpeed = def->texMoveSpeed;*/
836
837 // Add it to the generated line types array
838 XL_AddAutoGenType(l);
839 outptr = l;
840 }
841 #if __JDOOM__ || __JDOOM64__
842 else // BOOM format
843 {
844 // Generate the new line type
845 outptr = l;
846 }
847 #endif
848
849 return true;
850 #endif
851 }
852
853 /**
854 * @return @c true, if the type is defined.
855 */
XL_GetType(int id)856 linetype_t* XL_GetType(int id)
857 {
858 linetype_t* ptr;
859 char buff[6];
860
861 // Try finding it from the DDXGDATA lump.
862 ptr = XG_GetLumpLine(id);
863 if(ptr)
864 { // Got it!
865 memcpy(&typebuffer, ptr, sizeof(*ptr));
866 return &typebuffer;
867 }
868
869 // Does Doomsday have a definition for this?
870 dd_snprintf(buff, 6, "%i", id);
871
872 if(Def_Get(DD_DEF_LINE_TYPE, buff, &typebuffer))
873 return &typebuffer;
874
875 // Is this a type we can generate automatically?
876 if(XL_AutoGenType(id, &typebuffer))
877 return &typebuffer;
878
879 // A definition was not found.
880 return NULL;
881 }
882
XG_RandomInt(int min,int max)883 int XG_RandomInt(int min, int max)
884 {
885 if(max == min) return max;
886
887 float x = M_Random() / 256.0f; // Never reaches 1.
888 return (int) (min + x * (max - min) + x);
889 }
890
XG_RandomPercentFloat(float value,int percent)891 float XG_RandomPercentFloat(float value, int percent)
892 {
893 float i = (2 * M_Random() / 255.0f - 1) * percent / 100.0f;
894 return value * (1 + i);
895 }
896
findXLThinker(thinker_t * th,void * context)897 int findXLThinker(thinker_t *th, void *context)
898 {
899 xlthinker_t *xl = (xlthinker_t *) th;
900
901 if(xl->line == (Line *) context)
902 return true; // Stop iteration, we've found it.
903
904 return false; // Continue iteration.
905 }
906
XL_SetLineType(Line * line,int id)907 void XL_SetLineType(Line *line, int id)
908 {
909 LOG_AS("XL_SetLineType");
910
911 xline_t *xline = P_ToXLine(line);
912
913 if(XL_GetType(id))
914 {
915 xline->special = id;
916
917 // Allocate memory for the line type data.
918 if(!xline->xg)
919 {
920 xline->xg = (xgline_t *)Z_Calloc(sizeof(xgline_t), PU_MAP, 0);
921 }
922
923 // Init the extended line state.
924 xline->xg->disabled = false;
925 xline->xg->timer = 0;
926 xline->xg->tickerTimer = 0;
927 std::memcpy(&xline->xg->info, &typebuffer, sizeof(linetype_t));
928
929 // Initial active state.
930 xline->xg->active = (typebuffer.flags & LTF_ACTIVE) != 0;
931 xline->xg->activator = dummyThing;
932
933 LOG_MAP_MSG_XGDEVONLY2("Line %i (%s), ID %i",
934 P_ToIndex(line)
935 << xgClasses[xline->xg->info.lineClass].className
936 << id);
937
938 // If there is not already an xlthinker for this line, create one.
939 if(!Thinker_Iterate(XL_Thinker, findXLThinker, line))
940 {
941 // Not created one yet.
942 /*xlthinker_t *xl = (xlthinker_t *)Z_Calloc(sizeof(*xl), PU_MAP, 0);
943
944 xl->thinker.function = XL_Thinker;*/
945
946 ThinkerT<xlthinker_t> xl(Thinker::AllocateMemoryZone);
947 xl.function = XL_Thinker;
948 xl->line = line;
949
950 Thinker_Add(xl.Thinker::take());
951 }
952 }
953 else if(id)
954 {
955 LOG_MAP_MSG_XGDEVONLY2("Line %i, type %i NOT DEFINED", P_ToIndex(line) << id);
956 }
957 }
958
XL_Init()959 void XL_Init()
960 {
961 dummyThing.Thinker::zap();
962
963 // Clients rely on the server, they don't do XG themselves.
964 if(IS_CLIENT) return;
965
966 for(int i = 0; i < numlines; ++i)
967 {
968 Line *line = (Line *)P_ToPtr(DMU_LINE, i);
969 P_ToXLine(line)->xg = 0;
970
971 XL_SetLineType(line, P_ToXLine(line)->special);
972 }
973 }
974
XL_TraversePlanes(Line * line,int refType,int ref,void * data,void * context,dd_bool travsectors,mobj_t * activator,PlaneTraverserFunc func)975 int XL_TraversePlanes(Line *line, int refType, int ref, void *data, void *context,
976 dd_bool travsectors, mobj_t *activator, PlaneTraverserFunc func)
977 {
978 LOG_AS(travsectors? "XL_TraverseSectors" : "XL_TraversePlanes");
979
980 int tag = 0;
981 mobj_t *mo = NULL;
982 dd_bool ok, findSecTagged;
983
984 if(xgDev)
985 {
986 char buff[50];
987
988 if(ref)
989 {
990 sprintf(buff, ": %i", ref);
991 }
992
993 LOG_MAP_MSG_XGDEVONLY2("Line %i, ref (%s%s)",
994 P_ToIndex(line)
995 << (travsectors? LSREFTYPESTR(refType) : LPREFTYPESTR(refType))
996 << (ref? buff: ""));
997 }
998
999 if(refType == LPREF_NONE)
1000 return false; // This is not a reference!
1001
1002 Sector *frontSec = (Sector *)P_GetPtrp(line, DMU_FRONT_SECTOR);
1003 Sector *backSec = (Sector *)P_GetPtrp(line, DMU_BACK_SECTOR);
1004
1005 // References to a single plane
1006 if(refType == LPREF_MY_FLOOR || refType == LPREF_MY_CEILING)
1007 {
1008 if(frontSec)
1009 {
1010 return func(frontSec, refType == LPREF_MY_CEILING? true:false,
1011 data, context, activator);
1012 }
1013
1014 LOG_MAP_MSG_XGDEVONLY2("Line %i has no front sector!", P_ToIndex(line));
1015 }
1016
1017 if(refType == LPREF_BACK_FLOOR || refType == LPREF_BACK_CEILING)
1018 {
1019 if(backSec)
1020 {
1021 return func(backSec, refType == LPREF_BACK_CEILING? true:false,
1022 data, context, activator);
1023 }
1024
1025 LOG_MAP_MSG_XGDEVONLY2("Line %i has no back sector!", P_ToIndex(line));
1026 }
1027
1028 if(refType == LPREF_INDEX_FLOOR)
1029 {
1030 return func((Sector *)P_ToPtr(DMU_SECTOR, ref), false, data, context,
1031 activator);
1032 }
1033
1034 if(refType == LPREF_INDEX_CEILING)
1035 {
1036 return func((Sector *)P_ToPtr(DMU_SECTOR, ref), true, data, context,
1037 activator);
1038 }
1039
1040 // Can we use the tagged sector lists?
1041 findSecTagged = false;
1042 if(refType == LPREF_TAGGED_FLOORS || refType == LPREF_TAGGED_CEILINGS)
1043 {
1044 findSecTagged = true;
1045 tag = ref;
1046 }
1047 else if(refType == LPREF_LINE_TAGGED_FLOORS ||
1048 refType == LPREF_LINE_TAGGED_CEILINGS)
1049 {
1050 findSecTagged = true;
1051 tag = P_ToXLine(line)->tag;
1052 }
1053
1054 // References to multiple planes
1055 if(findSecTagged)
1056 {
1057 // Use tagged sector lists for these (speed).
1058 if(iterlist_t *list = P_GetSectorIterListForTag(tag, false))
1059 {
1060 // Find the first sector with the tag.
1061 IterList_SetIteratorDirection(list, ITERLIST_FORWARD);
1062 IterList_RewindIterator(list);
1063
1064 Sector *sec;
1065 while((sec = (Sector *)IterList_MoveIterator(list)) != NULL)
1066 {
1067 //xsector_t *xsec = P_ToXSector(sec);
1068
1069 if(refType == LPREF_TAGGED_FLOORS || refType == LPREF_TAGGED_CEILINGS)
1070 {
1071 if(!func(sec, refType == LPREF_TAGGED_CEILINGS, data,
1072 context, activator))
1073 {
1074 return false;
1075 }
1076 }
1077
1078 if(refType == LPREF_LINE_TAGGED_FLOORS ||
1079 refType == LPREF_LINE_TAGGED_CEILINGS)
1080 {
1081 if(!func(sec, refType == LPREF_LINE_TAGGED_CEILINGS,
1082 data, context, activator))
1083 {
1084 return false;
1085 }
1086 }
1087 }
1088 }
1089 }
1090 else
1091 {
1092 for(int i = 0; i < numsectors; ++i)
1093 {
1094 Sector *sec = (Sector *)P_ToPtr(DMU_SECTOR, i);
1095 xsector_t *xsec = P_ToXSector(sec);
1096
1097 if(refType == LPREF_ALL_FLOORS || refType == LPREF_ALL_CEILINGS)
1098 {
1099 if(!func(sec, refType == LPREF_ALL_CEILINGS, data,
1100 context, activator))
1101 {
1102 return false;
1103 }
1104 }
1105
1106 if((refType == LPREF_ACT_TAGGED_FLOORS ||
1107 refType == LPREF_ACT_TAGGED_CEILINGS) && xsec->xg &&
1108 xsec->xg->info.actTag == ref)
1109 {
1110 if(!func(sec, refType == LPREF_ACT_TAGGED_CEILINGS, data,
1111 context, activator))
1112 {
1113 return false;
1114 }
1115 }
1116
1117 // Reference all sectors with (at least) one mobj of specified
1118 // type inside.
1119 if(refType == LPREF_THING_EXIST_FLOORS ||
1120 refType == LPREF_THING_EXIST_CEILINGS)
1121 {
1122 ok = true;
1123
1124 for(mo = (mobj_t *)P_GetPtrp(sec, DMT_MOBJS); ok && mo; mo = mo->sNext)
1125 {
1126 if(mo->type == P_ToXLine(line)->xg->info.aparm[9])
1127 {
1128 LOG_MAP_MSG_XGDEVONLY2("Thing of type %i found in sector id %i",
1129 P_ToXLine(line)->xg->info.aparm[9] << i);
1130
1131 if(!func(sec, refType == LPREF_THING_EXIST_CEILINGS,
1132 data, context, activator))
1133 {
1134 return false;
1135 }
1136
1137 ok = false;
1138 }
1139 }
1140 }
1141
1142 // Reference all sectors with NONE of the specified mobj type
1143 // inside.
1144 if(refType == LPREF_THING_NOEXIST_FLOORS ||
1145 refType == LPREF_THING_NOEXIST_CEILINGS)
1146 {
1147 ok = true;
1148
1149 for(mo = (mobj_t *)P_GetPtrp(sec, DMT_MOBJS); ok && mo; mo = mo->sNext)
1150 {
1151 if(mo->type != P_ToXLine(line)->xg->info.aparm[9])
1152 {
1153 continue;
1154 }
1155
1156 ok = false;
1157 }
1158
1159 if(ok)
1160 {
1161 LOG_MAP_MSG_XGDEVONLY2("No things of type %i found in sector id %i",
1162 P_ToXLine(line)->xg->info.aparm[9] << i);
1163
1164 if(!func(sec, refType == LPREF_THING_NOEXIST_CEILINGS,
1165 data, context, activator))
1166 {
1167 return false;
1168 }
1169 }
1170 }
1171 }
1172 }
1173
1174 return true;
1175 }
1176
XL_TraverseLines(Line * line,int rtype,int ref,void * data,void * context,mobj_t * activator,LineTraverserFunc func)1177 int XL_TraverseLines(Line* line, int rtype, int ref, void* data,
1178 void* context, mobj_t* activator, LineTraverserFunc func)
1179 {
1180 LOG_AS("XL_TraverseLines");
1181
1182 int i;
1183 int tag = 0;
1184 int reftype = rtype;
1185 char buff[50];
1186 Line *iter = NULL;
1187 dd_bool findLineTagged;
1188
1189 // Binary XG data from DD_XGDATA uses the old flag values.
1190 // Add one to the ref type.
1191 if(xgDataLumps)
1192 reftype+= 1;
1193
1194 if(ref)
1195 sprintf(buff," : %i",ref);
1196
1197 LOG_MAP_MSG_XGDEVONLY2("Line %i, ref (%s%s)",
1198 P_ToIndex(line) << LREFTYPESTR(reftype) << (ref? buff : ""));
1199
1200 if(reftype == LREF_NONE)
1201 return func(NULL, true, data, context, activator); // Not a real reference
1202
1203 // References to single lines
1204 if(reftype == LREF_SELF) // Traversing self is simple.
1205 return func(line, true, data, context, activator);
1206
1207 if(reftype == LREF_INDEX)
1208 return func((Line *)P_ToPtr(DMU_LINE, ref), true, data, context, activator);
1209
1210 // Can we use the tagged line lists?
1211 findLineTagged = false;
1212 if(reftype == LREF_TAGGED)
1213 {
1214 findLineTagged = true;
1215 tag = ref;
1216 }
1217 else if(reftype == LREF_LINE_TAGGED)
1218 {
1219 findLineTagged = true;
1220 tag = P_ToXLine(line)->tag;
1221 }
1222
1223 // References to multiple lines
1224 if(findLineTagged)
1225 {
1226 // Use tagged line lists for these (speed).
1227 if(iterlist_t *list = P_GetLineIterListForTag(tag, false))
1228 {
1229 IterList_SetIteratorDirection(list, ITERLIST_FORWARD);
1230 IterList_RewindIterator(list);
1231 while((iter = (Line *)IterList_MoveIterator(list)) != NULL)
1232 {
1233 if(reftype == LREF_TAGGED)
1234 {
1235 if(!func(iter, true, data, context, activator))
1236 return false;
1237 }
1238 else if(reftype == LREF_LINE_TAGGED)
1239 {
1240 // Ref is true if line itself should be excluded.
1241 if(!ref || iter != line)
1242 {
1243 if(!func(iter, true, data, context, activator))
1244 return false;
1245 }
1246 }
1247 }
1248 }
1249 }
1250 else
1251 {
1252 for(i = 0; i < numlines; ++i)
1253 {
1254 iter = (Line *)P_ToPtr(DMU_LINE, i);
1255 if(reftype == LREF_ALL)
1256 {
1257 if(!func(iter, true, data, context, activator))
1258 return false;
1259 }
1260 else if(reftype == LREF_ACT_TAGGED)
1261 {
1262 xline_t *xl = P_ToXLine(iter);
1263
1264 if(xl->xg && xl->xg->info.actTag == ref)
1265 {
1266 if(!func(iter, true, data, context, activator))
1267 return false;
1268 }
1269 }
1270 }
1271 }
1272 return true;
1273 }
1274
1275 /**
1276 * @return Value as determined by the reference type from the specified line, using data
1277 * from either the line itself or context (will always be linetype_t).
1278 */
XL_ValidateLineRef(Line * line,int reftype,void *,char const * parmname)1279 int XL_ValidateLineRef(Line *line, int reftype, void * /*context*/, char const *parmname)
1280 {
1281 LOG_AS("XL_ValidateLineRef");
1282
1283 int answer = 0;
1284 Side *side;
1285
1286 switch(reftype)
1287 {
1288 case LDREF_ID: // Line ID.
1289 answer = P_ToIndex(line);
1290 LOG_MAP_MSG_XGDEVONLY2("Using Line ID (%i) as %s", answer << parmname);
1291 break;
1292
1293 case LDREF_SPECIAL: // Line Special.
1294 answer = P_ToXLine(line)->special;
1295 LOG_MAP_MSG_XGDEVONLY2("Using Line Special (%i) as %s", answer << parmname);
1296 break;
1297
1298 case LDREF_TAG: // Line Tag.
1299 answer = P_ToXLine(line)->tag;
1300 LOG_MAP_MSG_XGDEVONLY2("Using Line Tag (%i) as %s", answer << parmname);
1301 break;
1302
1303 case LDREF_ACTTAG: // Line ActTag.
1304 if(!P_ToXLine(line)->xg)
1305 {
1306 LOG_MAP_MSG_XGDEVONLY("REFERENCE NOT AN XG LINE");
1307 break;
1308 }
1309
1310 if(!P_ToXLine(line)->xg->info.actTag)
1311 {
1312 LOG_MAP_MSG_XGDEVONLY("REFERENCE DOESNT HAVE AN ACT TAG");
1313 break;
1314 }
1315
1316 answer = P_ToXLine(line)->xg->info.actTag;
1317 LOG_MAP_MSG_XGDEVONLY2("Using Line ActTag (%i) as %s", answer << parmname);
1318 break;
1319
1320 case LDREF_COUNT: // Line count.
1321 if(!P_ToXLine(line)->xg)
1322 {
1323 LOG_MAP_MSG_XGDEVONLY("REFERENCE NOT AN XG LINE");
1324 break;
1325 }
1326
1327 answer = P_ToXLine(line)->xg->info.actCount;
1328 LOG_MAP_MSG_XGDEVONLY2("Using Line Count (%i) as %s", answer << parmname);
1329 break;
1330
1331 case LDREF_ANGLE: { // Line angle.
1332 coord_t d1[2];
1333
1334 P_GetDoublepv(line, DMU_DXY, d1);
1335 answer = M_PointXYToAngle2(0, 0, d1[0], d1[1]) / (float) ANGLE_MAX * 360;
1336 LOG_MAP_MSG_XGDEVONLY2("Using Line Angle (%i) as %s", answer << parmname);
1337 break; }
1338
1339 case LDREF_LENGTH: // Line length.
1340 // Answer should be in map units.
1341 answer = P_GetFixedp(line, DMU_LENGTH) >> FRACBITS;
1342
1343 LOG_MAP_MSG_XGDEVONLY2("Using Line Length (%i) as %s", answer << parmname);
1344 break;
1345
1346 case LDREF_TOP_OFFSETX:
1347 // Can this ever fail?
1348 side = (Side *)P_GetPtrp(line, DMU_FRONT);
1349 if(!side)
1350 {
1351 LOG_MAP_MSG_XGDEVONLY("REFERENCE MISSING FRONT SIDE!");
1352 break;
1353 }
1354
1355 answer = P_GetIntp(side, DMU_TOP_MATERIAL_OFFSET_X);
1356
1357 LOG_MAP_MSG_XGDEVONLY2("Using Line Top X Offset (%i) as %s", answer << parmname);
1358 break;
1359
1360 case LDREF_TOP_OFFSETY:
1361 // Can this ever fail? (yes -dj)
1362 side = (Side *)P_GetPtrp(line, DMU_FRONT);
1363 if(!side)
1364 {
1365 LOG_MAP_MSG_XGDEVONLY("REFERENCE MISSING FRONT SIDE!");
1366 break;
1367 }
1368
1369 answer = P_GetIntp(side, DMU_TOP_MATERIAL_OFFSET_Y);
1370
1371 LOG_MAP_MSG_XGDEVONLY2("Using Line Top Y Offset (%i) as %s", answer << parmname);
1372 break;
1373
1374 case LDREF_MIDDLE_OFFSETX:
1375 // Can this ever fail? (yes -dj)
1376 side = (Side *)P_GetPtrp(line, DMU_FRONT);
1377 if(!side)
1378 {
1379 LOG_MAP_MSG_XGDEVONLY("REFERENCE MISSING FRONT SIDE!");
1380 break;
1381 }
1382
1383 answer = P_GetIntp(side, DMU_MIDDLE_MATERIAL_OFFSET_X);
1384
1385 LOG_MAP_MSG_XGDEVONLY2("Using Line Middle X Offset (%i) as %s", answer << parmname);
1386 break;
1387
1388 case LDREF_MIDDLE_OFFSETY:
1389 // Can this ever fail? (yes -dj)
1390 side = (Side *)P_GetPtrp(line, DMU_FRONT);
1391 if(!side)
1392 {
1393 LOG_MAP_MSG_XGDEVONLY("REFERENCE MISSING FRONT SIDE!");
1394 break;
1395 }
1396
1397 answer = P_GetIntp(side, DMU_MIDDLE_MATERIAL_OFFSET_Y);
1398
1399 LOG_MAP_MSG_XGDEVONLY2("Using Line Middle Y Offset (%i) as %s", answer << parmname);
1400 break;
1401
1402 case LDREF_BOTTOM_OFFSETX:
1403 // Can this ever fail? (yes -dj)
1404 side = (Side *)P_GetPtrp(line, DMU_FRONT);
1405 if(!side)
1406 {
1407 LOG_MAP_MSG_XGDEVONLY("REFERENCE MISSING FRONT SIDE!");
1408 break;
1409 }
1410
1411 answer = P_GetIntp(side, DMU_BOTTOM_MATERIAL_OFFSET_X);
1412
1413 LOG_MAP_MSG_XGDEVONLY2("Using Line Bottom X Offset (%i) as %s", answer << parmname);
1414 break;
1415
1416 case LDREF_BOTTOM_OFFSETY:
1417 // Can this ever fail? (yes -dj)
1418 side = (Side *)P_GetPtrp(line, DMU_FRONT);
1419 if(!side)
1420 {
1421 LOG_MAP_MSG_XGDEVONLY("REFERENCE MISSING FRONT SIDE!");
1422 break;
1423 }
1424
1425 answer = P_GetIntp(side, DMU_BOTTOM_MATERIAL_OFFSET_Y);
1426
1427 LOG_MAP_MSG_XGDEVONLY2("Using Line Bottom Y Offset (%i) as %s", answer << parmname);
1428 break;
1429
1430 default: // Could be explicit, return the actual int value
1431 answer = reftype;
1432 break;
1433 }
1434
1435 return answer;
1436 }
1437
1438 /**
1439 * Executes the lines' function as defined by its class.
1440 */
XL_DoFunction(linetype_t * info,Line * line,int sideNum,mobj_t * actThing,int evType)1441 void XL_DoFunction(linetype_t *info, Line *line, int sideNum, mobj_t *actThing, int evType)
1442 {
1443 DENG2_ASSERT(info && line);
1444 DENG2_ASSERT(info->lineClass >= 0 && info->lineClass < NUMXGCLASSES);
1445 LOG_AS("XL_DoFunction");
1446
1447 xgclass_t *xgClass = &xgClasses[info->lineClass];
1448
1449 LOG_MAP_MSG_XGDEVONLY2("Line %i, side %i, activator id %i, event %s",
1450 P_ToIndex(line) << sideNum << (actThing ? actThing->thinker.id : 0)
1451 << EVTYPESTR(evType));
1452 LOG_MAP_MSG_XGDEVONLY2("Executing class: %s (0x%X)...", xgClass->className << info->lineClass);
1453
1454 // Does this class only work with certain events?
1455 if(xgClass->evTypeFlags > 0)
1456 {
1457 if(!(xgClass->evTypeFlags & evType))
1458 {
1459 LOG_MAP_MSG_XGDEVONLY2("THIS CLASS DOES NOT SUPPORT %s EVENTS!", EVTYPESTR(evType));
1460 return;
1461 }
1462 }
1463
1464 // Does this class have an init function?
1465 if(xgClass->initFunc)
1466 xgClass->initFunc(line);
1467
1468 // Does this class have a do function?
1469 if(xgClass->doFunc)
1470 {
1471 // Do we need to traverse?
1472 switch(xgClass->traverse)
1473 {
1474 case TRAV_NONE: // No need for traversal, call the doFunc
1475 de::function_cast<LineTraverserFunc>(xgClass->doFunc)(line, true, line, info, actThing);
1476 break;
1477 case TRAV_LINES: // Traverse lines, executing doFunc for each
1478 XL_TraverseLines(line, info->iparm[xgClass->travRef],
1479 info->iparm[xgClass->travData], line,
1480 info, actThing, de::function_cast<LineTraverserFunc>(xgClass->doFunc));
1481 break;
1482 case TRAV_PLANES: // Traverse planes, executing doFunc for each
1483 case TRAV_SECTORS:
1484 XL_TraversePlanes(line, info->iparm[xgClass->travRef],
1485 info->iparm[xgClass->travData], line,
1486 info, xgClass->traverse == TRAV_SECTORS? true : false,
1487 actThing, de::function_cast<PlaneTraverserFunc>(xgClass->doFunc));
1488 break;
1489 }
1490 }
1491 }
1492
XLTrav_QuickActivate(Line * line,dd_bool,void * context,void *,mobj_t *)1493 int XLTrav_QuickActivate(Line *line, dd_bool /*ceiling*/, void *context, void * /*context2*/,
1494 mobj_t * /*activator*/)
1495 {
1496 if(line)
1497 {
1498 xline_t *xline = P_ToXLine(line);
1499 if(xline->xg)
1500 {
1501 xline->xg->active = (context? true : false);
1502 xline->xg->timer = XLTIMER_STOPPED; // Stop timer.
1503 }
1504 }
1505
1506 return true; // Continue iteration.
1507 }
1508
1509 /**
1510 * @return @c true, if the line is active.
1511 */
XLTrav_CheckLine(Line * line,dd_bool,void * context,void *,mobj_t *)1512 int XLTrav_CheckLine(Line *line, dd_bool /*ceiling*/, void *context,
1513 void * /*context2*/, mobj_t * /*activator*/)
1514 {
1515 if(line)
1516 {
1517 xline_t *xline = P_ToXLine(line);
1518
1519 if(!xline->xg)
1520 {
1521 return false; // Stop checking!
1522 }
1523
1524 return CPP_BOOL(xline->xg->active) == (context? true : false);
1525 }
1526
1527 return true; // Continue iteration.
1528 }
1529
1530 /**
1531 * @param data If @c true, the line will receive a chain event if inactive.
1532 * If @c false, the line will receive a chain event if active.
1533 */
XLTrav_SmartActivate(Line * line,dd_bool,void * context,void * context2,mobj_t *)1534 int XLTrav_SmartActivate(Line *line, dd_bool /*ceiling*/, void *context,
1535 void *context2, mobj_t * /*activator*/)
1536 {
1537 if(line)
1538 {
1539 xline_t *xline = P_ToXLine(line);
1540
1541 if(xline->xg)
1542 {
1543 if(CPP_BOOL(xline->xg->active) != (context? true : false))
1544 {
1545 XL_LineEvent(XLE_CHAIN, 0, line, 0, context2);
1546 }
1547 }
1548 }
1549
1550 return true; // Continue iteration.
1551 }
1552
1553 //
1554 // XG Line Type Classes which don't require traversal
1555 //
1556
XL_DoChainSequence(Line * line,dd_bool,void *,void * context2,mobj_t *)1557 int XL_DoChainSequence(Line *line, dd_bool /*ceiling*/, void * /*context*/,
1558 void *context2, mobj_t * /*activator*/)
1559 {
1560 if(line)
1561 {
1562 xline_t *xline = P_ToXLine(line);
1563
1564 if(xline->xg)
1565 {
1566 linetype_t *info = static_cast<linetype_t *>(context2);
1567
1568 xline->xg->chIdx = 1; // This is the first.
1569 // Start counting the first interval.
1570 xline->xg->chTimer =
1571 XG_RandomPercentFloat(info->fparm[1], info->fparm[0]);
1572 }
1573 }
1574
1575 return true;
1576 }
1577
XL_DoDamage(Line *,dd_bool,void *,void * context2,mobj_t * activator)1578 int XL_DoDamage(Line * /*line*/, dd_bool /*ceiling*/, void * /*context*/, void *context2,
1579 mobj_t *activator)
1580 {
1581 linetype_t *info = static_cast<linetype_t *>(context2);
1582
1583 if(!activator)
1584 {
1585 /* if(evtype == XLE_FUNC)
1586 LOG_MAP_MSG_XGDEVONLY("Sector Functions don't have activators! Use a Sector Chain instead");
1587 else*/
1588 LOG_MAP_MSG_XGDEVONLY("No activator! Can't damage anything");
1589
1590 return false;
1591 }
1592
1593 if(activator->health > info->iparm[2])
1594 {
1595 // Iparms define the min and max damage to inflict.
1596 // The real amount is random.
1597 int i = XG_RandomInt(info->iparm[0], info->iparm[1]);
1598 if(i > 0)
1599 {
1600 P_DamageMobj(activator, 0, 0, i, false);
1601 }
1602 else if(i < 0 && activator->health < info->iparm[3])
1603 {
1604 int origHealth = activator->health;
1605 // Don't go above a given level.
1606 activator->health = MIN_OF(activator->health-i, info->iparm[3]);
1607 // Need to signal an update?
1608 if(activator->player && activator->health != origHealth)
1609 {
1610 activator->player->health = activator->health;
1611 activator->player->update |= PSF_HEALTH;
1612 }
1613 }
1614 }
1615
1616 return true;
1617 }
1618
XL_DoPower(Line *,dd_bool,void *,void * context2,mobj_t * activator)1619 int XL_DoPower(Line * /*line*/, dd_bool /*ceiling*/, void * /*context*/,
1620 void *context2, mobj_t *activator)
1621 {
1622 linetype_t *info = static_cast<linetype_t *>(context2);
1623 DENG2_ASSERT(info);
1624 player_t *player = activator? activator->player : nullptr;
1625
1626 // Only players have armor.
1627 if(!player)
1628 {
1629 /* if(evtype == XLE_FUNC)
1630 LOG_MAP_MSG_XGDEVONLY("Sector Functions don't have activators! Use a Sector Chain instead");
1631 else*/
1632 LOG_MAP_MSG_XGDEVONLY("Activator MUST be a player...");
1633
1634 return false;
1635 }
1636
1637 int delta = XG_RandomInt(info->iparm[0], info->iparm[1]);
1638 if(delta > 0)
1639 {
1640 if(player->armorPoints + delta >= info->iparm[3])
1641 delta = de::max(0, info->iparm[3] - player->armorPoints);
1642 }
1643 else
1644 {
1645 if(player->armorPoints + delta <= info->iparm[2])
1646 delta = de::min(0, info->iparm[2] - player->armorPoints);
1647 }
1648
1649 if(delta)
1650 {
1651 if(!player->armorType)
1652 P_PlayerSetArmorType(player, 1);
1653 P_PlayerGiveArmorBonus(player, delta);
1654 }
1655
1656 return true;
1657 }
1658
XL_DoKey(Line *,dd_bool,void *,void * context2,mobj_t * activator)1659 int XL_DoKey(Line * /*line*/, dd_bool /*ceiling*/, void * /*context*/,
1660 void *context2, mobj_t *activator)
1661 {
1662 linetype_t *info = static_cast<linetype_t *>(context2);
1663 player_t *player = (activator? activator->player : 0);
1664
1665 if(!player) // Must be a player.
1666 {
1667 /* if(evtype == XLE_FUNC)
1668 LOG_MAP_MSG_XGDEVONLY("Sector Functions don't have activators! Use a Sector Chain instead");
1669 else*/
1670 LOG_MAP_MSG_XGDEVONLY("Activator MUST be a player...");
1671
1672 return false;
1673 }
1674
1675 for(int i = 0; i < NUM_KEY_TYPES; ++i)
1676 {
1677 if(info->iparm[0] & (1 << i))
1678 P_GiveKey(player, keytype_t(i));
1679 if(info->iparm[1] & (1 << i))
1680 player->keys[i] = false;
1681 }
1682
1683 return true;
1684 }
1685
XL_DoExplode(Line *,dd_bool,void *,void *,mobj_t * activator)1686 int XL_DoExplode(Line * /*line*/, dd_bool /*ceiling*/, void * /*context*/,
1687 void * /*context2*/, mobj_t *activator)
1688 {
1689 if(!activator)
1690 {
1691 /* if(evtype == XLE_FUNC)
1692 LOG_MAP_MSG_XGDEVONLY("Sector Functions don't have activators! Use a Sector Chain instead");
1693 else*/
1694 LOG_MAP_MSG_XGDEVONLY("No activator! Can't explode anything");
1695 return false;
1696 }
1697
1698 P_ExplodeMissile(activator);
1699 return true;
1700 }
1701
XL_DoCommand(Line *,dd_bool,void *,void * context2,mobj_t *)1702 int XL_DoCommand(Line * /*line*/, dd_bool /*ceiling*/, void * /*context*/,
1703 void *context2, mobj_t * /*activator*/)
1704 {
1705 linetype_t *info = static_cast<linetype_t *>(context2);
1706
1707 DD_Execute(true, info->sparm[0]);
1708 return true;
1709 }
1710
1711 //
1712 // Following classes require traversal hence "Trav_"
1713 //
1714
XLTrav_ChangeLineType(Line * line,dd_bool,void *,void * context2,mobj_t *)1715 int XLTrav_ChangeLineType(Line *line, dd_bool /*ceiling*/, void * /*context*/,
1716 void *context2, mobj_t * /*activator*/)
1717 {
1718 if(line)
1719 {
1720 linetype_t *info = static_cast<linetype_t *>(context2);
1721 XL_SetLineType(line, info->iparm[2]);
1722 }
1723
1724 return true; // Keep looking.
1725 }
1726
lineSideIfSector(Line & line,bool back=false)1727 static Side *lineSideIfSector(Line &line, bool back = false)
1728 {
1729 if(P_GetPtrp(&line, back? DMU_BACK_SECTOR : DMU_FRONT_SECTOR))
1730 {
1731 return (Side *)P_GetPtrp(&line, back? DMU_BACK : DMU_FRONT);
1732 }
1733 return nullptr;
1734 }
1735
XLTrav_ChangeWallMaterial(Line * line,dd_bool,void *,void * context2,mobj_t *)1736 int XLTrav_ChangeWallMaterial(Line *line, dd_bool /*ceiling*/, void * /*context*/,
1737 void *context2, mobj_t * /*activator*/)
1738 {
1739 LOG_AS("XLTrav_ChangeWallMaterial");
1740
1741 linetype_t *info = static_cast<linetype_t *>(context2);
1742 if(!line) return true; // Continue iteration.
1743
1744 // i2: sidenum
1745 // i3: top material (zero if no change)
1746 // i4: mid material (zero if no change, -1 to remove)
1747 // i5: bottom material (zero if no change)
1748 // i6: (true/false) set midtexture even if previously zero
1749 // i7: sdf_* flags
1750 // i8: mid blendmode
1751 // i9: top red
1752 // i10: top green
1753 // i11: top blue
1754 // i12: mid red
1755 // i13: mid green
1756 // i14: mid blue
1757 // i15: mid alpha
1758 // i16: bottom red
1759 // i17: bottom green
1760 // i18: bottom blue
1761
1762 Side *side = lineSideIfSector(*line, CPP_BOOL(info->iparm[2]));
1763 if(!side) return true; // Continue iteration.
1764
1765 LOG_MAP_MSG_XGDEVONLY2("Line %i", P_ToIndex(line));
1766
1767 XL_ChangeMaterial(line, info->iparm[2], LWS_UPPER,
1768 (world_Material *)P_ToPtr(DMU_MATERIAL, info->iparm[3]),
1769 BM_NORMAL, Vector3f(info->iparm[9], info->iparm[10], info->iparm[11]) / 255.f,
1770 info->iparm[7]);
1771
1772 world_Material *mat = 0;
1773 if(info->iparm[4] && (P_GetPtrp(side, DMU_MIDDLE_MATERIAL) || info->iparm[6]))
1774 {
1775 if(!P_GetPtrp(line, DMU_BACK_SECTOR) && info->iparm[4] == -1)
1776 mat = 0;
1777 else
1778 mat = (world_Material *)P_ToPtr(DMU_MATERIAL, info->iparm[4]);
1779 }
1780
1781 XL_ChangeMaterial(line, info->iparm[2], LWS_MID, mat,
1782 blendmode_t(info->iparm[8]),
1783 Vector4f(info->iparm[12], info->iparm[13], info->iparm[14], info->iparm[15]) / 255.f,
1784 info->iparm[7]);
1785
1786 XL_ChangeMaterial(line, info->iparm[2], LWS_LOWER,
1787 (world_Material *)P_ToPtr(DMU_MATERIAL, info->iparm[5]),
1788 BM_NORMAL, Vector3f(info->iparm[16], info->iparm[17], info->iparm[18]) / 255.f,
1789 info->iparm[7]);
1790
1791 return true;
1792 }
1793
XLTrav_Activate(Line * line,dd_bool,void *,void *,mobj_t * activator)1794 int XLTrav_Activate(Line *line, dd_bool /*ceiling*/, void * /*context*/,
1795 void * /*context2*/, mobj_t *activator)
1796 {
1797 if(line)
1798 {
1799 XL_LineEvent(XLE_CHAIN, 0, line, 0, activator);
1800 }
1801 return true; // Keep looking.
1802 }
1803
XLTrav_LineCount(Line * line,dd_bool,void *,void * context2,mobj_t *)1804 int XLTrav_LineCount(Line *line, dd_bool /*ceiling*/, void * /*context*/,
1805 void *context2, mobj_t * /*activator*/)
1806 {
1807 linetype_t *info = static_cast<linetype_t *>(context2);
1808
1809 if(line)
1810 {
1811 xline_t *xline = P_ToXLine(line);
1812 if(xline->xg)
1813 {
1814 if(info->iparm[2])
1815 {
1816 xline->xg->info.actCount = info->iparm[3];
1817 }
1818 else
1819 {
1820 xline->xg->info.actCount += info->iparm[3];
1821 }
1822 }
1823 }
1824
1825 return true;
1826 }
1827
XLTrav_Music(Line * line,dd_bool,void *,void * context2,mobj_t *)1828 int XLTrav_Music(Line *line, dd_bool /*ceiling*/, void * /*context*/,
1829 void *context2, mobj_t * /*activator*/)
1830 {
1831 LOG_AS("XLTrav_Music");
1832
1833 linetype_t *info = static_cast<linetype_t *>(context2);
1834
1835 int song = 0;
1836 int temp = 0;
1837
1838 if(info->iparm[2] == LREF_NONE)
1839 { // The old format, use ip0 explicitly.
1840 song = info->iparm[0];
1841 }
1842 else // We might possibly have a data reference to evaluate.
1843 {
1844 if(info->iparm[2] == LREF_NONE) // (ip0) will be used to determine next map.
1845 {
1846 song = info->iparm[0];
1847 }
1848 else if(line)
1849 {
1850 // Evaluate ip0 for a data reference.
1851 temp = XL_ValidateLineRef(line, info->iparm[0], context2, "Music ID");
1852 if(!temp)
1853 {
1854 LOG_MAP_MSG_XGDEVONLY("Reference data not valid. Song not changed");
1855 }
1856 else
1857 {
1858 song = temp;
1859 }
1860 }
1861 }
1862
1863 //// @todo Add code to validate song id here.
1864
1865 if(song)
1866 {
1867 LOG_MAP_MSG_XGDEVONLY2("Play Music ID (%i)%s",
1868 song << (info->iparm[1]? " looped" : ""));
1869 S_StartMusicNum(song, info->iparm[1]);
1870 }
1871
1872 return false; // Only do this once!
1873 }
1874
XLTrav_LineTeleport(Line * newLine,dd_bool,void * context,void * context2,mobj_t * mobj)1875 int XLTrav_LineTeleport(Line *newLine, dd_bool /*ceiling*/, void *context,
1876 void *context2, mobj_t *mobj)
1877 {
1878 // Maximum units to move object to avoid hiccups.
1879 #define FUDGEFACTOR 10
1880
1881 LOG_AS("XLTrav_LineTeleport");
1882
1883 int fudge = FUDGEFACTOR;
1884 int side = 0, stepDown;
1885 uint an;
1886 mobj_t *flash;
1887 Line *line = (Line *) context;
1888 linetype_t *info = (linetype_t *) context2;
1889 Vertex /* *newV1,*/ *newV2, *oldV1/*, *oldV2*/;
1890 Sector *newFrontSec, *newBackSec;
1891 coord_t newPos[3], pos, s, c;
1892 coord_t oldLineDelta[2], newLineDelta[2];
1893 angle_t angle;
1894
1895 // Don't teleport things marked noteleport!
1896 if(mobj->flags2 & MF2_NOTELEPORT)
1897 {
1898 LOG_MAP_MSG_XGDEVONLY("Activator can't be teleported (THING is unteleportable)");
1899 return false; // No point continuing...
1900 }
1901
1902 if(!line)
1903 return true; // Continue iteration.
1904
1905 // We shouldn't be trying to teleport to the same line
1906 if(newLine == line)
1907 {
1908 LOG_MAP_MSG_XGDEVONLY("Target == Origin. Continuing search...");
1909 return true; // Keep looking
1910 }
1911
1912 // Retrieve a few properties to make this look neater.
1913 oldV1 = (Vertex *)P_GetPtrp(line, DMU_VERTEX0);
1914 //oldV2 = (Vertex *)P_GetPtrp(line, DMU_VERTEX1);
1915 P_GetDoublepv(line, DMU_DXY, oldLineDelta);
1916
1917 //newV1 = (Vertex *)P_GetPtrp(newLine, DMU_VERTEX0);
1918 newV2 = (Vertex *)P_GetPtrp(newLine, DMU_VERTEX1);
1919 P_GetDoublepv(newLine, DMU_DXY, newLineDelta);
1920 newFrontSec = (Sector *)P_GetPtrp(newLine, DMU_FRONT_SECTOR);
1921 newBackSec = (Sector *)P_GetPtrp(newLine, DMU_BACK_SECTOR);
1922
1923 // i2: 1 = Spawn Fog
1924 // i3: Sound = Sound to play
1925 // i4: 1 = reversed
1926 // i5: 1 = always telestomp
1927
1928 LOG_MAP_MSG_XGDEVONLY2("%s, %s, %s",
1929 ( info->iparm[2]? "Spawn Flash" : "No Flash")
1930 << (info->iparm[3]? "Play Sound" : "Silent")
1931 << (info->iparm[4]? "Reversed" : "Normal."));
1932
1933 // Spawn flash at the old position?
1934 if(info->iparm[2])
1935 {
1936 if((flash = P_SpawnMobj(MT_TFOG, mobj->origin, mobj->angle + ANG180, 0)))
1937 {
1938 // Play a sound?
1939 if(info->iparm[3])
1940 S_StartSound(info->iparm[3], flash);
1941 }
1942 }
1943
1944 // Get the thing's position along the source line
1945 if(fabs(oldLineDelta[0]) > fabs(oldLineDelta[1]))
1946 pos = (mobj->origin[VX] - P_GetDoublep(oldV1, DMU_X)) / oldLineDelta[0];
1947 else
1948 pos = (mobj->origin[VY] - P_GetDoublep(oldV1, DMU_Y)) / oldLineDelta[1];
1949
1950 // Get the angle between the two lines, for rotating orientation and
1951 // momentum. Rotate 180 degrees, and flip the position across the exit
1952 // line, if reversed.
1953 angle = (info->iparm[4] ? pos = 1 - pos, 0 : ANG180) +
1954 M_PointXYToAngle2(0, 0, newLineDelta[0], newLineDelta[1]) -
1955 M_PointXYToAngle2(0, 0, oldLineDelta[0], oldLineDelta[1]);
1956
1957 // Interpolate position across the exit line.
1958 newPos[VX] = P_GetDoublep(newV2, DMU_X) - (pos * newLineDelta[0]);
1959 newPos[VY] = P_GetDoublep(newV2, DMU_Y) - (pos * newLineDelta[1]);
1960
1961 // Sine, cosine of angle adjustment
1962 s = FIX2FLT(finesine[angle >> ANGLETOFINESHIFT]);
1963 c = FIX2FLT(finecosine[angle >> ANGLETOFINESHIFT]);
1964
1965 // Whether walking towards first side of exit line steps down
1966 if(P_GetDoublep(newFrontSec, DMU_FLOOR_HEIGHT) <
1967 P_GetDoublep(newBackSec, DMU_FLOOR_HEIGHT))
1968 stepDown = true;
1969 else
1970 stepDown = false;
1971
1972 // Height of thing above ground.
1973 newPos[VZ] = mobj->origin[VZ] - mobj->floorZ;
1974
1975 /**
1976 * Side to exit the line on positionally.
1977 *
1978 * @note
1979 * This flag concerns exit position, not momentum. Due to potential for
1980 * round-off error, the thing can land on either the left or the right
1981 * side of the exit line, and steps must be taken to make sure it
1982 * does not end up on the wrong side.
1983 *
1984 * Exit momentum is always towards side 1 in a reversed teleporter, and
1985 * always towards side 0 otherwise.
1986 *
1987 * Exiting positionally on side 1 is always safe, as far as avoiding
1988 * oscillations and stuck-in-wall problems, but may not be optimum for
1989 * non-reversed teleporters.
1990 *
1991 * Exiting on side 0 can cause oscillations if momentum is towards side
1992 * 1, as it is with reversed teleporters.
1993 *
1994 * Exiting on side 1 slightly improves player viewing when going down a
1995 * step on a non-reversed teleporter.
1996 */
1997
1998 if(!info->iparm[4] || (mobj->player && stepDown))
1999 side = 1;
2000
2001 // Make sure we are on correct side of exit line.
2002 while((Line_PointOnSide(newLine, newPos) < 0) != side && --fudge >= 0)
2003 {
2004 if(fabs(newLineDelta[0]) > fabs(newLineDelta[1]))
2005 newPos[VY] -= FIX2FLT(((newLineDelta[0] < 0) != side)? -1 : 1);
2006 else
2007 newPos[VX] += FIX2FLT(((newLineDelta[1] < 0) != side)? -1 : 1);
2008 }
2009
2010 // Do the Teleport
2011 if(!P_TeleportMove(mobj, newPos[VX], newPos[VY], (info->iparm[5] > 0? true : false)))
2012 {
2013 LOG_MAP_MSG_XGDEVONLY("Something went horribly wrong... aborting.");
2014 return false;
2015 }
2016
2017 // Adjust z position to be same height above ground as before. Ground
2018 // level at the exit is measured as the higher of the two floor heights
2019 // at the exit line.
2020 if(stepDown)
2021 mobj->origin[VZ] = newPos[VZ] + P_GetDoublep(newFrontSec, DMU_FLOOR_HEIGHT);
2022 else
2023 mobj->origin[VZ] = newPos[VZ] + P_GetDoublep(newBackSec, DMU_FLOOR_HEIGHT);
2024
2025 // Rotate mobj's orientation according to difference in line angles.
2026 mobj->angle += angle;
2027
2028 // Update momentum of mobj crossing teleporter line?
2029 newPos[VX] = mobj->mom[MX];
2030 newPos[VY] = mobj->mom[MY];
2031
2032 // Rotate mobj's momentum to come out of exit just like it entered.
2033 mobj->mom[MX] = (newPos[VX] * c) - (newPos[VY] * s);
2034 mobj->mom[MY] = (newPos[VY] * c) + (newPos[VX] * s);
2035
2036 // Feet clipped?
2037 if(mobj->flags2 & MF2_FLOORCLIP)
2038 {
2039 mobj->floorClip = 0;
2040
2041 if(FEQUAL(mobj->origin[VZ], P_GetDoublep(Mobj_Sector(mobj), DMU_FLOOR_HEIGHT)))
2042 {
2043 terraintype_t const *tt = P_MobjFloorTerrain(mobj);
2044 if(tt->flags & TTF_FLOORCLIP)
2045 {
2046 mobj->floorClip = 10;
2047 }
2048 }
2049 }
2050
2051 // Spawn flash at the new position?
2052 if (!info->iparm[2])
2053 {
2054 an = mobj->angle >> ANGLETOFINESHIFT;
2055 if((flash = P_SpawnMobjXYZ(MT_TFOG,
2056 mobj->origin[VX] + 24 * FIX2FLT(finecosine[an]),
2057 mobj->origin[VY] + 24 * FIX2FLT(finesine[an]),
2058 mobj->origin[VZ], mobj->angle + ANG180, 0)))
2059 {
2060 // Play a sound?
2061 if(info->iparm[3])
2062 S_StartSound(info->iparm[3], flash);
2063 }
2064 }
2065
2066 // Adjust the player's view, incase there has been a height change
2067 if(mobj->player)
2068 {
2069 mobj->player->viewZ = mobj->origin[VZ] + mobj->player->viewHeight;
2070 mobj->dPlayer->flags |= DDPF_FIXANGLES | DDPF_FIXORIGIN | DDPF_FIXMOM;
2071 }
2072
2073 return false; // Do this only once!
2074
2075 #undef FUDGEFACTOR
2076 }
2077
XLTrav_LeaveMap(Line * line,dd_bool,void *,void * context2,mobj_t *)2078 int XLTrav_LeaveMap(Line *line, dd_bool /*ceiling*/, void * /*context*/, void *context2,
2079 mobj_t * /*activator*/)
2080 {
2081 LOG_AS("XLTrav_LeaveMap");
2082
2083 linetype_t *info = static_cast<linetype_t *>(context2);
2084
2085 // Is this a secret exit?
2086 if(info->iparm[0] > 0)
2087 {
2088 G_SetGameActionMapCompleted(gfw_Session()->mapUriForNamedExit("secret"), 0, true);
2089 return false;
2090 }
2091
2092 de::Uri newMapUri;
2093 if(info->iparm[1] == LREF_NONE)
2094 {
2095 // (ip3) will be used to determine next map (1-based).
2096 if(info->iparm[3])
2097 {
2098 newMapUri = G_ComposeMapUri(gfw_Session()->episodeId().toInt() - 1, info->iparm[3] - 1);
2099 LOG_MAP_MSG_XGDEVONLY2("Next map set to \"%s\"", newMapUri);
2100 }
2101 }
2102 // We might possibly have a data reference to evaluate.
2103 else if(line)
2104 {
2105 int const oldMapNumber = XL_ValidateLineRef(line, info->iparm[3], context2, "Map Number");
2106 if(oldMapNumber > 0)
2107 {
2108 newMapUri = G_ComposeMapUri(gfw_Session()->episodeId().toInt() - 1, oldMapNumber - 1);
2109 }
2110 }
2111
2112 if(newMapUri.isEmpty())
2113 {
2114 newMapUri = gfw_Session()->mapUriForNamedExit("next");
2115 LOG_MAP_MSG_XGDEVONLY("Next map set to default for the 'next' exit");
2116 }
2117
2118 // Check that the map truly exists.
2119 if(!P_MapExists(newMapUri.compose().toUtf8().constData()))
2120 {
2121 // Backward compatibility dictates that invalid refs be interpreted to mean the start map
2122 // of the current episode (which is known to always exist).
2123 newMapUri = de::makeUri(gfw_Session()->episodeDef()->gets("startMap"));
2124 }
2125
2126 G_SetGameActionMapCompleted(newMapUri);
2127 return false; // Only do this once!
2128 }
2129
XLTrav_DisableLine(Line * line,dd_bool,void * context,void *,mobj_t *)2130 int XLTrav_DisableLine(Line *line, dd_bool /*ceiling*/, void *context,
2131 void * /*context2*/, mobj_t * /*activator*/)
2132 {
2133 if(line)
2134 {
2135 xline_t *xline = P_ToXLine(line);
2136
2137 if(xline->xg)
2138 {
2139 xline_t *origLine = P_ToXLine((Line *) context);
2140
2141 xline->xg->disabled = origLine->xg->active;
2142 }
2143 }
2144
2145 return true; // Keep looking...
2146 }
2147
XLTrav_EnableLine(Line * line,dd_bool,void * context,void *,mobj_t *)2148 int XLTrav_EnableLine(Line *line, dd_bool /*ceiling*/, void *context,
2149 void * /*context2*/, mobj_t * /*activator*/)
2150 {
2151 if(line)
2152 {
2153 xline_t *xline = P_ToXLine(line);
2154 if(xline->xg)
2155 {
2156 xline_t *origLine = P_ToXLine((Line*) context);
2157
2158 xline->xg->disabled = !origLine->xg->active;
2159 }
2160 }
2161
2162 return true; // Keep looking...
2163 }
2164
2165 /**
2166 * Checks if the given lines are active or inactive.
2167 *
2168 * @return @c true if all are in the specified state.
2169 */
XL_CheckLineStatus(Line * line,int reftype,int ref,int active,mobj_t * activator)2170 dd_bool XL_CheckLineStatus(Line *line, int reftype, int ref, int active,
2171 mobj_t *activator)
2172 {
2173 return XL_TraverseLines(line, reftype, ref, &active, 0, activator, XLTrav_CheckLine);
2174 }
2175
XL_CheckMobjGone(thinker_t * th,void * context)2176 int XL_CheckMobjGone(thinker_t *th, void *context)
2177 {
2178 LOG_AS("XL_CheckMobjGone");
2179
2180 mobj_t *mo = (mobj_t *) th;
2181 int thingtype = *static_cast<int *>(context);
2182
2183 if(mo->type == thingtype && mo->health > 0)
2184 {
2185 // Not dead.
2186 LOG_MAP_MSG_XGDEVONLY2("Thing type %i: Found mo id=%i, health=%i, pos=%s",
2187 thingtype << mo->thinker.id << mo->health << Vector4d(mo->origin).asText());
2188 return true; // Stop iteration.
2189 }
2190
2191 return false; // Continue iteration.
2192 }
2193
XL_SwapSwitchTextures(Line * line,int snum)2194 void XL_SwapSwitchTextures(Line *line, int snum)
2195 {
2196 LOG_AS("XL_SwapSwitchTextures");
2197
2198 if(line)
2199 {
2200 Side *side = (Side *)P_GetPtrp(line, snum? DMU_BACK : DMU_FRONT);
2201
2202 if(side && P_ToggleSwitch(side, SFX_NONE, true, 0))
2203 {
2204 LOG_MAP_MSG_XGDEVONLY2("Line %i, side %i",
2205 P_ToIndex(line) << P_ToIndex(side));
2206 }
2207 }
2208 }
2209
2210 /**
2211 * Changes material of the given line.
2212 */
XL_ChangeMaterial(Line * line,int sidenum,int section,world_Material * mat,blendmode_t blendmode,Vector4f const & tintColor,int flags)2213 void XL_ChangeMaterial(Line *line, int sidenum, int section, world_Material *mat,
2214 blendmode_t blendmode, Vector4f const &tintColor, int flags)
2215 {
2216 Side *side = (Side *)P_GetPtrp(line, sidenum? DMU_BACK:DMU_FRONT);
2217 if(!side) return;
2218
2219 LOG_MAP_MSG_XGDEVONLY2("Line:%i side:%i section:%i material:%i tintColor:%s blendmode:%i",
2220 P_ToIndex(line) << sidenum << section << P_ToIndex(mat) << tintColor.asText() << blendmode);
2221
2222 // Which wall section are we working on?
2223 if(section == LWS_MID)
2224 {
2225 // Are we removing the middle texture?
2226 if(mat == (world_Material *) -1)
2227 {
2228 P_SetPtrp(side, DMU_MIDDLE_MATERIAL, NULL);
2229 }
2230 else if(mat)
2231 {
2232 P_SetPtrp(side, DMU_MIDDLE_MATERIAL, mat);
2233 }
2234
2235 // Are we changing the blendmode?
2236 if(blendmode)
2237 {
2238 P_SetIntp(side, DMU_MIDDLE_BLENDMODE, blendmode);
2239 }
2240
2241 // Are we changing the surface color?
2242 for(int i = 0; i < 4; ++i)
2243 {
2244 if(!de::fequal(tintColor[i], 0))
2245 P_SetFloatp(side, TO_DMU_MIDDLE_COLOR(i), tintColor[i]);
2246 }
2247 }
2248 else if(section == LWS_UPPER)
2249 {
2250 if(mat)
2251 {
2252 P_SetPtrp(side, DMU_TOP_MATERIAL, mat);
2253 }
2254
2255 for(int i = 0; i < 3; ++i)
2256 {
2257 if(!de::fequal(tintColor[i], 0))
2258 P_SetFloatp(side, TO_DMU_TOP_COLOR(i), tintColor[i]);
2259 }
2260 }
2261 else if(section == LWS_LOWER)
2262 {
2263 if(mat)
2264 {
2265 P_SetPtrp(side, DMU_BOTTOM_MATERIAL, mat);
2266 }
2267
2268 for(int i = 0; i < 3; ++i)
2269 {
2270 if(!de::fequal(tintColor[i], 0))
2271 P_SetFloatp(side, TO_DMU_BOTTOM_COLOR(i), tintColor[i]);
2272 }
2273 }
2274
2275 // Adjust the side's flags
2276 P_SetIntp(side, DMU_FLAGS, P_GetIntp(side, DMU_FLAGS) | flags);
2277 }
2278
XL_Message(mobj_t * act,char * msg,dd_bool global)2279 void XL_Message(mobj_t *act, char *msg, dd_bool global)
2280 {
2281 LOG_AS("XL_Message");
2282
2283 player_t *pl;
2284 int i;
2285
2286 if(!msg || !msg[0]) return;
2287
2288 if(global)
2289 {
2290 LOG_MAP_MSG_XGDEVONLY2("GLOBAL '%s'", msg);
2291 // Send to all players in the game.
2292 for(i = 0; i < MAXPLAYERS; ++i)
2293 {
2294 if(players[i].plr->inGame)
2295 P_SetMessage(&players[i], msg);
2296 }
2297 return;
2298 }
2299
2300 if(act->player)
2301 {
2302 pl = act->player;
2303 }
2304 else if((act->flags & MF_MISSILE) && act->target && act->target->player)
2305 {
2306 // Originator of the missile.
2307 pl = act->target->player;
2308 }
2309 else
2310 {
2311 // We don't know whom to send the message.
2312 LOG_MAP_MSG_XGDEVONLY2("'%s'\nNO DESTINATION, MESSAGE DISCARDED", msg);
2313 return;
2314 }
2315 P_SetMessage(pl, msg);
2316 }
2317
XL_ActivateLine(dd_bool activating,linetype_t * info,Line * line,int sidenum,mobj_t * activator,int evtype)2318 void XL_ActivateLine(dd_bool activating, linetype_t *info, Line *line, int sidenum,
2319 mobj_t *activator, int evtype)
2320 {
2321 DENG2_ASSERT(line);
2322 LOG_AS("XL_ActivateLine");
2323
2324 xline_t *xline = P_ToXLine(line);
2325 if(!xline) return; // huh?
2326
2327 LOG_MAP_MSG_XGDEVONLY2("%s line %i, side %i, type %i",
2328 (activating? "Activating" : "Deactivating") << P_ToIndex(line)
2329 << sidenum << xline->special);
2330
2331 DENG2_ASSERT(xline->xg);
2332 xgline_t &xgline = *xline->xg;
2333 if(xgline.disabled)
2334 {
2335 LOG_MAP_MSG_XGDEVONLY("LINE DISABLED, ABORTING");
2336 return; // The line is disabled.
2337 }
2338
2339 if((activating && xgline.active) || (!activating && !xgline.active))
2340 {
2341 LOG_MAP_MSG_XGDEVONLY2("Line is ALREADY %s, ABORTING", (activating ? "ACTIVE" : "INACTIVE"));
2342 return; // Do nothing (can't activate if already active!).
2343 }
2344
2345 // Activation should happen on the front side.
2346 // Let the line know who's activating it.
2347 xgline.activator = activator;
2348
2349 // Process (de)activation chains. Chains always pass as an activation
2350 // method, but the other requirements of the chained type must be met.
2351 if(activating && info->actChain)
2352 {
2353 LOG_MAP_MSG_XGDEVONLY2("Line has Act Chain (type %i) - It will be processed first...", info->actChain);
2354 XL_LineEvent(XLE_CHAIN, info->actChain, line, sidenum, activator);
2355 }
2356 else if(!activating && info->deactChain)
2357 {
2358 LOG_MAP_MSG_XGDEVONLY2("Line has Deact Chain (type %i) - It will be processed first...", info->deactChain);
2359 XL_LineEvent(XLE_CHAIN, info->deactChain, line, sidenum, activator);
2360 }
2361
2362 // Automatically swap any SW* textures.
2363 if(xgline.active != activating)
2364 {
2365 XL_SwapSwitchTextures(line, sidenum);
2366 }
2367
2368 // Change the state of the line.
2369 xgline.active = activating;
2370 xgline.timer = 0; // Reset timer.
2371
2372 // Activate lines with a matching tag with Group Activation.
2373 if( (activating && (info->flags2 & LTF2_GROUP_ACT)) ||
2374 (!activating && (info->flags2 & LTF2_GROUP_DEACT)))
2375 {
2376 XL_TraverseLines(line, LREF_LINE_TAGGED, true, &activating, 0, activator,
2377 XLTrav_SmartActivate);
2378 }
2379
2380 // For lines flagged Multiple, quick-(de)activate other lines that have
2381 // the same line tag.
2382 if(info->flags2 & LTF2_MULTIPLE)
2383 {
2384 XL_TraverseLines(line, LREF_LINE_TAGGED, true, &activating, 0, activator,
2385 XLTrav_QuickActivate);
2386 }
2387
2388 // Should we apply the function of the line? Functions are defined by
2389 // the class of the line type.
2390 if( (activating && (info->flags2 & LTF2_WHEN_ACTIVATED)) ||
2391 (!activating && (info->flags2 & LTF2_WHEN_DEACTIVATED)))
2392 {
2393 if(!(info->flags2 & LTF2_WHEN_LAST) || info->actCount == 1)
2394 {
2395 XL_DoFunction(info, line, sidenum, activator, evtype);
2396 }
2397 else
2398 {
2399 LOG_MAP_MSG_XGDEVONLY2("Line %i FUNCTION TEST FAILED", P_ToIndex(line));
2400 }
2401 }
2402 else if(activating)
2403 {
2404 LOG_MAP_MSG_XGDEVONLY2("Line %i has no activation function", P_ToIndex(line));
2405 }
2406 else
2407 {
2408 LOG_MAP_MSG_XGDEVONLY2("Line %i has no deactivation function", P_ToIndex(line));
2409 }
2410
2411 // Now do any secondary actions that should happen AFTER
2412 // the function of the line (regardless if one was applied or not)
2413 if(activating)
2414 {
2415 XL_Message(activator, info->actMsg,
2416 (info->flags2 & LTF2_GLOBAL_A_MSG) != 0);
2417
2418 if(info->actSound)
2419 {
2420 S_SectorSound((Sector *)P_GetPtrp(line, DMU_FRONT_SECTOR), info->actSound);
2421 }
2422
2423 // Change the texture of the line if asked to.
2424 if(info->wallSection && info->actMaterial != NOMATERIALID)
2425 {
2426 XL_ChangeMaterial(line, sidenum, info->wallSection,
2427 (world_Material *)P_ToPtr(DMU_MATERIAL, info->actMaterial));
2428 }
2429
2430 // Change the class of the line if asked to
2431 if(info->actLineType)
2432 {
2433 XL_SetLineType(line, info->actLineType);
2434 }
2435 }
2436 else
2437 {
2438 XL_Message(activator, info->deactMsg, (info->flags2 & LTF2_GLOBAL_D_MSG) != 0);
2439
2440 if(info->deactSound)
2441 {
2442 S_SectorSound((Sector *)P_GetPtrp(line, DMU_FRONT_SECTOR), info->deactSound);
2443 }
2444
2445 // Change the texture of the line if asked to.
2446 if(info->wallSection && info->deactMaterial != NOMATERIALID)
2447 {
2448 XL_ChangeMaterial(line, sidenum, info->wallSection,
2449 (world_Material *)P_ToPtr(DMU_MATERIAL, info->deactMaterial));
2450 }
2451
2452 // Change the class of the line if asked to.
2453 if(info->deactLineType)
2454 {
2455 XL_SetLineType(line, info->deactLineType);
2456 }
2457 }
2458 }
2459
2460 /**
2461 * XL_CheckKeys
2462 */
XL_CheckKeys(mobj_t * mo,int flags2,dd_bool doMsg,dd_bool doSfx)2463 dd_bool XL_CheckKeys(mobj_t* mo, int flags2, dd_bool doMsg, dd_bool doSfx)
2464 {
2465 player_t* act = mo->player;
2466 #if __JDOOM__ || __JDOOM64__
2467 int num = 6;
2468 int* keys = (int *) act->keys;
2469 int badsound = SFX_OOF;
2470 #elif __JHERETIC__
2471 int num = 3;
2472 dd_bool* keys = act->keys;
2473 int badsound = SFX_PLROOF;
2474 #elif __JSTRIFE__
2475 //// @todo FIXME!!!
2476 int num = 3;
2477 int* keys = (int *) act->keys;
2478 int badsound = SFX_NONE;
2479 #endif
2480 int i;
2481
2482 for(i = 0; i < num; ++i)
2483 {
2484 if((flags2 & LTF2_KEY(i)) && !keys[i])
2485 { // This key is missing!
2486
2487 // Show a message?
2488 if(doMsg)
2489 {
2490 sprintf(msgbuf, "YOU NEED A %s.", GET_TXT(TXT_KEY1 + i));
2491 XL_Message(mo, msgbuf, false);
2492 }
2493
2494 // Play a sound?
2495 if(doSfx)
2496 S_ConsoleSound(badsound, mo, act - players);
2497
2498 return false;
2499 }
2500 }
2501
2502 return true;
2503 }
2504
2505 /**
2506 * Decides if the event leads to (de)activation.
2507 * Line must be extended.
2508 * Most conditions use AND (act method, game mode and difficult use OR).
2509 *
2510 * @return @c true, iff the event is processed.
2511 */
XL_LineEvent(int evtype,int linetype,Line * line,int sidenum,void * data)2512 int XL_LineEvent(int evtype, int linetype, Line* line, int sidenum,
2513 void* data)
2514 {
2515 LOG_AS("XL_LineEvent");
2516
2517 int i;
2518 xline_t* xline;
2519 xgline_t* xg;
2520 linetype_t* info;
2521 dd_bool active;
2522 mobj_t* activator_thing = (mobj_t *) data;
2523 player_t* activator = 0;
2524 dd_bool anyTrigger = false;
2525
2526 // Clients rely on the server, they don't do XG themselves.
2527 if(IS_CLIENT)
2528 return false;
2529
2530 xline = P_ToXLine(line);
2531 xg = xline->xg;
2532 info = &xg->info;
2533 active = xg->active;
2534
2535 if(activator_thing)
2536 activator = activator_thing->player;
2537
2538 #if __JDOOM__ || __JDOOM64__
2539 // BOOM intergration
2540 if((xline->flags & ML_ALLTRIGGER) && !(info->flags2 & LTF2_OVERRIDE_ANY))
2541 anyTrigger = true;
2542 #endif
2543
2544 LOG_MAP_MSG_XGDEVONLY2("%s line %i, side %i (chained type %i)%s",
2545 EVTYPESTR(evtype) << P_ToIndex(line) << sidenum << linetype
2546 << (anyTrigger? " ANY Trigger" : ""));
2547
2548 if(xg->disabled)
2549 {
2550 LOG_MAP_MSG_XGDEVONLY("LINE IS DISABLED, ABORTING EVENT");
2551 return false; // The line is disabled.
2552 }
2553
2554 // This is a chained event.
2555 if(linetype)
2556 {
2557 if(!XL_GetType(linetype))
2558 return false;
2559 info = &typebuffer;
2560 }
2561
2562 // Process chained event first. It takes precedence.
2563 if(info->evChain)
2564 {
2565 if(XL_LineEvent(evtype, info->evChain, line, sidenum, data))
2566 {
2567 LOG_MAP_MSG_XGDEVONLY2("Event %s, line %i, side %i OVERRIDDEN BY EVENT CHAIN %i",
2568 EVTYPESTR(evtype) << P_ToIndex(line) << sidenum << info->evChain);
2569 return true;
2570 }
2571 }
2572
2573 // Check restrictions and conditions that will prevent processing
2574 // the event.
2575 if((active && info->actType == LTACT_COUNTED_OFF) ||
2576 (!active && info->actType == LTACT_COUNTED_ON))
2577 {
2578 // Can't be processed at this time.
2579 LOG_MAP_MSG_XGDEVONLY2("Line %i: Active=%i, type=%i ABORTING EVENT",
2580 P_ToIndex(line) << active << info->actType);
2581 return false;
2582 }
2583
2584 // Check the type of the event vs. the requirements of the line.
2585 if(evtype == XLE_CHAIN || evtype == XLE_FUNC)
2586 goto type_passes;
2587 if(evtype == XLE_USE &&
2588 ((((info->flags & LTF_PLAYER_USE_A) && activator && !active) ||
2589 ((info->flags & LTF_OTHER_USE_A) && !activator && !active) ||
2590 ((info->flags & LTF_PLAYER_USE_D) && activator && active) ||
2591 ((info->flags & LTF_OTHER_USE_D) && !activator && active)) || anyTrigger))
2592 goto type_passes;
2593 if(evtype == XLE_SHOOT &&
2594 ((((info->flags & LTF_PLAYER_SHOOT_A) && activator && !active) ||
2595 ((info->flags & LTF_OTHER_SHOOT_A) && !activator && !active) ||
2596 ((info->flags & LTF_PLAYER_SHOOT_D) && activator && active) ||
2597 ((info->flags & LTF_OTHER_SHOOT_D) && !activator && active)) || anyTrigger))
2598 goto type_passes;
2599 if(evtype == XLE_CROSS &&
2600 ((((info->flags & LTF_PLAYER_CROSS_A) && activator && !active) ||
2601 ((info->flags & LTF_MONSTER_CROSS_A) &&
2602 (activator_thing && (activator_thing->flags & MF_COUNTKILL)) && !active) ||
2603 ((info->flags & LTF_MISSILE_CROSS_A) &&
2604 (activator_thing && (activator_thing->flags & MF_MISSILE)) && !active) ||
2605 ((info->flags & LTF_ANY_CROSS_A) && !active) ||
2606 ((info->flags & LTF_PLAYER_CROSS_D) && activator && active) ||
2607 ((info->flags & LTF_MONSTER_CROSS_D) &&
2608 (activator_thing && (activator_thing->flags & MF_COUNTKILL)) && active) ||
2609 ((info->flags & LTF_MISSILE_CROSS_D) &&
2610 (activator_thing && (activator_thing->flags & MF_MISSILE)) && active) ||
2611 ((info->flags & LTF_ANY_CROSS_D) && active)) || anyTrigger))
2612 goto type_passes;
2613 if(evtype == XLE_HIT &&
2614 ((((info->flags & LTF_PLAYER_HIT_A) && activator && !active) ||
2615 ((info->flags & LTF_OTHER_HIT_A) && !activator && !active) ||
2616 ((info->flags & LTF_MONSTER_HIT_A) &&
2617 (activator_thing && (activator_thing->flags & MF_COUNTKILL)) && !active) ||
2618 ((info->flags & LTF_MISSILE_HIT_A) &&
2619 (activator_thing && (activator_thing->flags & MF_MISSILE)) && !active) ||
2620 ((info->flags & LTF_ANY_HIT_A) && !active) ||
2621 ((info->flags & LTF_PLAYER_HIT_D) && activator && active) ||
2622 ((info->flags & LTF_OTHER_HIT_D) && !activator && active) ||
2623 ((info->flags & LTF_MONSTER_HIT_D) &&
2624 (activator_thing && (activator_thing->flags & MF_COUNTKILL)) && active) ||
2625 ((info->flags & LTF_MISSILE_HIT_D) &&
2626 (activator_thing && (activator_thing->flags & MF_MISSILE)) && active) ||
2627 ((info->flags & LTF_ANY_HIT_D) && active)) || anyTrigger))
2628 goto type_passes;
2629 if(evtype == XLE_TICKER &&
2630 (((info->flags & LTF_TICKER_A) && !active) ||
2631 ((info->flags & LTF_TICKER_D) && active)))
2632 goto type_passes;
2633
2634 // Type doesn't pass, sorry.
2635 LOG_MAP_MSG_XGDEVONLY2("Line %i: ACT REQUIREMENTS NOT FULFILLED, ABORTING EVENT", P_ToIndex(line));
2636 return false;
2637
2638 type_passes:
2639
2640 if(info->flags & LTF_NO_OTHER_USE_SECRET)
2641 {
2642 // Non-players can't use this line if line is flagged secret.
2643 if(evtype == XLE_USE && !activator && (xline->flags & ML_SECRET))
2644 {
2645 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to no_other_use_secret", P_ToIndex(line));
2646 return false;
2647 }
2648 }
2649
2650 if(info->flags & LTF_MOBJ_GONE)
2651 {
2652 if(Thinker_Iterate(P_MobjThinker, XL_CheckMobjGone, &info->aparm[9]))
2653 return false;
2654 }
2655
2656 if(info->flags & LTF_ACTIVATOR_TYPE)
2657 {
2658 // Check the activator's type.
2659 if(!activator_thing || activator_thing->type != info->aparm[9])
2660 {
2661 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to activator type", P_ToIndex(line));
2662 return false;
2663 }
2664 }
2665
2666 if((evtype == XLE_USE || evtype == XLE_SHOOT || evtype == XLE_CROSS) &&
2667 !(info->flags2 & LTF2_TWOSIDED))
2668 {
2669 // Only allow (de)activation from the front side.
2670 if(sidenum != 0)
2671 {
2672 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to line side test", P_ToIndex(line));
2673 return false;
2674 }
2675 }
2676
2677 // Check counting.
2678 if(!info->actCount)
2679 {
2680 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to Count = 0", P_ToIndex(line));
2681 return false;
2682 }
2683
2684 // More requirements.
2685 if((info->flags2 & LTF2_HEALTH_ABOVE) && activator_thing &&
2686 activator_thing->health <= info->aparm[0])
2687 return false;
2688 if((info->flags2 & LTF2_HEALTH_BELOW) && activator_thing &&
2689 activator_thing->health >= info->aparm[1])
2690 return false;
2691 if((info->flags2 & LTF2_POWER_ABOVE) &&
2692 (!activator || activator->armorPoints <= info->aparm[2]))
2693 return false;
2694 if((info->flags2 & LTF2_POWER_BELOW) &&
2695 (!activator || activator->armorPoints >= info->aparm[3]))
2696 return false;
2697 if(info->flags2 & LTF2_LINE_ACTIVE)
2698 if(!XL_CheckLineStatus(line, info->aparm[4], info->aparm[5], true,
2699 activator_thing))
2700 {
2701 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to line_active test", P_ToIndex(line));
2702 return false;
2703 }
2704 if(info->flags2 & LTF2_LINE_INACTIVE)
2705 if(!XL_CheckLineStatus(line, info->aparm[6], info->aparm[7], false,
2706 activator_thing))
2707 {
2708 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to line_inactive test", P_ToIndex(line));
2709 return false;
2710 }
2711 // Check game mode.
2712 if (IS_NETGAME)
2713 {
2714 const int netFlags = info->flags2 & (LTF2_COOPERATIVE | LTF2_DEATHMATCH);
2715 if (!netFlags)
2716 {
2717 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to netgame mode", P_ToIndex(line));
2718 return false;
2719 }
2720 if (netFlags != (LTF2_COOPERATIVE | LTF2_DEATHMATCH))
2721 {
2722 // Need to check which game mode.
2723 const auto netType = gfw_Rule(deathmatch);
2724 if (((netFlags & LTF2_COOPERATIVE) && netType != 0) ||
2725 ((netFlags & LTF2_DEATHMATCH) && netType == 0))
2726 {
2727 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to non-matching deathmatch/coop flag", P_ToIndex(line));
2728 return false;
2729 }
2730 }
2731 }
2732 else
2733 {
2734 if(!(info->flags2 & LTF2_SINGLEPLAYER))
2735 {
2736 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to game mode (1p)", P_ToIndex(line));
2737 return false;
2738 }
2739 }
2740
2741 // Check skill level.
2742 // SM_NOTHINGS will be interpreted as SM_BABY.
2743 if(gfw_Rule(skill) < 1)
2744 i = 1;
2745 else if(gfw_Rule(skill) > 3)
2746 i = 4;
2747 else
2748 i = 1 << (gfw_Rule(skill) - 1);
2749
2750 if(!(info->flags2 & (i << LTF2_SKILL_SHIFT)))
2751 {
2752 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to skill level (%i)",
2753 P_ToIndex(line) << gfw_Rule(skill));
2754 return false;
2755 }
2756
2757 // Check activator color.
2758 if(info->flags2 & LTF2_COLOR)
2759 {
2760 if(!activator)
2761 return false;
2762 if(cfg.playerColor[activator - players] != info->aparm[8])
2763 {
2764 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to activator color (%i)",
2765 P_ToIndex(line) << cfg.playerColor[activator-players]);
2766 return false;
2767 }
2768 }
2769
2770 // Keys require that the activator is a player.
2771 if(info->
2772 flags2 & (LTF2_KEY1 | LTF2_KEY2 | LTF2_KEY3 | LTF2_KEY4 | LTF2_KEY5 |
2773 LTF2_KEY6))
2774 {
2775 // Check keys.
2776 if(!activator)
2777 {
2778 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to missing key (no activator)", P_ToIndex(line));
2779 return false;
2780 }
2781
2782 // Check that all the flagged keys are present.
2783 if(!XL_CheckKeys(activator_thing, info->flags2, true,
2784 (evtype == XLE_USE? true : false)))
2785 {
2786 LOG_MAP_MSG_XGDEVONLY2("Line %i: ABORTING EVENT due to missing key", P_ToIndex(line));
2787 return false; // Keys missing!
2788 }
2789 }
2790
2791 // All tests passed, use this event.
2792 if(info->actCount > 0 && evtype != XLE_CHAIN && evtype != XLE_FUNC)
2793 {
2794 // Decrement counter.
2795 info->actCount--;
2796
2797 LOG_MAP_MSG_XGDEVONLY2("Line %i: Decrementing counter, now %i",
2798 P_ToIndex(line) << info->actCount);
2799 }
2800
2801 XL_ActivateLine(!active, info, line, sidenum, activator_thing, evtype);
2802 return true;
2803 }
2804
2805 /**
2806 * @return @c true, if the event was processed.
2807 */
XL_CrossLine(Line * line,int sidenum,mobj_t * thing)2808 int XL_CrossLine(Line* line, int sidenum, mobj_t* thing)
2809 {
2810 if(!line || !P_ToXLine(line)->xg)
2811 return false;
2812
2813 return XL_LineEvent(XLE_CROSS, 0, line, sidenum, thing);
2814 }
2815
2816 /**
2817 * @return @c true, if the event was processed.
2818 */
XL_UseLine(Line * line,int sidenum,mobj_t * thing)2819 int XL_UseLine(Line* line, int sidenum, mobj_t* thing)
2820 {
2821 if(!line || !P_ToXLine(line)->xg)
2822 return false;
2823
2824 return XL_LineEvent(XLE_USE, 0, line, sidenum, thing);
2825 }
2826
2827 /**
2828 * @return @c true, if the event was processed.
2829 */
XL_ShootLine(Line * line,int sidenum,mobj_t * thing)2830 int XL_ShootLine(Line *line, int sidenum, mobj_t *thing)
2831 {
2832 if(!line || !P_ToXLine(line)->xg)
2833 return false;
2834
2835 return XL_LineEvent(XLE_SHOOT, 0, line, sidenum, thing);
2836 }
2837
XL_HitLine(Line * line,int sidenum,mobj_t * thing)2838 int XL_HitLine(Line *line, int sidenum, mobj_t *thing)
2839 {
2840 if(!line || !P_ToXLine(line)->xg)
2841 return false;
2842
2843 return XL_LineEvent(XLE_HIT, 0, line, sidenum, thing);
2844 }
2845
XL_DoChain(Line * line,int chain,dd_bool activating,mobj_t * actThing)2846 void XL_DoChain(Line *line, int chain, dd_bool activating, mobj_t *actThing)
2847 {
2848 LOG_AS("XL_DoChain");
2849
2850 // We'll use a dummy for the chain.
2851 Line *dummyLineDef = P_AllocDummyLine();
2852 xline_t *xdummyLineDef = P_ToXLine(dummyLineDef);
2853
2854 xdummyLineDef->xg = (xgline_t *)Z_Calloc(sizeof(xgline_t), PU_MAP, 0);
2855
2856 P_SetPtrp(dummyLineDef, DMU_FRONT_SECTOR, P_GetPtrp(line, DMU_FRONT_SECTOR));
2857 if(0 != P_GetPtrp(line, DMU_BACK))
2858 {
2859 P_SetPtrp(dummyLineDef, DMU_BACK_SECTOR, P_GetPtrp(line, DMU_BACK_SECTOR));
2860 }
2861
2862 LOG_MAP_MSG_XGDEVONLY2("Line %i, chained type %i", P_ToIndex(line) << chain);
2863 LOG_MAP_MSG_XGDEVONLY2("(dummy line will show up as %i)", P_ToIndex(dummyLineDef));
2864
2865 // Copy all properties to the dummies.
2866 P_CopyLine(dummyLineDef, line);
2867
2868 xdummyLineDef->xg->active = !activating;
2869
2870 // Make the chain event
2871 XL_LineEvent(XLE_CHAIN, chain, dummyLineDef, 0, actThing);
2872
2873 Z_Free(xdummyLineDef->xg);
2874 P_FreeDummyLine(dummyLineDef);
2875 }
2876
2877 /**
2878 * XG lines get to think.
2879 */
XL_Thinker(void * xlThinkerPtr)2880 void XL_Thinker(void *xlThinkerPtr)
2881 {
2882 DENG2_ASSERT(xlThinkerPtr);
2883 LOG_AS("XL_Thinker");
2884
2885 xlthinker_t *xl = static_cast<xlthinker_t *>(xlThinkerPtr);
2886 Line *line = xl->line;
2887
2888 // Clients rely on the server, they don't do XG themselves.
2889 if(IS_CLIENT) return;
2890
2891 if(!xl->line) return;
2892
2893 // Not an xline? Most perculiar...
2894 xline_t *xline = P_ToXLine(line);
2895 if(!xline) return;
2896
2897 // Not an extended line?
2898 xgline_t *xg = xline->xg;
2899 if(!xg) return;
2900
2901 // If disabled do nothing.
2902 if(xg->disabled) return;
2903
2904 linetype_t *info = &xg->info;
2905 float levtime = TIC2FLT(mapTime);
2906
2907 // Increment time.
2908 if(xg->timer >= 0)
2909 {
2910 xg->timer++;
2911 xg->tickerTimer++;
2912 }
2913
2914 // Activation by ticker.
2915 if((info->tickerEnd <= 0 ||
2916 (levtime >= info->tickerStart && levtime <= info->tickerEnd)) &&
2917 xg->tickerTimer > info->tickerInterval)
2918 {
2919 if(info->flags & LTF_TICKER)
2920 {
2921 xg->tickerTimer = 0;
2922 XL_LineEvent(XLE_TICKER, 0, line, 0, XG_DummyThing());
2923 }
2924
2925 // How about some forced functions?
2926 if((((info->flags2 & LTF2_WHEN_ACTIVE) && xg->active) ||
2927 ((info->flags2 & LTF2_WHEN_INACTIVE) && !xg->active)) &&
2928 (!(info->flags2 & LTF2_WHEN_LAST) || info->actCount == 1))
2929 {
2930 XL_DoFunction(info, line, 0, (mobj_t *)xg->activator, XLE_FORCED);
2931 }
2932 }
2933
2934 // Only process active chain sequences.
2935 if(xg->active && info->lineClass == LTC_CHAIN_SEQUENCE)
2936 {
2937 xg->chTimer -= TIC2FLT(1);
2938
2939 // idata = current pos
2940 // fdata = count down seconds
2941
2942 // i1..i19: line types
2943 // f0: interval randomness (100 means real interval can be 0%..200%).
2944 // f1..f19: intervals (seconds)
2945
2946 // If the counter goes to zero, it's time to execute the chain.
2947 if(xg->chTimer < 0)
2948 {
2949 LOG_MAP_MSG_XGDEVONLY2("Line %i, executing...", P_ToIndex(line));
2950
2951 // Are there any more chains?
2952 if(xg->chIdx < DDLT_MAX_PARAMS && info->iparm[xg->chIdx])
2953 {
2954 // Only send activation events.
2955 XL_DoChain(line, info->iparm[xg->chIdx], true, (mobj_t *)xg->activator);
2956
2957 // Advance to the next one.
2958 xg->chIdx++;
2959
2960 // Are we out of chains?
2961 if((xg->chIdx == DDLT_MAX_PARAMS || !info->iparm[xg->chIdx]) &&
2962 (info->iparm[0] & CHSF_LOOP))
2963 {
2964 // Loop back to beginning.
2965 xg->chIdx = 1;
2966 }
2967
2968 // If there are more chains, get the next interval.
2969 if(xg->chIdx < DDLT_MAX_PARAMS && info->iparm[xg->chIdx])
2970 {
2971 // Start counting it down.
2972 xg->chTimer =
2973 XG_RandomPercentFloat(info->fparm[xg->chIdx],
2974 info->fparm[0]);
2975 }
2976 }
2977 else if(info->iparm[0] & CHSF_DEACTIVATE_WHEN_DONE)
2978 {
2979 // The sequence has been completed.
2980 XL_ActivateLine(false, info, line, 0, (mobj_t *)xg->activator, XLE_CHAIN);
2981 }
2982 }
2983 }
2984
2985 // Check for automatical (de)activation.
2986 if(((info->actType == LTACT_COUNTED_OFF ||
2987 info->actType == LTACT_FLIP_COUNTED_OFF) && xg->active) ||
2988 ((info->actType == LTACT_COUNTED_ON ||
2989 info->actType == LTACT_FLIP_COUNTED_ON) && !xg->active))
2990 {
2991 if(info->actTime >= 0 && xg->timer > FLT2TIC(info->actTime))
2992 {
2993 LOG_MAP_MSG_XGDEVONLY2("Line %i, timed to go %s",
2994 P_ToIndex(line)
2995 << (xg->active ? "INACTIVE" : "ACTIVE"));
2996
2997 // Swap line state without any checks.
2998 XL_ActivateLine(!xg->active, info, line, 0, dummyThing, XLE_AUTO);
2999 }
3000 }
3001
3002 if(info->materialMoveSpeed)
3003 {
3004 // The texture should be moved. Calculate the offsets.
3005 coord_t current[2]; // The current offset.
3006 Side* side;
3007 float spd = info->materialMoveSpeed;
3008 coord_t offset[2];
3009 angle_t ang = ((angle_t) (ANGLE_MAX * (info->materialMoveAngle / 360))) >> ANGLETOFINESHIFT;
3010
3011 offset[VX] = -(FIX2FLT(finecosine[ang]) * spd);
3012 offset[VY] = FIX2FLT(finesine[ang]) * spd;
3013
3014 /**
3015 * Apply to both sides of the line.
3016 * These are group offsets. All surfaces on a given side are moved
3017 * using the same texmove speed/angle.
3018 *
3019 * @todo Implement per-surface texture movement also which would
3020 * be added to each independantly.
3021 */
3022
3023 // Front side.
3024 side = (Side *)P_GetPtrp(line, DMU_FRONT);
3025 if(side)
3026 {
3027 P_GetDoublepv(side, DMU_TOP_MATERIAL_OFFSET_XY, current);
3028 current[VX] += offset[VX];
3029 current[VY] += offset[VY];
3030 P_SetDoublepv(side, DMU_TOP_MATERIAL_OFFSET_XY, current);
3031
3032 P_GetDoublepv(side, DMU_MIDDLE_MATERIAL_OFFSET_XY, current);
3033 current[VX] += offset[VX];
3034 current[VY] += offset[VY];
3035 P_SetDoublepv(side, DMU_MIDDLE_MATERIAL_OFFSET_XY, current);
3036
3037 P_GetDoublepv(side, DMU_BOTTOM_MATERIAL_OFFSET_XY, current);
3038 current[VX] += offset[VX];
3039 current[VY] += offset[VY];
3040 P_SetDoublepv(side, DMU_BOTTOM_MATERIAL_OFFSET_XY, current);
3041 }
3042
3043 // Back side.
3044 side = (Side *)P_GetPtrp(line, DMU_BACK);
3045 if(side)
3046 {
3047 P_GetDoublepv(side, DMU_TOP_MATERIAL_OFFSET_XY, current);
3048 current[VX] += offset[VX];
3049 current[VY] += offset[VY];
3050 P_SetDoublepv(side, DMU_TOP_MATERIAL_OFFSET_XY, current);
3051
3052 P_GetDoublepv(side, DMU_MIDDLE_MATERIAL_OFFSET_XY, current);
3053 current[VX] += offset[VX];
3054 current[VY] += offset[VY];
3055 P_SetDoublepv(side, DMU_MIDDLE_MATERIAL_OFFSET_XY, current);
3056
3057 P_GetDoublepv(side, DMU_BOTTOM_MATERIAL_OFFSET_XY, current);
3058 current[VX] += offset[VX];
3059 current[VY] += offset[VY];
3060 P_SetDoublepv(side, DMU_BOTTOM_MATERIAL_OFFSET_XY, current);
3061 }
3062 }
3063 }
3064
3065 /**
3066 * During update, definitions are re-read, so the pointers need to be
3067 * updated. However, this is a bit messy operation, prone to errors.
3068 * Instead, we just disable XG...
3069 */
XL_Update(void)3070 void XL_Update(void)
3071 {
3072 int i;
3073 xline_t *xline;
3074
3075 // It's all PU_MAP memory, so we can just lose it.
3076 for(i = 0; i < numlines; ++i)
3077 {
3078 xline = P_GetXLine(i);
3079
3080 if(xline->xg)
3081 {
3082 xline->xg = NULL;
3083 xline->special = 0;
3084 }
3085 }
3086 }
3087
3088 #endif
3089