1 /////////////////////////////////////////
2 //
3 //             OpenLieroX
4 //
5 // code under LGPL, based on JasonBs work,
6 // enhanced by Dark Charlie and Albert Zeyer
7 //
8 //
9 /////////////////////////////////////////
10 
11 
12 // Projectile Class
13 // Created 11/2/02
14 // Jason Boettcher
15 
16 
17 #include "LieroX.h"
18 #include "CGameScript.h" // for all PRJ_* and PJ_* constants only
19 #include "GfxPrimitives.h"
20 #include "CProjectile.h"
21 #include "Protocol.h"
22 #include "CWorm.h"
23 #include "Entity.h"
24 #include "MathLib.h"
25 #include "CClient.h"
26 #include "ProfileSystem.h"
27 #include "Debug.h"
28 #include "ProjectileDesc.h"
29 #include "Physics.h"
30 #include "Geometry.h"
31 
32 
setUnused()33 void CProjectile::setUnused() {
34 	bUsed = false;
35 	timerInfo.clear();
36 
37 	onInvalidation.occurred( EventData(this) );
38 }
39 
40 ///////////////////
41 // Spawn the projectile
42 // this function is called by CClient::SpawnProjectile()
Spawn(proj_t * _proj,CVec _pos,CVec _vel,int _rot,int _owner,int _random,AbsTime time,AbsTime ignoreWormCollBeforeTime)43 void CProjectile::Spawn(proj_t *_proj, CVec _pos, CVec _vel, int _rot, int _owner, int _random, AbsTime time, AbsTime ignoreWormCollBeforeTime)
44 {
45 	if (_owner < 0 || _owner >= MAX_WORMS)
46 		iOwner = -1;
47 	else
48 		iOwner = _owner;
49 
50 	bUsed = true;
51 	tProjInfo = _proj;
52 	fLife = 0;
53 	fExtra = 0;
54 	vOldPos = _pos;
55 	vPos = _pos;
56 	vVelocity = _vel;
57 	fRotation = (float)_rot;
58 	radius.x = tProjInfo->Width / 2;
59 	radius.y = tProjInfo->Height / 2;
60 	health = 100;
61 
62 	fLastTrailProj = AbsTime();
63 	iRandom = _random;
64     iFrameX = 0;
65 	fIgnoreWormCollBeforeTime = ignoreWormCollBeforeTime;
66 
67     fTimeVarRandom = GetFixedRandomNum(iRandom);
68 	fLastSimulationTime = time;
69 	fSpawnTime = time;
70 	timerInfo.clear();
71 
72 	fSpeed = _vel.GetLength();
73 	CalculateCheckSteps();
74 
75 	fFrame = 0;
76 	bFrameDelta = true;
77 
78 	firstbounce = true;
79 
80 	switch(tProjInfo->Type) {
81 		case PRJ_RECT:
82 		case PRJ_PIXEL:
83 		case PRJ_CIRCLE:
84 		case PRJ_POLYGON: {
85 			// Choose a colour
86 			if(tProjInfo->Colour.size() > 0) {
87 				int c = GetRandomInt(tProjInfo->Colour.size()-1);
88 				iColour = tProjInfo->Colour[c];
89 			}
90 			else {
91 				// don't give the warning here, give it while loading GS
92 				iColour = Color();
93 			}
94 			break;
95 		}
96 		case PRJ_IMAGE: break;
97 		case __PRJ_LBOUND: case __PRJ_UBOUND: errors << "CProjectile::DrawShadow: hit __PRJ_BOUND" << endl;
98 	}
99 
100 
101 	fWallshootTime = 0.011f + getRandomFloat() / 1000; // Support wallshooting - ignore collisions before this time
102 
103 	// TODO: the check was tProjInfo->Type != PJ_BOUNCE before, which didn't make sense. is it correct now?
104 	// TODO: fGravity != 0 => bChangesSpeed=false was here before. why?
105 	bChangesSpeed = ((int)tProjInfo->Dampening == 1)
106 		&& (tProjInfo->Hit.Type != PJ_BOUNCE || (int)tProjInfo->Hit.BounceCoeff == 1);  // Changes speed on bounce
107 
108 	updateCollMapInfo();
109 }
110 
111 
112 ///////////////////
113 // Gets a random float from a special list
114 // TODO: how does this belong to projectiles? move it perhaps out here
getRandomFloat()115 float CProjectile::getRandomFloat()
116 {
117 	float r = GetFixedRandomNum(iRandom++);
118 
119 	iRandom %= 255;
120 
121 	return r;
122 }
123 
124 
125 //////////////////////
126 // Pre-calculates the check steps for collisions
CalculateCheckSteps()127 void CProjectile::CalculateCheckSteps()
128 {
129 	MIN_CHECKSTEP = 4;
130 	MAX_CHECKSTEP = 6;
131 	AVG_CHECKSTEP = 4;
132 
133 	iCheckSpeedLen = (int)vVelocity.GetLength2();
134 	if (iCheckSpeedLen < 14000)  {
135 		MIN_CHECKSTEP = 0;
136 		MAX_CHECKSTEP = 3;
137 		AVG_CHECKSTEP = 2;
138 	} else if (iCheckSpeedLen < 75000)  {
139 		MIN_CHECKSTEP = 0;
140 		MAX_CHECKSTEP = 4;
141 		AVG_CHECKSTEP = 2;
142 	} else if (iCheckSpeedLen < 250000)  {
143 		MIN_CHECKSTEP = 1;
144 		MAX_CHECKSTEP = 5;
145 		AVG_CHECKSTEP = 2;
146 
147 	// HINT: add or substract some small random number for high speeds, it behaves more like original LX
148 	} else {
149 		int rnd = (getRandomIndex() % 3);
150 		rnd *= SIGN(getRandomFloat());
151 		MIN_CHECKSTEP = 6;
152 		if (tProjInfo->Hit.Type == PJ_BOUNCE)  { // HINT: this avoids fast bouncing projectiles to stay in a wall too often (for example zimm)
153 			MAX_CHECKSTEP = 2;
154 			AVG_CHECKSTEP = 2;
155 		} else {
156 			MAX_CHECKSTEP = 9 + rnd;
157 			AVG_CHECKSTEP = 6 + rnd;
158 		}
159 	}
160 
161 	MAX_CHECKSTEP2 = MAX_CHECKSTEP * MAX_CHECKSTEP;
162 	MIN_CHECKSTEP2 = MIN_CHECKSTEP * MIN_CHECKSTEP;
163 }
164 
165 
166 ///////////////////
167 // Check for a collision (static version; doesnt do anything else then checking)
168 // Returns true if there was a collision, otherwise false is returned
169 // This is not used anywhere within physics, it's just for AI (and also not very correct; it ignored radius)
CheckCollision(proj_t * tProjInfo,float dt,CVec pos,CVec vel)170 int CProjectile::CheckCollision(proj_t* tProjInfo, float dt, CVec pos, CVec vel)
171 {
172 	// Check if it hit the terrain
173 	CMap* map = cClient->getMap();
174 	int mw = map->GetWidth();
175 	int mh = map->GetHeight();
176 	int w,h;
177 
178 	if(tProjInfo->Type == PRJ_PIXEL)
179 		w=h=1;
180 	else
181 		w=h=2;
182 
183 	float maxspeed2 = (float)(4*w*w+4*w+1); // (2w+1)^2
184 	if( (vel*dt).GetLength2() > maxspeed2) {
185 		dt *= 0.5f;
186 
187 		int col = CheckCollision(tProjInfo,dt,pos,vel);
188 		if(col) return col;
189 
190 		pos += vel*dt;
191 
192 		return CheckCollision(tProjInfo,dt,pos,vel);
193 	}
194 
195 	pos += vel*dt;
196 
197 	int px = (int)pos.x;
198 	int py = (int)pos.y;
199 
200 	// Hit edges
201 	if(px-w<0 || py-h<0 || px+w>=mw || py+h>=mh)
202 		return PJC_TERRAIN|PJC_MAPBORDER;
203 
204 	const uchar* gridflags = map->getAbsoluteGridFlags();
205 	int grid_w = map->getGridWidth();
206 	int grid_h = map->getGridHeight();
207 	int grid_cols = map->getGridCols();
208 	if(grid_w < 2*w+1 || grid_h < 2*h+1 // this ensures, that this check is safe
209 	|| gridflags[((py-h)/grid_h)*grid_cols + (px-w)/grid_w] & (PX_ROCK|PX_DIRT)
210 	|| gridflags[((py+h)/grid_h)*grid_cols + (px-w)/grid_w] & (PX_ROCK|PX_DIRT)
211 	|| gridflags[((py-h)/grid_h)*grid_cols + (px+w)/grid_w] & (PX_ROCK|PX_DIRT)
212 	|| gridflags[((py+h)/grid_h)*grid_cols + (px+w)/grid_w] & (PX_ROCK|PX_DIRT))
213 	for(int y=py-h;y<=py+h;y++) {
214 
215 		uchar *pf = map->GetPixelFlags() + y*mw + px-w;
216 
217 		for(int x=px-w;x<=px+w;x++) {
218 
219 
220 			if(!(*pf & PX_EMPTY))
221 				return PJC_TERRAIN;
222 
223 			pf++;
224 		}
225 	}
226 
227 
228 	// No collision
229 	return 0;
230 }
231 
232 ///////////////////
233 // Draw the projectile
Draw(SDL_Surface * bmpDest,CViewport * view)234 void CProjectile::Draw(SDL_Surface * bmpDest, CViewport *view)
235 {
236 	CMap* map = cClient->getMap();
237 	VectorD2<int> p = view->physicToReal(vPos, cClient->getGameLobby()->features[FT_InfiniteMap], map->GetWidth(), map->GetHeight());
238 
239     switch (tProjInfo->Type) {
240 		case PRJ_PIXEL:
241 			if(view->posInside(p))
242 				DrawRectFill2x2(bmpDest, p.x - 1, p.y - 1,iColour);
243 			return;
244 
245 		case PRJ_IMAGE:  {
246 
247 			if(tProjInfo->bmpImage == NULL)
248 				return;
249 
250 			float framestep = 0;
251 
252 			if (view->posInside(p))  {  // HINT: this is how it was in old LX, the projectile is not animated/destroyed when out of the screen
253 				if(tProjInfo->Animating)
254 					framestep = fFrame;
255 
256 				// Special angle
257 				// Basically another way of organising the angles in images
258 				// Straight up is in the middle, rotating left goes left, rotating right goes right in terms
259 				// of image index's from the centre
260 				else if(tProjInfo->UseSpecAngle) {
261 					CVec dir = vVelocity;
262 					float angle = (float)( -atan2(dir.x,dir.y) * (180.0f/PI) );
263 					int direct = 0;
264 
265 					if(angle > 0)
266 						angle=180-angle;
267 					if(angle < 0) {
268 						angle=180+angle;
269 						direct = 1;
270 					}
271 					if(angle == 0)
272 						direct = 0;
273 
274 
275 					int num = (tProjInfo->AngleImages - 1) / 2;
276 					if(direct == 0)
277 						// Left side
278 						framestep = (float)(151-angle) / 151.0f * (float)num;
279 					else {
280 						// Right side
281 						framestep = (float)angle / 151.0f * (float)num;
282 						framestep += num+1;
283 					}
284 				}
285 
286 				// Directed in the direction the projectile is travelling
287 				else if(tProjInfo->UseAngle) {
288 					CVec dir = vVelocity;
289 					float angle = (float)( -atan2(dir.x,dir.y) * (180.0f/PI) );
290 					float offset = 360.0f / (float)tProjInfo->AngleImages;
291 
292 					FMOD(angle, 360.0f);
293 
294 					framestep = angle / offset;
295 				}
296 
297 				// Spinning projectile only when moving
298 				else if(tProjInfo->RotIncrement != 0 && tProjInfo->Rotating && (vVelocity.x != 0 || vVelocity.y != 0))
299 					framestep = fRotation / (float)tProjInfo->RotIncrement;
300 
301 			}
302 
303 			const int size = tProjInfo->bmpImage->h;
304 			const int half = size/2;
305 			iFrameX = (int)framestep*size;
306 			MOD(iFrameX, tProjInfo->bmpImage->w);
307 
308 			DrawImageAdv(bmpDest, tProjInfo->bmpImage, iFrameX, 0, p.x-half, p.y-half, size,size);
309 
310 			return;
311 		}
312 
313 		case PRJ_CIRCLE:
314 			DrawCircleFilled(bmpDest, p.x, p.y, radius.x*2, radius.y*2, iColour);
315 			return;
316 
317 		case PRJ_RECT:
318 			DrawRectFill(bmpDest, p.x - radius.x*2, p.y - radius.y*2, p.x + radius.x*2, p.y + radius.x*2, iColour);
319 			return;
320 
321 		case PRJ_POLYGON:
322 			getProjInfo()->polygon.drawFilled(bmpDest, (int)vPos.x, (int)vPos.y, view, iColour);
323 			return;
324 
325 		case __PRJ_LBOUND: case __PRJ_UBOUND: errors << "CProjectile::Draw: hit __PRJ_BOUND" << endl;
326 	}
327 }
328 
329 
330 ///////////////////
331 // Draw the projectiles shadow
DrawShadow(SDL_Surface * bmpDest,CViewport * view)332 void CProjectile::DrawShadow(SDL_Surface * bmpDest, CViewport *view)
333 {
334 	if (tLX->fDeltaTime >= 0.1f) // Don't draw projectile shadows with FPS <= 10 to get a little better performance
335 		return;
336 
337 	CMap* map = cClient->getMap();
338 
339 	// TODO: DrawObjectShadow is a bit complicated to fix for shadows&tiling, so I just leave all shadows away for now...
340 	if(!view->physicsInside(vPos /*, cClient->getGameLobby()->features[FT_InfiniteMap], map->GetWidth(), map->GetHeight() */))
341 		return;
342 
343 	switch (tProjInfo->Type)  {
344 
345 		// Pixel
346 		case PRJ_PIXEL:
347 			map->DrawPixelShadow(bmpDest, view, (int)vPos.x, (int)vPos.y);
348 			break;
349 
350 		// Image
351 		case PRJ_IMAGE:  {
352 
353 			if(tProjInfo->bmpImage == NULL)
354 				return;
355 			/*if (tProjInfo->bmpImage.get()->w <= 2 && tProjInfo->bmpImage.get()->h <= 2)  {
356 				map->DrawPixelShadow(bmpDest, view, (int)vPos.x, (int)vPos.y);
357 				return;
358 			}*/
359 
360 			int size = tProjInfo->bmpImage->h;
361 			int half = size / 2;
362 			map->DrawObjectShadow(bmpDest, tProjInfo->bmpImage, tProjInfo->bmpShadow.get(), iFrameX, 0, size,size, view, (int)vPos.x-(half>>1), (int)vPos.y-(half>>1));
363 
364 			break;
365 		}
366 
367 		case PRJ_CIRCLE:
368 		case PRJ_POLYGON:
369 		case PRJ_RECT:
370 			// TODO ...
371 			break;
372 
373 		case __PRJ_LBOUND: case __PRJ_UBOUND: errors << "CProjectile::DrawShadow: hit __PRJ_BOUND" << endl;
374     }
375 }
376 
377 
378 ///////////////////
379 // Bounce
380 // HINT: this is not exactly the way the original LX did it,
381 //		but this way is way more correct and it seems to work OK
382 //		(original LX resets the bounce-direction on each checked side)
Bounce(float fCoeff)383 void CProjectile::Bounce(float fCoeff)
384 {
385 	float x,y;
386 	x=y=1;
387 
388 	// This code is right, it should be done like that
389 	// However, we want to keep compatibility with .56 and when on each client would be another simulation,
390 	// we couldn't call that compatibility at all
391 
392 	// For now we just keep the old, wrong code, so noone will call OLX players as cheaters
393 /*	if(CollisionSide & (COL_TOP|COL_BOTTOM)) {
394 		y=-y;
395 	}
396 	if(CollisionSide & (COL_LEFT|COL_RIGHT)) {
397 		x=-x;
398 	}
399 
400 	if(CollisionSide & COL_TOP) {
401 		y*=fCoeff;
402 	}
403 	if(CollisionSide & COL_BOTTOM) {
404 		y*=fCoeff;
405 	}
406 	if(CollisionSide & COL_LEFT) {
407 		x*=fCoeff;
408 	}
409 	if(CollisionSide & COL_RIGHT) {
410 		x*=fCoeff;
411 	}*/
412 
413 
414 	// WARNING: this code should not be used; it is simply wrong
415 	//	(this was the way the original LX did it)
416 
417 	if (CollisionSide & COL_TOP)  {
418 		x = fCoeff;
419 		y = -fCoeff;
420 	}
421 	if (CollisionSide & COL_BOTTOM)  {
422 		x = fCoeff;
423 		y = -fCoeff;
424 	}
425 	if (CollisionSide & COL_LEFT)  {
426 		x = -fCoeff;
427 		y = fCoeff;
428 	}
429 	if (CollisionSide & COL_RIGHT)  {
430 		x = -fCoeff;
431 		y = fCoeff;
432 	}
433 
434 
435 	vVelocity.x *= x;
436 	vVelocity.y *= y;
437 }
438 
439 
440 
441 
442 
443 template<bool TOP, bool LEFT>
MPI(const VectorD2<int> & p,const VectorD2<int> & r)444 static CClient::MapPosIndex MPI(const VectorD2<int>& p, const VectorD2<int>& r) {
445 	return CClient::MapPosIndex( p + VectorD2<int>(LEFT ? -r.x : r.x, TOP ? -r.y : r.y) );
446 }
447 
448 template<bool INSERT>
updateMap(CProjectile * prj,const VectorD2<int> & p,const VectorD2<int> & r)449 static void updateMap(CProjectile* prj, const VectorD2<int>& p, const VectorD2<int>& r) {
450 	for(int x = MPI<true,true>(p,r).x; x <= MPI<true,false>(p,r).x; ++x)
451 		for(int y = MPI<true,true>(p,r).y; y <= MPI<false,true>(p,r).y; ++y) {
452 			CClient::ProjectileSet* projs = cClient->projPosMap[CClient::MapPosIndex(x,y).index(cClient->getMap())];
453 			if(projs == NULL) continue;
454 			if(INSERT)
455 				projs->insert(prj);
456 			else
457 				projs->erase(prj);
458 		}
459 }
460 
updateCollMapInfo(const VectorD2<int> * oldPos,const VectorD2<int> * oldRadius)461 void CProjectile::updateCollMapInfo(const VectorD2<int>* oldPos, const VectorD2<int>* oldRadius) {
462 	if(!cClient->getGameScript()->getNeedCollisionInfo()) return;
463 
464 	if(!isUsed()) { // not used anymore
465 		if(oldPos && oldRadius)
466 			updateMap<false>(this, *oldPos, *oldRadius);
467 		return;
468 	}
469 
470 	if(oldPos && oldRadius) {
471 		if(
472 		   (MPI<true,true>(*oldPos,*oldRadius) == MPI<true,true>(vPos,radius)) &&
473 		   (MPI<true,false>(*oldPos,*oldRadius) == MPI<true,false>(vPos,radius)) &&
474 		   (MPI<false,true>(*oldPos,*oldRadius) == MPI<false,true>(vPos,radius)) &&
475 		   (MPI<false,false>(*oldPos,*oldRadius) == MPI<false,false>(vPos,radius))) {
476 			return; // nothing has changed
477 		}
478 
479 		// delete from all
480 		updateMap<false>(this, *oldPos, *oldRadius);
481 	}
482 
483 	// add to all
484 	updateMap<true>(this, vPos, radius);
485 }
486