UFO: Alien Invasion
Loading...
Searching...
No Matches
cl_spawn.cpp
Go to the documentation of this file.
1
5
6/*
7Copyright (C) 2002-2025 UFO: Alien Invasion.
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18See the GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, 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
72
73static 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)},
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#define MIN_AMBIENT_COMPONENT 0.1
111#define MIN_AMBIENT_SUM 0.50
112
114static void SP_worldspawn (const localEntityParse_t* entData)
115{
116 /* maximum level */
117 cl.mapMaxLevel = entData->maxLevel;
118
119 if (GAME_IsMultiplayer()) {
120 if (cl_teamnum->integer > entData->maxMultiplayerTeams || cl_teamnum->integer <= TEAM_CIVILIAN) {
121 Com_Printf("The selected team is not usable. "
122 "The map doesn't support %i teams but only %i teams\n",
123 cl_teamnum->integer, entData->maxMultiplayerTeams);
124 Cvar_SetValue("cl_teamnum", TEAM_DEFAULT);
125 Com_Printf("Set teamnum to %i\n", cl_teamnum->integer);
126 }
127 }
128
130 const int dayLightmap = CL_GetConfigStringInteger(CS_LIGHTMAP);
131
133 vec3_t sunAngles;
134 vec4_t sunColor;
135 vec_t sunIntensity;
136 if (dayLightmap) {
137 /* set defaults for daylight */
138 Vector4Set(refdef.ambientColor, 0.26, 0.26, 0.26, 1.0);
139 sunIntensity = 280;
140 VectorSet(sunAngles, -75, 100, 0);
141 Vector4Set(sunColor, 0.90, 0.75, 0.65, 1.0);
142
143 /* override defaults with data from worldspawn entity, if any */
144 if (VectorNotEmpty(entData->ambientDayColor))
145 VectorCopy(entData->ambientDayColor, refdef.ambientColor);
146
147 if (entData->dayLight)
148 sunIntensity = entData->dayLight;
149
150 if (Vector2NotEmpty(entData->daySunAngles))
151 Vector2Copy(entData->daySunAngles, sunAngles);
152
153 if (VectorNotEmpty(entData->daySunColor))
154 VectorCopy(entData->daySunColor, sunColor);
155
156 Vector4Set(refdef.sunSpecularColor, 1.0, 1.0, 0.9, 1);
157 } else {
158 /* set defaults for night light */
159 Vector4Set(refdef.ambientColor, 0.16, 0.16, 0.17, 1.0);
160 sunIntensity = 15;
161 VectorSet(sunAngles, -80, 220, 0);
162 Vector4Set(sunColor, 0.25, 0.25, 0.35, 1.0);
163
164 /* override defaults with data from worldspawn entity, if any */
165 if (VectorNotEmpty(entData->ambientNightColor))
166 VectorCopy(entData->ambientNightColor, refdef.ambientColor);
167
168 if (entData->nightLight)
169 sunIntensity = entData->nightLight;
170
171 if (Vector2NotEmpty(entData->nightSunAngles))
172 Vector2Copy(entData->nightSunAngles, sunAngles);
173
174 if (VectorNotEmpty(entData->nightSunColor))
175 VectorCopy(entData->nightSunColor, sunColor);
176
177 Vector4Set(refdef.sunSpecularColor, 0.5, 0.5, 0.7, 1);
178 }
179
180 ColorNormalize(sunColor, sunColor);
181 VectorScale(sunColor, sunIntensity/255.0, sunColor);
182 Vector4Copy(sunColor, refdef.sunDiffuseColor);
183
184 /* clamp ambient for models */
185 Vector4Copy(refdef.ambientColor, refdef.modelAmbientColor);
186 for (int i = 0; i < 3; i++)
187 if (refdef.modelAmbientColor[i] < MIN_AMBIENT_COMPONENT)
188 refdef.modelAmbientColor[i] = MIN_AMBIENT_COMPONENT;
189
190 /* scale it into a reasonable range, the clamp above ensures this will work */
191 while (VectorSum(refdef.modelAmbientColor) < MIN_AMBIENT_SUM)
192 VectorScale(refdef.modelAmbientColor, 1.25, refdef.modelAmbientColor);
193
194 AngleVectors(sunAngles, refdef.sunVector, nullptr, nullptr);
195 refdef.sunVector[3] = 0.0; /* to use as directional light source in OpenGL */
196
198 refdef.weather = WEATHER_NONE;
199 refdef.fogColor[3] = 1.0;
200 VectorSet(refdef.fogColor, 0.75, 0.75, 0.75);
201}
202
203static void SP_misc_model (const localEntityParse_t* entData)
204{
205 if (entData->model[0] == '\0') {
206 Com_Printf("misc_model without \"model\" specified\n");
207 return;
208 }
209
210 int renderFlags = 0;
211 if (entData->spawnflags & (1 << MISC_MODEL_GLOW))
212 renderFlags |= RF_PULSE;
213
214 /* add it */
215 localModel_t* lm = LM_AddModel(entData->model, entData->origin, entData->angles, entData->entnum, (entData->spawnflags & 0xFF), renderFlags, entData->scale);
216 if (lm) {
217 if (LM_GetByID(entData->targetname) != nullptr)
218 Com_Error(ERR_DROP, "Ambiguous targetname '%s'", entData->targetname);
219 Q_strncpyz(lm->id, entData->targetname, sizeof(lm->id));
220 Q_strncpyz(lm->target, entData->target, sizeof(lm->target));
221 Q_strncpyz(lm->tagname, entData->tagname, sizeof(lm->tagname));
222
223 if (lm->animname[0] != '\0' && lm->tagname[0] != '\0') {
224 Com_Printf("Warning: Model has animation set, but also a tag - use the tag and skip the animation\n");
225 lm->animname[0] = '\0';
226 }
227
228 if (lm->tagname[0] != '\0' && lm->target[0] == '\0') {
229 Com_Error(ERR_DROP, "Warning: Model has tag set, but no target given");
230 }
231
232 lm->think = LMT_Init;
233 lm->skin = entData->skin;
234 lm->frame = entData->frame;
235 if (!lm->frame)
236 Q_strncpyz(lm->animname, entData->anim, sizeof(lm->animname));
237 else
238 Com_Printf("Warning: Model has frame and anim parameters - using frame (no animation)\n");
239 }
240}
241
242static void SP_misc_particle (const localEntityParse_t* entData)
243{
244 const int dayLightmap = CL_GetConfigStringInteger(CS_LIGHTMAP);
245 if (!(dayLightmap && (entData->spawnflags & (1 << SPAWNFLAG_NO_DAY))))
246 CL_AddMapParticle(entData->particle, entData->origin, entData->wait, entData->entStringPos, (entData->spawnflags & 0xFF));
247}
248
249static void SP_misc_sound (const localEntityParse_t* entData)
250{
251 const int dayLightmap = CL_GetConfigStringInteger(CS_LIGHTMAP);
252 if (!(dayLightmap && (entData->spawnflags & (1 << SPAWNFLAG_NO_DAY))))
253 LE_AddAmbientSound(entData->noise, entData->origin, (entData->spawnflags & 0xFF), entData->volume, entData->attenuation);
254}
255
259static void SP_light (const localEntityParse_t* entData)
260{
261 if (entData->light < 1.0)
262 return;
263
264 const int dayLightmap = CL_GetConfigStringInteger(CS_LIGHTMAP);
265 if (!(dayLightmap && (entData->spawnflags & (1 << SPAWNFLAG_NO_DAY)))) {
266 R_AddStaticLight(entData->origin, entData->light, entData->color);
267 }
268}
269
270typedef struct {
271 const char* name;
272 void (*spawn) (const localEntityParse_t* entData);
273} spawn_t;
274
275static const spawn_t spawns[] = {
276 {"worldspawn", SP_worldspawn},
277 {"misc_model", SP_misc_model},
278 {"misc_particle", SP_misc_particle},
279 {"misc_sound", SP_misc_sound},
280 {"light", SP_light},
281
282 {nullptr, nullptr}
283};
284
288static void CL_SpawnCall (const localEntityParse_t* entData)
289{
290 if (entData->classname[0] == '\0')
291 return;
292
293 /* check normal spawn functions */
294 for (const spawn_t* s = spawns; s->name; s++) {
295 if (Q_streq(s->name, entData->classname)) {
296 /* found it */
297 s->spawn(entData);
298 return;
299 }
300 }
301}
302
309{
310 int maxLevel;
311
312 if (cl.mapMaxLevel > 0 && cl.mapMaxLevel < PATHFINDING_HEIGHT)
313 maxLevel = cl.mapMaxLevel;
314 else
315 maxLevel = PATHFINDING_HEIGHT;
316
317 /* vid restart? */
318 if (cl.numMapParticles || cl.numLMs)
319 return;
320
321 /* parse ents */
322 const char* es = cl.mapData->mapEntityString;
323 int entnum = 0;
324 while (1) {
325 localEntityParse_t entData;
326 /* parse the opening brace */
327 const char* entityToken = Com_Parse(&es);
328 /* memorize the start */
329 if (!es)
330 break;
331
332 if (entityToken[0] != '{')
333 Com_Error(ERR_DROP, "V_ParseEntitystring: found %s when expecting {", entityToken);
334
335 /* initialize */
336 OBJZERO(entData);
337 VectorSet(entData.scale, 1, 1, 1);
338 entData.volume = SND_VOLUME_DEFAULT;
339 entData.maxLevel = maxLevel;
340 entData.entStringPos = es;
341 entData.entnum = entnum;
344
345 /* go through all the dictionary pairs */
346 while (1) {
347 /* parse key */
348 entityToken = Com_Parse(&es);
349 if (entityToken[0] == '}')
350 break;
351 if (!es)
352 Com_Error(ERR_DROP, "V_ParseEntitystring: EOF without closing brace");
353
354 for (const value_t* v = localEntityValues; v->string; v++)
355 if (Q_streq(entityToken, v->string)) {
356 /* found a definition */
357 entityToken = Com_Parse(&es);
358 if (!es)
359 Com_Error(ERR_DROP, "V_ParseEntitystring: EOF without closing brace");
360 Com_EParseValue(&entData, entityToken, v->type, v->ofs, v->size);
361 break;
362 }
363 }
364 CL_SpawnCall(&entData);
365
366 entnum++;
367 }
368
369 /* after we have parsed all the entities we can resolve the target, targetname
370 * connections for the misc_model entities */
371 LM_Think();
372}
int CL_GetConfigStringInteger(int index)
clientBattleScape_t cl
bool GAME_IsMultiplayer(void)
Definition cl_game.cpp:299
Shared game type headers.
void LE_AddAmbientSound(const char *sound, const vec3_t origin, int levelflags, float volume, float attenuation)
Adds ambient sounds from misc_sound entities.
void LM_Think(void)
localModel_t * LM_GetByID(const char *id)
void LMT_Init(localModel_t *localModel)
localModel_t * LM_AddModel(const char *model, const vec3_t origin, const vec3_t angles, int entnum, int levelflags, int renderFlags, const vec3_t scale)
Prepares local (not known or handled by the server) models to the map, which will be added later in L...
cvar_t * cl_teamnum
Definition cl_main.cpp:81
void CL_AddMapParticle(const char *ptl, const vec3_t origin, const vec2_t wait, const char *info, int levelflags)
Spawns the map particle.
rendererData_t refdef
Definition r_main.cpp:45
#define WEATHER_NONE
Definition cl_renderer.h:37
#define MISC_MODEL_GLOW
Definition cl_spawn.cpp:32
static void SP_light(const localEntityParse_t *entData)
Definition cl_spawn.cpp:259
static const spawn_t spawns[]
Definition cl_spawn.cpp:275
static void SP_misc_particle(const localEntityParse_t *entData)
Definition cl_spawn.cpp:242
static void SP_worldspawn(const localEntityParse_t *entData)
Definition cl_spawn.cpp:114
#define MIN_AMBIENT_SUM
Definition cl_spawn.cpp:111
static const value_t localEntityValues[]
Definition cl_spawn.cpp:73
static void CL_SpawnCall(const localEntityParse_t *entData)
Finds the spawn function for the entity and calls it.
Definition cl_spawn.cpp:288
#define SPAWNFLAG_NO_DAY
Definition cl_spawn.cpp:33
static void SP_misc_model(const localEntityParse_t *entData)
Definition cl_spawn.cpp:203
static void SP_misc_sound(const localEntityParse_t *entData)
Definition cl_spawn.cpp:249
#define MIN_AMBIENT_COMPONENT
Definition cl_spawn.cpp:110
void CL_SpawnParseEntitystring(void)
Parse the map entity string and spawns those entities that are client-side only.
Definition cl_spawn.cpp:308
Primary header for client.
void Com_Error(int code, const char *fmt,...)
Definition common.cpp:459
void Com_Printf(const char *const fmt,...)
Definition common.cpp:428
#define SOUND_ATTN_IDLE
Definition common.h:187
#define ERR_DROP
Definition common.h:211
void Cvar_SetValue(const char *varName, float value)
Expands value to a string and calls Cvar_Set.
Definition cvar.cpp:671
#define TEAM_DEFAULT
Definition defines.h:51
#define PATHFINDING_HEIGHT
15 max, adjusting above 8 will require a rewrite to the DV code
Definition defines.h:294
#define MAX_QPATH
Definition filesys.h:40
voidpf uLong int origin
Definition ioapi.h:45
vec_t ColorNormalize(const vec3_t in, vec3_t out)
Definition mathlib.cpp:190
void AngleVectors(const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up)
Create the rotation matrix in order to rotate something.
Definition mathlib.cpp:631
const char * Com_Parse(const char *data_p[], char *target, size_t size, bool replaceWhitespaces)
Parse a token out of a string.
Definition parse.cpp:107
Shared parsing functions.
#define CS_LIGHTMAP
Definition q_shared.h:321
#define TEAM_MAX_HUMAN
Definition q_shared.h:64
#define TEAM_CIVILIAN
Definition q_shared.h:61
#define RF_PULSE
Definition r_entity.h:47
QGL_EXTERN void(APIENTRY *qglActiveTexture)(GLenum texture)
QGL_EXTERN int GLboolean GLfloat * v
Definition r_gl.h:120
QGL_EXTERN GLint i
Definition r_gl.h:113
void R_AddStaticLight(const vec3_t origin, float radius, const vec3_t color)
Add static light for model lighting (world already got them baked into lightmap).
Definition r_light.cpp:261
#define SND_VOLUME_DEFAULT
Definition s_main.h:42
int Com_EParseValue(void *base, const char *token, valueTypes_t type, int ofs, size_t size)
Definition scripts.cpp:964
@ V_FLOAT
Definition scripts.h:54
@ V_NULL
Definition scripts.h:49
@ V_STRING
Definition scripts.h:58
@ V_INT
Definition scripts.h:52
@ V_VECTOR
Definition scripts.h:56
@ V_POS
Definition scripts.h:55
#define MEMBER_SIZEOF(TYPE, MEMBER)
Definition scripts.h:34
#define Q_streq(a, b)
Definition shared.h:136
#define OBJZERO(obj)
Definition shared.h:178
#define MAX_VAR
Definition shared.h:36
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition shared.cpp:457
const char * entStringPos
Definition cl_spawn.cpp:69
char particle[MAX_VAR]
Definition cl_spawn.cpp:42
vec3_t ambientNightColor
Definition cl_spawn.cpp:48
char target[MAX_VAR]
Definition cl_spawn.cpp:37
char targetname[MAX_VAR]
Definition cl_spawn.cpp:38
char anim[MAX_VAR]
Definition cl_spawn.cpp:40
char noise[MAX_QPATH]
Definition cl_spawn.cpp:43
char classname[MAX_VAR]
Definition cl_spawn.cpp:36
char model[MAX_QPATH]
Definition cl_spawn.cpp:41
char tagname[MAX_VAR]
Definition cl_spawn.cpp:39
local models
void(* think)(struct localModel_s *localModel)
char id[MAX_VAR]
char target[MAX_VAR]
char animname[MAX_QPATH]
char tagname[MAX_VAR]
const char * name
Definition cl_spawn.cpp:271
void(* spawn)(const localEntityParse_t *entData)
Definition cl_spawn.cpp:272
float vec_t
Definition ufotypes.h:37
vec_t vec3_t[3]
Definition ufotypes.h:39
vec_t vec4_t[4]
Definition ufotypes.h:40
vec_t vec2_t[2]
Definition ufotypes.h:38
static const vec3_t scale
#define Vector4Set(v, r, g, b, a)
Definition vector.h:62
#define Vector2NotEmpty(a)
Definition vector.h:75
#define Vector4Copy(src, dest)
Definition vector.h:53
#define VectorNotEmpty(a)
Definition vector.h:72
#define VectorSum(a)
Definition vector.h:60
#define VectorCopy(src, dest)
Definition vector.h:51
#define VectorSet(v, x, y, z)
Definition vector.h:59
#define VectorScale(in, scale, out)
Definition vector.h:79
#define Vector2Copy(src, dest)
Definition vector.h:52