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