Пример #1
0
/**
 * @brief Called from the precache check to queue a download.
 * @sa CL_CheckOrDownloadFile
 */
bool CL_QueueHTTPDownload (const char *ufoPath)
{
	/* no http server (or we got booted) */
	if (!cls.downloadServer[0] || abortDownloads || !cl_http_downloads->integer)
		return false;

	dlqueue_t** anchor = &cls.downloadQueue;
	for (; *anchor; anchor = &(*anchor)->next) {
		/* avoid sending duplicate requests */
		if (Q_streq(ufoPath, (*anchor)->ufoPath))
			return true;
	}

	dlqueue_t* const q = Mem_AllocType(dlqueue_t);
	q->next = NULL;
	q->state = DLQ_STATE_NOT_STARTED;
	Q_strncpyz(q->ufoPath, ufoPath, sizeof(q->ufoPath));
	*anchor = q;

	/* special case for map file lists */
	if (cl_http_filelists->integer) {
		const char *extension = Com_GetExtension(ufoPath);
		if (extension != NULL && !Q_strcasecmp(extension, "bsp")) {
			char listPath[MAX_OSPATH];
			const size_t len = strlen(ufoPath);
			Com_sprintf(listPath, sizeof(listPath), BASEDIRNAME"/%.*s.filelist", (int)(len - 4), ufoPath);
			CL_QueueHTTPDownload(listPath);
		}
	}

	/* if a download entry has made it this far, CL_FinishHTTPDownload is guaranteed to be called. */
	pendingCount++;

	return true;
}
Пример #2
0
/**
 * @brief Build a patch for a surface that emits light
 * @note This is called in the lighting stage
 * @param fn The face number of the surface that emits the light
 * @param w The winding
 * @sa BuildLights
 */
static void BuildPatch (int fn, winding_t* w)
{
	patch_t* patch;
	dBspPlane_t* plane;

	patch = Mem_AllocType(patch_t);

	face_patches[fn] = patch;

	patch->face = &curTile->faces[fn];
	patch->winding = w;

	/* resolve the normal */
	plane = &curTile->planes[patch->face->planenum];

	if (patch->face->side)
		VectorNegate(plane->normal, patch->normal);
	else
		VectorCopy(plane->normal, patch->normal);

	WindingCenter(w, patch->origin);

	/* nudge the origin out along the normal */
	VectorMA(patch->origin, 2.0, patch->normal, patch->origin);

	patch->area = WindingArea(w);

	if (patch->area < 1.0)  /* clamp area */
		patch->area = 1.0;

	EmissiveLight(patch);  /* surface light */
}
Пример #3
0
/**
 * @brief Allocates a tree and initializes it
 */
tree_t *AllocTree (void)
{
	tree_t *tree = Mem_AllocType(tree_t);

	tree->aabb.clearBounds();

	return tree;
}
Пример #4
0
/**
 * @sa FreePortal
 */
static portal_t *AllocPortal (void)
{
	if (threadstate.numthreads == 1)
		c_active_portals++;
	if (c_active_portals > c_peak_portals)
		c_peak_portals = c_active_portals;

	return Mem_AllocType(portal_t);
}
static void GAME_SCP_InitStartup (void)
{
	static saveSubsystems_t mission_subsystemXML = {"staticcampaign", SCP_Save, SCP_Load};

	GAME_CP_InitStartup();

	SAV_AddSubsystem(&mission_subsystemXML);

	ccs.missionSpawnCallback = SCP_SpawnNewMissions;
	ccs.missionResultCallback = SCP_CampaignProgress;
	scd = Mem_AllocType(staticCampaignData_t);
}
Пример #6
0
void SetKeyValue (entity_t* ent, const char* key, const char* value)
{
	epair_t* ep;

	for (ep = ent->epairs; ep; ep = ep->next)
		if (Q_streq(ep->key, key)) {
			ep->value = Mem_StrDup(value);
			return;
		}
	ep = Mem_AllocType(epair_t);
	ep->next = ent->epairs;
	ent->epairs = ep;
	ep->key = Mem_StrDup(key);
	ep->value = Mem_StrDup(value);
}
Пример #7
0
epair_t* AddEpair (const char* key, const char* value, int entNum)
{
	epair_t* e = Mem_AllocType(epair_t);

	if (IsInvalidEntityToken(value) || IsInvalidEntityToken(key))
		Sys_Error("Invalid entity %i found with key '%s' and value '%s'", entNum, key, value);

	if (strlen(key) >= MAX_KEY - 1)
		Sys_Error("ParseEpar: token too long");
	e->key = key;
	if (strlen(value) >= MAX_VALUE - 1)
		Sys_Error("ParseEpar: token too long");
	e->value = value;

	return e;
}
Пример #8
0
/**
 *	@brief Chops the patch by a global grid
 */
static void SubdividePatch(patch_t* patch)
{
	winding_t* w, *o1, *o2;
	vec3_t mins, maxs;
	vec3_t split;
	vec_t dist;
	int i;
	patch_t* newp;

	w = patch->winding;
	WindingBounds(w, mins, maxs);

	VectorClear(split);

	for (i = 0; i < 3; i++) {
		if (floor((mins[i] + 1) / PATCH_SUBDIVIDE) < floor((maxs[i] - 1) / PATCH_SUBDIVIDE)) {
			split[i] = 1.0;
			break;
		}
	}
	/* no splitting needed */
	if (i == 3)
		return;

	/* split the winding */
	dist = PATCH_SUBDIVIDE * (1 + floor((mins[i] + 1) / PATCH_SUBDIVIDE));
	ClipWindingEpsilon(w, split, dist, ON_EPSILON, &o1, &o2);

	/* create a new patch */
	newp = Mem_AllocType(patch_t);

	newp->next = patch->next;
	patch->next = newp;

	patch->winding = o1;
	newp->winding = o2;

	FinishSubdividePatch(patch, newp);

	SubdividePatch(patch);
	SubdividePatch(newp);
}
Пример #9
0
/**
 * @sa AllocBrush
 * @sa AllocTree
 */
node_t *AllocNode (void)
{
	return Mem_AllocType(node_t);
}
Пример #10
0
static void testMove (void)
{
	vec3_t vec;
	pos3_t pos;
	pos_t gridPos;

	if (FS_CheckFile("maps/%s.bsp", mapName) != -1) {
		char entityString[MAX_TOKEN_CHARS];
		CM_LoadMap(mapName, true, "", entityString, &mapData, &mapTiles);
		CM_LoadMap(mapName, true, "", entityString, &mapData, &mapTiles);
	} else {
		UFO_CU_FAIL_MSG_FATAL(va("Map resource '%s.bsp' for test is missing.", mapName));
	}

	VectorSet(vec, 16, 16, 48);
	VecToPos(vec, pos);
	CU_ASSERT_EQUAL(pos[0], 128);
	CU_ASSERT_EQUAL(pos[1], 128);
	CU_ASSERT_EQUAL(pos[2], 0);

	VectorSet(vec, 80, 16, 80);
	VecToPos(vec, pos);
	CU_ASSERT_EQUAL(pos[0], 130);
	CU_ASSERT_EQUAL(pos[1], 128);
	CU_ASSERT_EQUAL(pos[2], 1);

	gridPos = Grid_Fall(mapData.routing, ACTOR_SIZE_NORMAL, pos);
	CU_ASSERT_EQUAL(gridPos, 1);

	{
		const byte crouchingState = 0;
		const int maxTUs = MAX_ROUTE_TUS;
		int lengthStored;
		pos3_t to;
		pathing_t* path = Mem_AllocType(pathing_t);

		VectorSet(vec, 80, 80, 32);
		VecToPos(vec, pos);

		Grid_CalcPathing(mapData.routing, ACTOR_SIZE_NORMAL, path, pos, maxTUs, nullptr);
		Grid_MoveStore(path);

		/* move downwards */
		{
			int lengthUnstored;
			VectorSet(vec, 80, 48, 32);
			VecToPos(vec, to);

			lengthUnstored = Grid_MoveLength(path, to, crouchingState, false);
			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthUnstored, lengthStored);
			CU_ASSERT_EQUAL(lengthStored, TU_MOVE_STRAIGHT);
		}
		/* try to move three steps upwards - there is a brush*/
		{
			VectorSet(vec, 80, 176, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, ROUTING_NOT_REACHABLE);
		}
		/* try move into the nodraw */
		{
			VectorSet(vec, 48, 16, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, ROUTING_NOT_REACHABLE);
		}
		/* move into the lightclip */
		{
			VectorSet(vec, 48, 48, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, TU_MOVE_DIAGONAL);
		}
		/* move into the passable */
		{
			VectorSet(vec, 144, 48, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, TU_MOVE_DIAGONAL + TU_MOVE_STRAIGHT);
		}
		/* go to the other side - diagonal, followed by six straight moves */
		{
			VectorSet(vec, -16, 48, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, 6 * TU_MOVE_STRAIGHT + TU_MOVE_DIAGONAL);
		}
		/* try to walk out of the map */
		{
			VectorSet(vec, 48, 272, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, ROUTING_NOT_REACHABLE);
		}
		/* walk to the map border */
		{
			VectorSet(vec, 48, 240, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, 4 * TU_MOVE_STRAIGHT + TU_MOVE_DIAGONAL);
		}
		/* walk a level upwards */
		{
			VectorSet(vec, 240, 80, 96);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, 5 * TU_MOVE_STRAIGHT);
		}
		/* move to the door (not a func_door) */
		{
			VectorSet(vec, 176, -80, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, 4 * TU_MOVE_STRAIGHT + 2 * TU_MOVE_DIAGONAL);
		}
		/* move into the trigger_touch */
		{
			VectorSet(vec, -48, -80, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, 5 * TU_MOVE_STRAIGHT + 3 * TU_MOVE_DIAGONAL);
		}
		/* try to walk into the actorclip */
		{
			VectorSet(vec, -48, -48, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, ROUTING_NOT_REACHABLE);
		}
	}
}
Пример #11
0
static void testMoveEntities (void)
{
	pos3_t pos;
	vec3_t vec;
	pathing_t* path = Mem_AllocType(pathing_t);
	forbiddenList_t forbiddenList;
	const byte crouchingState = 0;
	const int maxTUs = MAX_ROUTE_TUS;
	forbiddenList.reset();

	SV_Map(true, mapName, nullptr);

	/* starting point */
	VectorSet(vec, 240, -144, 32);
	VecToPos(vec, pos);

	G_CompleteRecalcRouting();

	{
		Edict* ent = nullptr;
		while ((ent = G_EdictsGetNextInUse(ent))) {
			/* Dead 2x2 unit will stop walking, too. */
			if (ent->type == ET_SOLID) {
				int j;
				for (j = 0; j < ent->forbiddenListSize; j++) {
					forbiddenList.add(ent->forbiddenListPos[j], (byte*) &ent->fieldSize);
				}
			}
		}
	}

	{
		int lengthStored;
		pos3_t to;

		Grid_CalcPathing(sv->mapData.routing, ACTOR_SIZE_NORMAL, path, pos, maxTUs, &forbiddenList);
		Grid_MoveStore(path);

		/* walk onto the func_breakable */
		{
			VectorSet(vec, 112, -144, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, 4 * TU_MOVE_STRAIGHT);
		}
		/* walk over the func_breakable */
		{
			VectorSet(vec, 80, -144, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, 5 * TU_MOVE_STRAIGHT);
		}
		/* walk over the func_breakable */
		{
			VectorSet(vec, 16, -144, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, 7 * TU_MOVE_STRAIGHT);
		}
	}

	/* starting point */
	VectorSet(vec, 144, 144, 32);
	VecToPos(vec, pos);

	{
		int lengthStored;
		pos3_t to;

		Grid_CalcPathing(sv->mapData.routing, ACTOR_SIZE_NORMAL, path, pos, maxTUs, &forbiddenList);
		Grid_MoveStore(path);

		/* walk through the opened door */
		{
			VectorSet(vec, 112, 144, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, TU_MOVE_STRAIGHT);
		}

		/* walk around the opened door */
		{
			VectorSet(vec, 144, 208, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, true);
			CU_ASSERT_EQUAL(lengthStored, 2 * TU_MOVE_STRAIGHT + TU_MOVE_DIAGONAL);
		}
	}

	SV_ShutdownGameProgs();
}
Пример #12
0
/**
 * @brief Allocate a sequence context
 * @return Context
 */
sequenceContext_t *SEQ_AllocContext (void)
{
	sequenceContext_t *context;
	context = Mem_AllocType(sequenceContext_t);
	return context;
}
Пример #13
0
static face_t* AllocFace (void)
{
	c_faces++;

	return Mem_AllocType(face_t);
}
Пример #14
0
/**
 * @brief Decides if following events should be delayed. The delay is the amount of time the actor needs to walk
 * from the start to the end pos.
 */
int CL_ActorDoMoveTime (const eventRegister_t* self, dbuffer* msg, eventTiming_t* eventTiming)
{
	int time = 0;

	const int eventTime = eventTiming->nextTime;
	const int number = NET_ReadShort(msg);
	/* get le */
	le_t* le = LE_Get(number);
	if (!le)
		LE_NotFoundError(number);

	pos3_t pos;
	VectorCopy(le->pos, pos);
	byte crouchingState = LE_IsCrouched(le) ? 1 : 0;

	leStep_t* newStep = Mem_AllocType(leStep_t);
	if (le->stepList == nullptr) {
		le->stepList = newStep;
		le->stepIndex = 0;
	} else {
		/* append to the list */
		leStep_t* step = le->stepList;
		while (step) {
			if (step->next == nullptr) {
				step->next = newStep;
				le->stepIndex++;
				break;
			}
			step = step->next;
		}
	}

	/* the end of this event is marked with a 0 */
	while (NET_PeekLong(msg) != 0) {
		newStep->steps = NET_ReadByte(msg);
		const dvec_t dvec = NET_ReadShort(msg);
		const byte dir = getDVdir(dvec);
		pos3_t oldPos;
		VectorCopy(pos, oldPos);
		PosAddDV(pos, crouchingState, dvec);
		const int stepTime = LE_ActorGetStepTime(le, pos, oldPos, dir, NET_ReadShort(msg));
		newStep->stepTimes[newStep->steps] = stepTime;
		time += stepTime;
		NET_ReadShort(msg);
	}
	++newStep->steps;

	if (newStep->steps > MAX_ROUTE)
		Com_Error(ERR_DROP, "route length overflow: %i", newStep->steps);

	/* skip the end of move marker */
	NET_ReadLong(msg);

	/* Also skip the final position */
	NET_ReadByte(msg);
	NET_ReadByte(msg);
	NET_ReadByte(msg);

	assert(NET_PeekByte(msg) == EV_NULL);

	eventTiming->nextTime += time + 400;
	newStep->lastMoveTime = eventTime;
	newStep->lastMoveDuration = time;
	return eventTime;
}
Пример #15
0
static void testMoveEntities (void)
{
	routing_t *routing;
	pos3_t pos;
	vec3_t vec;
	pathing_t *path = Mem_AllocType(pathing_t);
	pos_t *forbiddenList[MAX_FORBIDDENLIST];
	int forbiddenListLength = 0;
	const byte crouchingState = 0;
	const int distance = MAX_ROUTE;

	SV_Map(qtrue, mapName, NULL);

	/* starting point */
	VectorSet(vec, 240, -144, 32);
	VecToPos(vec, pos);

	routing = &sv->mapData.map[ACTOR_SIZE_NORMAL - 1];

	G_CompleteRecalcRouting();

	{
		edict_t *ent = NULL;
		while ((ent = G_EdictsGetNextInUse(ent))) {
			/* Dead 2x2 unit will stop walking, too. */
			if (ent->type == ET_SOLID) {
				int j;
				for (j = 0; j < ent->forbiddenListSize; j++) {
					forbiddenList[forbiddenListLength++] = ent->forbiddenListPos[j];
					forbiddenList[forbiddenListLength++] = (byte*) &ent->fieldSize;
				}
			}
		}
	}

	{
		int lengthStored;
		pos3_t to;

		Grid_MoveCalc(routing, ACTOR_SIZE_NORMAL, path, pos, crouchingState, distance, forbiddenList, forbiddenListLength);
		Grid_MoveStore(path);

		/* walk onto the func_breakable */
		{
			VectorSet(vec, 112, -144, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, qtrue);
			CU_ASSERT_EQUAL(lengthStored, 4 * TU_MOVE_STRAIGHT);
		}
		/* walk over the func_breakable */
		{
			VectorSet(vec, 80, -144, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, qtrue);
			CU_ASSERT_EQUAL(lengthStored, 5 * TU_MOVE_STRAIGHT);
		}
		/* walk over the func_breakable */
		{
			VectorSet(vec, 16, -144, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, qtrue);
			CU_ASSERT_EQUAL(lengthStored, 7 * TU_MOVE_STRAIGHT);
		}
	}

	/* starting point */
	VectorSet(vec, 144, 144, 32);
	VecToPos(vec, pos);

	{
		int lengthStored;
		pos3_t to;

		Grid_MoveCalc(routing, ACTOR_SIZE_NORMAL, path, pos, crouchingState, distance, forbiddenList, forbiddenListLength);
		Grid_MoveStore(path);

		/* walk through the opened door */
		{
			VectorSet(vec, 112, 144, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, qtrue);
			CU_ASSERT_EQUAL(lengthStored, TU_MOVE_STRAIGHT);
		}

		/* walk around the opened door */
		{
			VectorSet(vec, 144, 208, 32);
			VecToPos(vec, to);

			lengthStored = Grid_MoveLength(path, to, crouchingState, qtrue);
			CU_ASSERT_EQUAL(lengthStored, 2 * TU_MOVE_STRAIGHT + TU_MOVE_DIAGONAL);
		}
	}

	SV_ShutdownGameProgs();
}
Пример #16
0
/**
 * @brief Create lights out of patches and entity lights
 * @sa LightWorld
 * @sa BuildPatch
 */
void BuildLights (void)
{
	int i;
	light_t* l;

	/* surfaces */
	for (i = 0; i < MAX_MAP_FACES; i++) {
		/* iterate subdivided patches */
		for(const patch_t* p = face_patches[i]; p; p = p->next) {
			if (VectorEmpty(p->light))
				continue;

			numlights[config.compile_for_day]++;
			l = Mem_AllocType(light_t);

			VectorCopy(p->origin, l->origin);

			l->next = lights[config.compile_for_day];
			lights[config.compile_for_day] = l;

			l->type = emit_surface;

			l->intensity = ColorNormalize(p->light, l->color);
			l->intensity *= p->area * config.surface_scale;
		}
	}

	/* entities (skip the world) */
	for (i = 1; i < num_entities; i++) {
		float intensity;
		const char* color;
		const char* target;
		const entity_t* e = &entities[i];
		const char* name = ValueForKey(e, "classname");
		if (!Q_strstart(name, "light"))
			continue;

		/* remove those lights that are only for the night version */
		if (config.compile_for_day) {
			const int spawnflags = atoi(ValueForKey(e, "spawnflags"));
			if (!(spawnflags & 1))	/* day */
				continue;
		}

		numlights[config.compile_for_day]++;
		l = Mem_AllocType(light_t);

		GetVectorForKey(e, "origin", l->origin);

		/* link in */
		l->next = lights[config.compile_for_day];
		lights[config.compile_for_day] = l;

		intensity = FloatForKey(e, "light");
		if (!intensity)
			intensity = 300.0;
		color = ValueForKey(e, "_color");
		if (color && color[0] != '\0'){
			if (sscanf(color, "%f %f %f", &l->color[0], &l->color[1], &l->color[2]) != 3)
				Sys_Error("Invalid _color entity property given: %s", color);
			ColorNormalize(l->color, l->color);
		} else
			VectorSet(l->color, 1.0, 1.0, 1.0);
		l->intensity = intensity * config.entity_scale;
		l->type = emit_point;

		target = ValueForKey(e, "target");
		if (target[0] != '\0' || Q_streq(name, "light_spot")) {
			l->type = emit_spotlight;
			l->stopdot = FloatForKey(e, "_cone");
			if (!l->stopdot)
				l->stopdot = 10;
			l->stopdot = cos(l->stopdot * torad);
			if (target[0] != '\0') {	/* point towards target */
				entity_t* e2 = FindTargetEntity(target);
				if (!e2)
					Com_Printf("WARNING: light at (%i %i %i) has missing target '%s' - e.g. create an info_null that has a 'targetname' set to '%s'\n",
						(int)l->origin[0], (int)l->origin[1], (int)l->origin[2], target, target);
				else {
					vec3_t dest;
					GetVectorForKey(e2, "origin", dest);
					VectorSubtract(dest, l->origin, l->normal);
					VectorNormalize(l->normal);
				}
			} else {	/* point down angle */
				const float angle = FloatForKey(e, "angle");
				if (angle == ANGLE_UP) {
					l->normal[0] = l->normal[1] = 0.0;
					l->normal[2] = 1.0;
				} else if (angle == ANGLE_DOWN) {
					l->normal[0] = l->normal[1] = 0.0;
					l->normal[2] = -1.0;
				} else {
					l->normal[2] = 0;
					l->normal[0] = cos(angle * torad);
					l->normal[1] = sin(angle * torad);
				}
			}
		}
	}

	/* handle worldspawn light settings */
	{
		const entity_t* e = &entities[0];
		const char* ambient, *light, *angles, *color;
		float f;
		int i;

		if (config.compile_for_day) {
			ambient = ValueForKey(e, "ambient_day");
			light = ValueForKey(e, "light_day");
			angles = ValueForKey(e, "angles_day");
			color = ValueForKey(e, "color_day");
		} else {
			ambient = ValueForKey(e, "ambient_night");
			light = ValueForKey(e, "light_night");
			angles = ValueForKey(e, "angles_night");
			color = ValueForKey(e, "color_night");
		}

		if (light[0] != '\0')
			sun_intensity = atoi(light);

		if (angles[0] != '\0') {
			VectorClear(sun_angles);
			if (sscanf(angles, "%f %f", &sun_angles[0], &sun_angles[1]) != 2)
				Sys_Error("wrong angles values given: '%s'", angles);
			AngleVectors(sun_angles, sun_normal, nullptr, nullptr);
		}

		if (color[0] != '\0') {
			GetVectorFromString(color, sun_color);
			ColorNormalize(sun_color, sun_color);
		}

		if (ambient[0] != '\0')
			GetVectorFromString(ambient, sun_ambient_color);

		/* optionally pull brightness from worldspawn */
		f = FloatForKey(e, "brightness");
		if (f > 0.0)
			config.brightness = f;

		/* saturation as well */
		f = FloatForKey(e, "saturation");
		if (f > 0.0)
			config.saturation = f;
		else
			Verb_Printf(VERB_EXTRA, "Invalid saturation setting (%f) in worldspawn found\n", f);

		f = FloatForKey(e, "contrast");
		if (f > 0.0)
			config.contrast = f;
		else
			Verb_Printf(VERB_EXTRA, "Invalid contrast setting (%f) in worldspawn found\n", f);

		/* lightmap resolution downscale (e.g. 4 = 1 << 4) */
		i = atoi(ValueForKey(e, "quant"));
		if (i >= 1 && i <= 6)
			config.lightquant = i;
		else
			Verb_Printf(VERB_EXTRA, "Invalid quant setting (%i) in worldspawn found\n", i);
	}

	Verb_Printf(VERB_EXTRA, "light settings:\n * intensity: %i\n * sun_angles: pitch %f yaw %f\n * sun_color: %f:%f:%f\n * sun_ambient_color: %f:%f:%f\n",
		sun_intensity, sun_angles[0], sun_angles[1], sun_color[0], sun_color[1], sun_color[2], sun_ambient_color[0], sun_ambient_color[1], sun_ambient_color[2]);
	Verb_Printf(VERB_NORMAL, "%i direct lights for %s lightmap\n", numlights[config.compile_for_day], (config.compile_for_day ? "day" : "night"));
}