1 /**
2  * @file
3  */
4 
5 /*
6 Copyright (C) 2002-2013 UFO: Alien Invasion.
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 
23 */
24 
25 #include "../client.h"
26 #include "cl_battlescape.h"
27 #include "cl_actor.h"
28 
29 clientBattleScape_t cl;
30 
31 /**
32  * @brief Searches a local entity at the given position.
33  * @param[in] pos The grid position to search a local entity at
34  * @param[in] includingStunned Also search for stunned actors if @c true.
35  * @param[in] actor The current selected actor
36  */
CL_BattlescapeSearchAtGridPos(const pos3_t pos,bool includingStunned,const le_t * actor)37 le_t* CL_BattlescapeSearchAtGridPos (const pos3_t pos, bool includingStunned, const le_t* actor)
38 {
39 	le_t* le;
40 	le_t* nonActor = nullptr;
41 
42 	/* search for an actor on this field */
43 	le = nullptr;
44 	while ((le = LE_GetNextInUse(le))) {
45 		if (actor != nullptr && le == actor->clientAction) {
46 			/* if the actor has a client action assigned and we click onto the actor,
47 			 * we will trigger this client action */
48 			if (VectorCompare(actor->pos, pos))
49 				nonActor = le;
50 		} else if (le != actor && LE_IsLivingAndVisibleActor(le) && (includingStunned || !LE_IsStunned(le)))
51 			switch (le->fieldSize) {
52 			case ACTOR_SIZE_NORMAL:
53 				if (VectorCompare(le->pos, pos))
54 					return le;
55 				break;
56 			case ACTOR_SIZE_2x2: {
57 				pos3_t actor2x2[3];
58 
59 				VectorSet(actor2x2[0], le->pos[0] + 1, le->pos[1],     le->pos[2]);
60 				VectorSet(actor2x2[1], le->pos[0],     le->pos[1] + 1, le->pos[2]);
61 				VectorSet(actor2x2[2], le->pos[0] + 1, le->pos[1] + 1, le->pos[2]);
62 				if (VectorCompare(le->pos, pos)
63 				|| VectorCompare(actor2x2[0], pos)
64 				|| VectorCompare(actor2x2[1], pos)
65 				|| VectorCompare(actor2x2[2], pos))
66 					return le;
67 				break;
68 			}
69 			default:
70 				Com_Error(ERR_DROP, "Grid_MoveCalc: unknown actor-size: %i!", le->fieldSize);
71 		}
72 	}
73 
74 	return nonActor;
75 }
76 
77 /**
78  * @brief Check whether we already have actors spawned on the battlefield
79  * @sa CL_OnBattlescape
80  * @return @c true when we are in battlefield and have soldiers spawned (game is running)
81  */
CL_BattlescapeRunning(void)82 bool CL_BattlescapeRunning (void)
83 {
84 	return cl.spawned;
85 }
86 
87 /**
88  * @brief Check whether we are in a tactical mission as server or as client. But this
89  * only means that we are able to render the map - not that the game is running (the
90  * team can still be missing - see @c CL_BattlescapeRunning)
91  * @note handles multiplayer and singleplayer
92  * @sa CL_BattlescapeRunning
93  * @return true when we are in battlefield
94  */
CL_OnBattlescape(void)95 bool CL_OnBattlescape (void)
96 {
97 	/* server_state is set to zero (ss_dead) on every battlefield shutdown */
98 	if (Com_ServerState())
99 		return true; /* server */
100 
101 	/* client */
102 	if (cls.state >= ca_connected)
103 		return true;
104 
105 	return false;
106 }
107 
108 
109 #define LOOKUP_EPSILON 0.0001f
110 
111 /**
112  * @brief table for lookup_erf
113  * lookup[]= {erf(0), erf(0.1), ...}
114  */
115 static const float lookup[30]= {
116 	0.0f,    0.1125f, 0.2227f, 0.3286f, 0.4284f, 0.5205f, 0.6039f,
117 	0.6778f, 0.7421f, 0.7969f, 0.8427f, 0.8802f, 0.9103f, 0.9340f,
118 	0.9523f, 0.9661f, 0.9763f, 0.9838f, 0.9891f, 0.9928f, 0.9953f,
119 	0.9970f, 0.9981f, 0.9989f, 0.9993f, 0.9996f, 0.9998f, 0.9999f,
120 	0.9999f, 1.0000f
121 };
122 
123 /**
124  * @brief table for lookup_erf
125  * lookup[]= {10*(erf(0.1)-erf(0.0)), 10*(erf(0.2)-erf(0.1)), ...}
126  */
127 static const float lookupdiff[30]= {
128 	1.1246f, 1.1024f, 1.0592f, 0.9977f, 0.9211f, 0.8336f, 0.7395f,
129 	0.6430f, 0.5481f, 0.4579f, 0.3750f, 0.3011f, 0.2369f, 0.1828f,
130 	0.1382f, 0.1024f, 0.0744f, 0.0530f, 0.0370f, 0.0253f, 0.0170f,
131 	0.0112f, 0.0072f, 0.0045f, 0.0028f, 0.0017f, 0.0010f, 0.0006f,
132 	0.0003f, 0.0002f
133 };
134 
135 /**
136  * @brief calculate approximate erf, the error function
137  * http://en.wikipedia.org/wiki/Error_function
138  * uses lookup table and linear interpolation
139  * approximation good to around 0.001.
140  * easily good enough for the job.
141  * @param[in] z the number to calculate the erf of.
142  * @return for positive arg, returns approximate erf. for -ve arg returns 0.0f.
143  */
CL_LookupErrorFunction(float z)144 static float CL_LookupErrorFunction (float z)
145 {
146 	float ifloat;
147 	int iint;
148 
149 	/* erf(-z)=-erf(z), but erf of -ve number should never be used here
150 	 * so return 0 here */
151 	if (z < LOOKUP_EPSILON)
152 		return 0.0f;
153 	if (z > 2.8f)
154 		return 1.0f;
155 	ifloat = floor(z * 10.0f);
156 	iint = (int)ifloat;
157 	assert(iint < 30);
158 	return lookup[iint] + (z - ifloat / 10.0f) * lookupdiff[iint];
159 }
160 
CL_TestLine(const vec3_t start,const vec3_t stop,const int levelmask)161 static inline bool CL_TestLine (const vec3_t start, const vec3_t stop, const int levelmask)
162 {
163 	return TR_TestLine(cl.mapTiles, start, stop, levelmask);
164 }
165 
166 /**
167  * @brief Calculates chance to hit if the actor has a fire mode activated.
168  * @param[in] actor The local entity of the actor to calculate the hit probability for.
169  * @todo The hit probability should work somewhat differently for splash damage weapons.
170  * Since splash damage weapons can deal damage even when they don't directly hit an actor,
171  * the hit probability should be defined as the predicted percentage of the maximum splash
172  * damage of the firemode, assuming the projectile explodes at the desired location. This
173  * means that a percentage should be displayed for EVERY actor in the predicted blast
174  * radius. This will likely require specialized code.
175  */
CL_GetHitProbability(const le_t * actor)176 int CL_GetHitProbability (const le_t* actor)
177 {
178 	assert(actor);
179 	assert(actor->fd);
180 
181 	pos3_t toPos;
182 	if (IS_MODE_FIRE_PENDING(actor->actorMode))
183 		VectorCopy(actor->mousePendPos, toPos);
184 	else
185 		VectorCopy(mousePos, toPos);
186 
187 	/** @todo use LE_FindRadius */
188 	const le_t* le = LE_GetFromPos(toPos);
189 	if (!le)
190 		return 0;
191 
192 	/* Target is not visible */
193 	if  (LE_IsInvisible(le))
194 		return 0;
195 
196 	/* or suicide attempted */
197 	if (LE_IsSelected(le) && !FIRESH_IsMedikit(le->fd))
198 		return 0;
199 
200 	vec3_t shooter;
201 	VectorCopy(actor->origin, shooter);
202 	vec3_t target;
203 	VectorCopy(le->origin, target);
204 
205 	/* Calculate HitZone: */
206 	const int distx = fabs(shooter[0] - target[0]);
207 	const int disty = fabs(shooter[1] - target[1]);
208 	const float distance = sqrtf(distx * distx + disty * disty);
209 	float pseudosin;
210 	if (distx > disty)
211 		pseudosin = distance / distx;
212 	else
213 		pseudosin = distance / disty;
214 	float width = 2 * PLAYER_WIDTH * pseudosin;
215 	float height = LE_IsCrouched(le) ? PLAYER_CROUCHING_HEIGHT : PLAYER_STANDING_HEIGHT;
216 
217 	const character_t* chr = CL_ActorGetChr(actor);
218 	if (!chr)
219 		Com_Error(ERR_DROP, "No character given for local entity");
220 
221 	const float acc = GET_ACC(chr->score.skills[ABILITY_ACCURACY], actor->fd->weaponSkill ? chr->score.skills[actor->fd->weaponSkill] : 0.0);
222 
223 	const float crouch = (LE_IsCrouched(actor) && actor->fd->crouch) ? actor->fd->crouch : 1.0;
224 
225 	const float commonfactor = crouch * torad * distance * CL_ActorInjuryModifier(actor, MODIFIER_ACCURACY);
226 	const float stdevupdown = (actor->fd->spread[0] * (WEAPON_BALANCE + SKILL_BALANCE * acc)) * commonfactor;
227 	const float stdevleftright = (actor->fd->spread[1] * (WEAPON_BALANCE + SKILL_BALANCE * acc)) * commonfactor;
228 	const float hitchance = (stdevupdown > LOOKUP_EPSILON ? CL_LookupErrorFunction(height * 0.3536f / stdevupdown) : 1.0f)
229 			  * (stdevleftright > LOOKUP_EPSILON ? CL_LookupErrorFunction(width * 0.3536f / stdevleftright) : 1.0f);
230 	/* 0.3536=sqrt(2)/4 */
231 
232 	/* Calculate cover: */
233 	int n = 0;
234 	height = height / 18;
235 	width = width / 18;
236 	target[2] -= UNIT_HEIGHT / 2;
237 	target[2] += height * 9;
238 	float perpX = disty / distance * width;
239 	float perpY = 0 - distx / distance * width;
240 
241 	target[0] += perpX;
242 	perpX *= 2;
243 	target[1] += perpY;
244 	perpY *= 2;
245 	target[2] += 6 * height;
246 	if (!CL_TestLine(shooter, target, TL_FLAG_NONE))
247 		n++;
248 	target[0] += perpX;
249 	target[1] += perpY;
250 	target[2] -= 6 * height;
251 	if (!CL_TestLine(shooter, target, TL_FLAG_NONE))
252 		n++;
253 	target[0] += perpX;
254 	target[1] += perpY;
255 	target[2] += 4 * height;
256 	if (!CL_TestLine(shooter, target, TL_FLAG_NONE))
257 		n++;
258 	target[2] += 4 * height;
259 	if (!CL_TestLine(shooter, target, TL_FLAG_NONE))
260 		n++;
261 	target[0] -= perpX * 3;
262 	target[1] -= perpY * 3;
263 	target[2] -= 12 * height;
264 	if (!CL_TestLine(shooter, target, TL_FLAG_NONE))
265 		n++;
266 	target[0] -= perpX;
267 	target[1] -= perpY;
268 	target[2] += 6 * height;
269 	if (!CL_TestLine(shooter, target, TL_FLAG_NONE))
270 		n++;
271 	target[0] -= perpX;
272 	target[1] -= perpY;
273 	target[2] -= 4 * height;
274 	if (!CL_TestLine(shooter, target, TL_FLAG_NONE))
275 		n++;
276 	target[0] -= perpX;
277 	target[1] -= perpY;
278 	target[2] += 10 * height;
279 	if (!CL_TestLine(shooter, target, TL_FLAG_NONE))
280 		n++;
281 
282 	return 100 * (hitchance * (0.125) * n);
283 }
284 
285 static const float mapZBorder = -(UNIT_HEIGHT * 5);
286 /**
287  * @brief Checks whether give position is still inside the map borders
288  * @param[in] position The position to check (world coordinate)
289  * @param[in] delta The delta from the map boundaries. Positive values to make
290  * the position being earlier out of the map, negative values to let the position
291  * be later out of the map
292  * @return @c true if the given position is out of the map boundaries, @c false
293  * otherwise.
294  */
CL_OutsideMap(const vec3_t position,const float delta)295 bool CL_OutsideMap (const vec3_t position, const float delta)
296 {
297 	if (position[0] < cl.mapData->mapBox.getMinX() - delta || position[0] > cl.mapData->mapBox.getMaxX() + delta)
298 		return true;
299 
300 	if (position[1] < cl.mapData->mapBox.getMinY() - delta || position[1] > cl.mapData->mapBox.getMaxY() + delta)
301 		return true;
302 
303 	/* if a le is deeper than 5 levels below the latest walkable level (0) then
304 	 * we can assume that it is outside the world
305 	 * This is needed because some maps (e.g. the dam map) has unwalkable levels
306 	 * that just exists for detail reasons */
307 	if (position[2] < mapZBorder)
308 		return true;
309 
310 	/* still inside the map borders */
311 	return false;
312 }
313 
314 /**
315  * @brief Counts visible enemies on the battlescape
316  * @return The amount of visible enemies (from all the other teams)
317  */
CL_CountVisibleEnemies(void)318 int CL_CountVisibleEnemies (void)
319 {
320 	le_t* le;
321 	int count;
322 
323 	count = 0;
324 	le = nullptr;
325 	while ((le = LE_GetNextInUse(le))) {
326 		if (LE_IsLivingAndVisibleActor(le) && le->team != cls.team && le->team != TEAM_CIVILIAN)
327 			count++;
328 	}
329 
330 	return count;
331 }
332 
333 #ifdef DEBUG
334 /** @todo this does not belong here */
335 #include "../../common/routing.h"
336 
337 /**
338  * @brief Dumps contents of the entire client map to console for inspection.
339  * @sa CL_InitLocal
340  */
Grid_DumpWholeClientMap_f(void)341 void Grid_DumpWholeClientMap_f (void)
342 {
343 	RT_DumpWholeMap(cl.mapTiles, cl.mapData->routing);
344 }
345 
346 /**
347  * @brief Dumps contents of the entire client routing table to CSV file.
348  * @sa CL_InitLocal
349  */
Grid_DumpClientRoutes_f(void)350 void Grid_DumpClientRoutes_f (void)
351 {
352 	ipos3_t wpMins, wpMaxs;
353 	VecToPos(cl.mapData->mapBox.mins, wpMins);
354 	VecToPos(cl.mapData->mapBox.maxs, wpMaxs);
355 	RT_WriteCSVFiles(cl.mapData->routing, "ufoaiclient", wpMins, wpMaxs);
356 }
357 #endif
358 
CL_GetConfigString(int index)359 char* CL_GetConfigString (int index)
360 {
361 	if (!Com_CheckConfigStringIndex(index))
362 		Com_Error(ERR_DROP, "invalid access to configstring array with index: %i", index);
363 
364 	return cl.configstrings[index];
365 }
366 
CL_GetConfigStringInteger(int index)367 int CL_GetConfigStringInteger (int index)
368 {
369 	return atoi(CL_GetConfigString(index));
370 }
371 
CL_SetConfigString(int index,dbuffer * msg)372 char* CL_SetConfigString (int index, dbuffer* msg)
373 {
374 	if (!Com_CheckConfigStringIndex(index))
375 		Com_Error(ERR_DROP, "invalid access to configstring array with index: %i", index);
376 
377 	/* change the string in cl
378 	 * there may be overflows in i==CS_TILES - but thats ok
379 	 * see definition of configstrings and MAX_TILESTRINGS */
380 	if (index == CS_TILES || index == CS_POSITIONS)
381 		NET_ReadString(msg, cl.configstrings[index], MAX_TOKEN_CHARS * MAX_TILESTRINGS);
382 	else
383 		NET_ReadString(msg, cl.configstrings[index], sizeof(cl.configstrings[index]));
384 
385 	return cl.configstrings[index];
386 }
387