1 /**
2 * @file
3 * @brief Client side entity spawning from map entity string
4 */
5
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18 See the GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 */
24
25 #include "cl_spawn.h"
26 #include "../client.h"
27 #include "../cgame/cl_game.h"
28 #include "cl_particle.h"
29 #include "../../shared/parse.h"
30
31 /* position in the spawnflags */
32 #define MISC_MODEL_GLOW 9
33 #define SPAWNFLAG_NO_DAY 8
34
35 typedef struct localEntityParse_s {
36 char classname[MAX_VAR];
37 char target[MAX_VAR];
38 char targetname[MAX_VAR];
39 char tagname[MAX_VAR];
40 char anim[MAX_VAR];
41 char model[MAX_QPATH];
42 char particle[MAX_VAR];
43 char noise[MAX_QPATH];
44 vec3_t origin;
45 vec3_t angles;
46 vec3_t scale;
47 vec3_t color;
48 vec3_t ambientNightColor;
49 vec_t nightLight;
50 vec2_t nightSunAngles;
51 vec3_t nightSunColor;
52 vec3_t ambientDayColor;
53 vec_t dayLight;
54 vec2_t daySunAngles;
55 vec3_t daySunColor;
56 vec2_t wait;
57 int maxLevel;
58 int maxMultiplayerTeams;
59 int skin;
60 int frame;
61 int light;
62 int spawnflags;
63 float volume;
64 float attenuation;
65 float angle;
66 int maxteams;
67
68 /* not filled from entity string */
69 const char* entStringPos;
70 int entnum;
71 } localEntityParse_t;
72
73 static const value_t localEntityValues[] = {
74 {"skin", V_INT, offsetof(localEntityParse_t, skin), MEMBER_SIZEOF(localEntityParse_t, skin)},
75 {"maxteams", V_INT, offsetof(localEntityParse_t, maxteams), MEMBER_SIZEOF(localEntityParse_t, maxteams)},
76 {"spawnflags", V_INT, offsetof(localEntityParse_t, spawnflags), MEMBER_SIZEOF(localEntityParse_t, spawnflags)},
77 {"maxlevel", V_INT, offsetof(localEntityParse_t, maxLevel), MEMBER_SIZEOF(localEntityParse_t, maxLevel)},
78 {"attenuation", V_FLOAT, offsetof(localEntityParse_t, attenuation), MEMBER_SIZEOF(localEntityParse_t, attenuation)},
79 {"volume", V_FLOAT, offsetof(localEntityParse_t, volume), MEMBER_SIZEOF(localEntityParse_t, volume)},
80 {"frame", V_INT, offsetof(localEntityParse_t, frame), MEMBER_SIZEOF(localEntityParse_t, frame)},
81 {"angle", V_FLOAT, offsetof(localEntityParse_t, angle), MEMBER_SIZEOF(localEntityParse_t, angle)},
82 {"wait", V_POS, offsetof(localEntityParse_t, wait), MEMBER_SIZEOF(localEntityParse_t, wait)},
83 {"angles", V_VECTOR, offsetof(localEntityParse_t, angles), MEMBER_SIZEOF(localEntityParse_t, angles)},
84 {"origin", V_VECTOR, offsetof(localEntityParse_t, origin), MEMBER_SIZEOF(localEntityParse_t, origin)},
85 {"color", V_VECTOR, offsetof(localEntityParse_t, color), MEMBER_SIZEOF(localEntityParse_t, color)},
86 {"_color", V_VECTOR, offsetof(localEntityParse_t, color), MEMBER_SIZEOF(localEntityParse_t, color)},
87 {"modelscale_vec", V_VECTOR, offsetof(localEntityParse_t, scale), MEMBER_SIZEOF(localEntityParse_t, scale)},
88 {"wait", V_POS, offsetof(localEntityParse_t, wait), MEMBER_SIZEOF(localEntityParse_t, wait)},
89 {"classname", V_STRING, offsetof(localEntityParse_t, classname), 0},
90 {"model", V_STRING, offsetof(localEntityParse_t, model), 0},
91 {"anim", V_STRING, offsetof(localEntityParse_t, anim), 0},
92 {"particle", V_STRING, offsetof(localEntityParse_t, particle), 0},
93 {"noise", V_STRING, offsetof(localEntityParse_t, noise), 0},
94 {"tag", V_STRING, offsetof(localEntityParse_t, tagname), 0},
95 {"target", V_STRING, offsetof(localEntityParse_t, target), 0},
96 {"targetname", V_STRING, offsetof(localEntityParse_t, targetname), 0},
97 {"light", V_INT, offsetof(localEntityParse_t, light), MEMBER_SIZEOF(localEntityParse_t, light)},
98 {"ambient_day", V_VECTOR, offsetof(localEntityParse_t, ambientDayColor), MEMBER_SIZEOF(localEntityParse_t, ambientDayColor)},
99 {"light_day", V_FLOAT, offsetof(localEntityParse_t, dayLight), MEMBER_SIZEOF(localEntityParse_t, dayLight)},
100 {"angles_day", V_POS, offsetof(localEntityParse_t, daySunAngles), MEMBER_SIZEOF(localEntityParse_t, daySunAngles)},
101 {"color_day", V_VECTOR, offsetof(localEntityParse_t, daySunColor), MEMBER_SIZEOF(localEntityParse_t, daySunColor)},
102 {"ambient_night", V_VECTOR, offsetof(localEntityParse_t, ambientNightColor), MEMBER_SIZEOF(localEntityParse_t, ambientNightColor)},
103 {"light_night", V_FLOAT, offsetof(localEntityParse_t, nightLight), MEMBER_SIZEOF(localEntityParse_t, nightLight)},
104 {"angles_night", V_POS, offsetof(localEntityParse_t, nightSunAngles), MEMBER_SIZEOF(localEntityParse_t, nightSunAngles)},
105 {"color_night", V_VECTOR, offsetof(localEntityParse_t, nightSunColor), MEMBER_SIZEOF(localEntityParse_t, nightSunColor)},
106
107 {nullptr, V_NULL, 0, 0}
108 };
109
110 static void SP_worldspawn(const localEntityParse_t* entData);
111 static void SP_misc_model(const localEntityParse_t* entData);
112 static void SP_misc_particle(const localEntityParse_t* entData);
113 static void SP_misc_sound(const localEntityParse_t* entData);
114 static void SP_light(const localEntityParse_t* entData);
115
116 typedef struct {
117 const char* name;
118 void (*spawn) (const localEntityParse_t* entData);
119 } spawn_t;
120
121 static const spawn_t spawns[] = {
122 {"worldspawn", SP_worldspawn},
123 {"misc_model", SP_misc_model},
124 {"misc_particle", SP_misc_particle},
125 {"misc_sound", SP_misc_sound},
126 {"light", SP_light},
127
128 {nullptr, nullptr}
129 };
130
131 /**
132 * @brief Finds the spawn function for the entity and calls it
133 */
CL_SpawnCall(const localEntityParse_t * entData)134 static void CL_SpawnCall (const localEntityParse_t* entData)
135 {
136 const spawn_t* s;
137
138 if (entData->classname[0] == '\0')
139 return;
140
141 /* check normal spawn functions */
142 for (s = spawns; s->name; s++) {
143 /* found it */
144 if (Q_streq(s->name, entData->classname)) {
145 s->spawn(entData);
146 return;
147 }
148 }
149 }
150
151 /**
152 * @brief Parse the map entity string and spawns those entities that are client-side only
153 * @sa G_SendEdictsAndBrushModels
154 * @sa CL_AddBrushModel
155 */
CL_SpawnParseEntitystring(void)156 void CL_SpawnParseEntitystring (void)
157 {
158 const char* es = cl.mapData->mapEntityString;
159 int entnum = 0, maxLevel;
160
161 if (cl.mapMaxLevel > 0 && cl.mapMaxLevel < PATHFINDING_HEIGHT)
162 maxLevel = cl.mapMaxLevel;
163 else
164 maxLevel = PATHFINDING_HEIGHT;
165
166 /* vid restart? */
167 if (cl.numMapParticles || cl.numLMs)
168 return;
169
170 /* parse ents */
171 while (1) {
172 localEntityParse_t entData;
173 /* parse the opening brace */
174 const char* entityToken = Com_Parse(&es);
175 /* memorize the start */
176 if (!es)
177 break;
178
179 if (entityToken[0] != '{')
180 Com_Error(ERR_DROP, "V_ParseEntitystring: found %s when expecting {", entityToken);
181
182 /* initialize */
183 OBJZERO(entData);
184 VectorSet(entData.scale, 1, 1, 1);
185 entData.volume = SND_VOLUME_DEFAULT;
186 entData.maxLevel = maxLevel;
187 entData.entStringPos = es;
188 entData.entnum = entnum;
189 entData.maxMultiplayerTeams = TEAM_MAX_HUMAN;
190 entData.attenuation = SOUND_ATTN_IDLE;
191
192 /* go through all the dictionary pairs */
193 while (1) {
194 const value_t* v;
195 /* parse key */
196 entityToken = Com_Parse(&es);
197 if (entityToken[0] == '}')
198 break;
199 if (!es)
200 Com_Error(ERR_DROP, "V_ParseEntitystring: EOF without closing brace");
201
202 for (v = localEntityValues; v->string; v++)
203 if (Q_streq(entityToken, v->string)) {
204 /* found a definition */
205 entityToken = Com_Parse(&es);
206 if (!es)
207 Com_Error(ERR_DROP, "V_ParseEntitystring: EOF without closing brace");
208 Com_EParseValue(&entData, entityToken, v->type, v->ofs, v->size);
209 break;
210 }
211 }
212 CL_SpawnCall(&entData);
213
214 entnum++;
215 }
216
217 /* after we have parsed all the entities we can resolve the target, targetname
218 * connections for the misc_model entities */
219 LM_Think();
220 }
221
222 #define MIN_AMBIENT_COMPONENT 0.1
223 #define MIN_AMBIENT_SUM 0.50
224
225 /** @note Defaults should match those of ufo2map, or lighting will be inconsistent between world and models */
SP_worldspawn(const localEntityParse_t * entData)226 static void SP_worldspawn (const localEntityParse_t* entData)
227 {
228 const int dayLightmap = CL_GetConfigStringInteger(CS_LIGHTMAP);
229 int i;
230 vec3_t sunAngles;
231 vec4_t sunColor;
232 vec_t sunIntensity;
233
234 /* maximum level */
235 cl.mapMaxLevel = entData->maxLevel;
236
237 if (GAME_IsMultiplayer()) {
238 if (cl_teamnum->integer > entData->maxMultiplayerTeams || cl_teamnum->integer <= TEAM_CIVILIAN) {
239 Com_Printf("The selected team is not usable. "
240 "The map doesn't support %i teams but only %i teams\n",
241 cl_teamnum->integer, entData->maxMultiplayerTeams);
242 Cvar_SetValue("cl_teamnum", TEAM_DEFAULT);
243 Com_Printf("Set teamnum to %i\n", cl_teamnum->integer);
244 }
245 }
246
247 /** @todo - make sun position/color vary based on local time at location? */
248
249 /** @note Some vectors have exra elements to comply with mathlib and/or OpenGL conventions, but handled as shorter ones */
250 if (dayLightmap) {
251 /* set defaults for daylight */
252 Vector4Set(refdef.ambientColor, 0.26, 0.26, 0.26, 1.0);
253 sunIntensity = 280;
254 VectorSet(sunAngles, -75, 100, 0);
255 Vector4Set(sunColor, 0.90, 0.75, 0.65, 1.0);
256
257 /* override defaults with data from worldspawn entity, if any */
258 if (VectorNotEmpty(entData->ambientDayColor))
259 VectorCopy(entData->ambientDayColor, refdef.ambientColor);
260
261 if (entData->dayLight)
262 sunIntensity = entData->dayLight;
263
264 if (Vector2NotEmpty(entData->daySunAngles))
265 Vector2Copy(entData->daySunAngles, sunAngles);
266
267 if (VectorNotEmpty(entData->daySunColor))
268 VectorCopy(entData->daySunColor, sunColor);
269
270 Vector4Set(refdef.sunSpecularColor, 1.0, 1.0, 0.9, 1);
271 } else {
272 /* set defaults for night light */
273 Vector4Set(refdef.ambientColor, 0.16, 0.16, 0.17, 1.0);
274 sunIntensity = 15;
275 VectorSet(sunAngles, -80, 220, 0);
276 Vector4Set(sunColor, 0.25, 0.25, 0.35, 1.0);
277
278 /* override defaults with data from worldspawn entity, if any */
279 if (VectorNotEmpty(entData->ambientNightColor))
280 VectorCopy(entData->ambientNightColor, refdef.ambientColor);
281
282 if (entData->nightLight)
283 sunIntensity = entData->nightLight;
284
285 if (Vector2NotEmpty(entData->nightSunAngles))
286 Vector2Copy(entData->nightSunAngles, sunAngles);
287
288 if (VectorNotEmpty(entData->nightSunColor))
289 VectorCopy(entData->nightSunColor, sunColor);
290
291 Vector4Set(refdef.sunSpecularColor, 0.5, 0.5, 0.7, 1);
292 }
293
294 ColorNormalize(sunColor, sunColor);
295 VectorScale(sunColor, sunIntensity/255.0, sunColor);
296 Vector4Copy(sunColor, refdef.sunDiffuseColor);
297
298 /* clamp ambient for models */
299 Vector4Copy(refdef.ambientColor, refdef.modelAmbientColor);
300 for (i = 0; i < 3; i++)
301 if (refdef.modelAmbientColor[i] < MIN_AMBIENT_COMPONENT)
302 refdef.modelAmbientColor[i] = MIN_AMBIENT_COMPONENT;
303
304 /* scale it into a reasonable range, the clamp above ensures this will work */
305 while (VectorSum(refdef.modelAmbientColor) < MIN_AMBIENT_SUM)
306 VectorScale(refdef.modelAmbientColor, 1.25, refdef.modelAmbientColor);
307
308 AngleVectors(sunAngles, refdef.sunVector, nullptr, nullptr);
309 refdef.sunVector[3] = 0.0; /* to use as directional light source in OpenGL */
310
311 /** @todo Parse fog from worldspawn config */
312 refdef.weather = WEATHER_NONE;
313 refdef.fogColor[3] = 1.0;
314 VectorSet(refdef.fogColor, 0.75, 0.75, 0.75);
315 }
316
SP_misc_model(const localEntityParse_t * entData)317 static void SP_misc_model (const localEntityParse_t* entData)
318 {
319 localModel_t* lm;
320 int renderFlags = 0;
321
322 if (entData->model[0] == '\0') {
323 Com_Printf("misc_model without \"model\" specified\n");
324 return;
325 }
326
327 if (entData->spawnflags & (1 << MISC_MODEL_GLOW))
328 renderFlags |= RF_PULSE;
329
330 /* add it */
331 lm = LM_AddModel(entData->model, entData->origin, entData->angles, entData->entnum, (entData->spawnflags & 0xFF), renderFlags, entData->scale);
332 if (lm) {
333 if (LM_GetByID(entData->targetname) != nullptr)
334 Com_Error(ERR_DROP, "Ambiguous targetname '%s'", entData->targetname);
335 Q_strncpyz(lm->id, entData->targetname, sizeof(lm->id));
336 Q_strncpyz(lm->target, entData->target, sizeof(lm->target));
337 Q_strncpyz(lm->tagname, entData->tagname, sizeof(lm->tagname));
338
339 if (lm->animname[0] != '\0' && lm->tagname[0] != '\0') {
340 Com_Printf("Warning: Model has animation set, but also a tag - use the tag and skip the animation\n");
341 lm->animname[0] = '\0';
342 }
343
344 if (lm->tagname[0] != '\0' && lm->target[0] == '\0') {
345 Com_Error(ERR_DROP, "Warning: Model has tag set, but no target given");
346 }
347
348 lm->think = LMT_Init;
349 lm->skin = entData->skin;
350 lm->frame = entData->frame;
351 if (!lm->frame)
352 Q_strncpyz(lm->animname, entData->anim, sizeof(lm->animname));
353 else
354 Com_Printf("Warning: Model has frame and anim parameters - using frame (no animation)\n");
355 }
356 }
357
SP_misc_particle(const localEntityParse_t * entData)358 static void SP_misc_particle (const localEntityParse_t* entData)
359 {
360 const int dayLightmap = CL_GetConfigStringInteger(CS_LIGHTMAP);
361 if (!(dayLightmap && (entData->spawnflags & (1 << SPAWNFLAG_NO_DAY))))
362 CL_AddMapParticle(entData->particle, entData->origin, entData->wait, entData->entStringPos, (entData->spawnflags & 0xFF));
363 }
364
SP_misc_sound(const localEntityParse_t * entData)365 static void SP_misc_sound (const localEntityParse_t* entData)
366 {
367 const int dayLightmap = CL_GetConfigStringInteger(CS_LIGHTMAP);
368 if (!(dayLightmap && (entData->spawnflags & (1 << SPAWNFLAG_NO_DAY))))
369 LE_AddAmbientSound(entData->noise, entData->origin, (entData->spawnflags & 0xFF), entData->volume, entData->attenuation);
370 }
371
372 /**
373 * @param entData
374 */
SP_light(const localEntityParse_t * entData)375 static void SP_light (const localEntityParse_t* entData)
376 {
377 const int dayLightmap = CL_GetConfigStringInteger(CS_LIGHTMAP);
378
379 if (entData->light < 1.0)
380 return;
381
382 if (!(dayLightmap && (entData->spawnflags & (1 << SPAWNFLAG_NO_DAY)))) {
383 R_AddStaticLight(entData->origin, entData->light, entData->color);
384 }
385 }
386