1 /* bzflag
2 * Copyright (c) 1993-2021 Tim Riker
3 *
4 * This package is free software; you can redistribute it and/or
5 * modify it under the terms of the license found in the file
6 * named COPYING that should have accompanied this file.
7 *
8 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11 */
12
13 /*
14 *
15 */
16
17 // interface header
18 #include "RobotPlayer.h"
19
20 // common implementation headers
21 #include "BZDBCache.h"
22
23 // local implementation headers
24 #include "World.h"
25 #include "Intersect.h"
26 #include "TargetingUtils.h"
27
28 std::vector<BzfRegion*>* RobotPlayer::obstacleList = NULL;
29
RobotPlayer(const PlayerId & _id,const char * _name,ServerLink * _server,const char * _motto="")30 RobotPlayer::RobotPlayer(const PlayerId& _id, const char* _name,
31 ServerLink* _server,
32 const char* _motto = "") :
33 LocalPlayer(_id, _name, _motto),
34 target(NULL),
35 pathIndex(0),
36 timerForShot(0.0f),
37 drivingForward(true)
38 {
39 gettingSound = false;
40 server = _server;
41 }
42
43 // estimate a player's position at now+t, similar to dead reckoning
projectPosition(const Player * targ,const float t,float & x,float & y,float & z) const44 void RobotPlayer::projectPosition(const Player *targ,const float t,float &x,float &y,float &z) const
45 {
46 double hisx=targ->getPosition()[0];
47 double hisy=targ->getPosition()[1];
48 double hisz=targ->getPosition()[2];
49 double hisvx=targ->getVelocity()[0];
50 double hisvy=targ->getVelocity()[1];
51 double hisvz=targ->getVelocity()[2];
52 double omega=fabs(targ->getAngularVelocity());
53 double sx,sy;
54
55 if ((targ->getStatus() & PlayerState::Falling) || fabs(omega) < 2*M_PI / 360 * 0.5)
56 {
57 sx=t*hisvx;
58 sy=t*hisvy;
59 }
60 else
61 {
62 double hisspeed = hypotf(hisvx, hisvy);
63 double alfa = omega * t;
64 double r = hisspeed / fabs(omega);
65 double dx = r * sin(alfa);
66 double dy2 = r * (1 - cos(alfa));
67 double beta = atan2(dy2, dx) * (targ->getAngularVelocity() > 0 ? 1 : -1);
68 double gamma = atan2(hisvy, hisvx);
69 double rho = gamma+beta;
70 sx = hisspeed * t * cos(rho);
71 sy = hisspeed * t * sin(rho);
72 }
73 x=(float)hisx+(float)sx;
74 y=(float)hisy+(float)sy;
75 z=(float)hisz+(float)hisvz*t;
76 if (targ->getStatus() & PlayerState::Falling)
77 z += 0.5f * BZDBCache::gravity * t * t;
78 if (z < 0) z = 0;
79 }
80
81
82 // get coordinates to aim at when shooting a player; steps:
83 // 1. estimate how long it will take shot to hit target
84 // 2. calc position of target at that point of time
85 // 3. jump to 1., using projected position, loop until result is stable
getProjectedPosition(const Player * targ,float * projpos) const86 void RobotPlayer::getProjectedPosition(const Player *targ, float *projpos) const
87 {
88 double myx = getPosition()[0];
89 double myy = getPosition()[1];
90 double hisx = targ->getPosition()[0];
91 double hisy = targ->getPosition()[1];
92 double deltax = hisx - myx;
93 double deltay = hisy - myy;
94 double distance = hypotf(deltax,deltay) - BZDB.eval(StateDatabase::BZDB_MUZZLEFRONT) - BZDBCache::tankRadius;
95 if (distance <= 0) distance = 0;
96 double shotspeed = BZDB.eval(StateDatabase::BZDB_SHOTSPEED)*
97 (getFlag() == Flags::Laser ? BZDB.eval(StateDatabase::BZDB_LASERADVEL) :
98 getFlag() == Flags::RapidFire ? BZDB.eval(StateDatabase::BZDB_RFIREADVEL) :
99 getFlag() == Flags::MachineGun ? BZDB.eval(StateDatabase::BZDB_MGUNADVEL) : 1) +
100 hypotf(getVelocity()[0], getVelocity()[1]);
101
102 double errdistance = 1.0;
103 float tx, ty, tz;
104 for (int tries=0 ; errdistance > 0.05 && tries < 4 ; tries++)
105 {
106 float t = (float)distance / (float)shotspeed;
107 projectPosition(targ, t + 0.05f, tx, ty, tz); // add 50ms for lag
108 double distance2 = hypotf(tx - myx, ty - myy);
109 errdistance = fabs(distance2-distance) / (distance + ZERO_TOLERANCE);
110 distance = distance2;
111 }
112 projpos[0] = tx;
113 projpos[1] = ty;
114 projpos[2] = tz;
115
116 // projected pos in building -> use current pos
117 if (World::getWorld()->inBuilding(projpos, 0.0f, BZDBCache::tankHeight))
118 {
119 projpos[0] = targ->getPosition()[0];
120 projpos[1] = targ->getPosition()[1];
121 projpos[2] = targ->getPosition()[2];
122 }
123 }
124
125
doUpdate(float dt)126 void RobotPlayer::doUpdate(float dt)
127 {
128 LocalPlayer::doUpdate(dt);
129
130 float tankRadius = BZDBCache::tankRadius;
131 const float shotRange = BZDB.eval(StateDatabase::BZDB_SHOTRANGE);
132 const float shotRadius = BZDB.eval(StateDatabase::BZDB_SHOTRADIUS);
133
134 // fire shot if any available
135 timerForShot -= dt;
136 if (timerForShot < 0.0f)
137 timerForShot = 0.0f;
138
139 if (getFiringStatus() != Ready)
140 return;
141
142 bool shoot = false;
143 const float azimuth = getAngle();
144 // Allow shooting only if angle is near and timer has elapsed
145 if ((!path.empty()) && timerForShot <= 0.0f)
146 {
147 float p1[3];
148 getProjectedPosition(target, p1);
149 const float* p2 = getPosition();
150 float shootingAngle = atan2f(p1[1] - p2[1], p1[0] - p2[0]);
151 if (shootingAngle < 0.0f)
152 shootingAngle += (float)(2.0 * M_PI);
153 float azimuthDiff = shootingAngle - azimuth;
154 if (azimuthDiff > M_PI)
155 azimuthDiff -= (float)(2.0 * M_PI);
156 else if (azimuthDiff < -M_PI)
157 azimuthDiff += (float)(2.0 * M_PI);
158
159 const float targetdistance = hypotf(p1[0] - p2[0], p1[1] - p2[1]) -
160 BZDB.eval(StateDatabase::BZDB_MUZZLEFRONT) - tankRadius;
161
162 const float missby = fabs(azimuthDiff) *
163 (targetdistance - BZDBCache::tankLength);
164 // only shoot if we miss by less than half a tanklength and no building inbetween
165 if (missby < 0.5f * BZDBCache::tankLength &&
166 p1[2] < shotRadius)
167 {
168 float pos[3] = {getPosition()[0], getPosition()[1],
169 getPosition()[2] + BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT)
170 };
171 float dir[3] = {cosf(azimuth), sinf(azimuth), 0.0f};
172 Ray tankRay(pos, dir);
173 float maxdistance = targetdistance;
174 if (!ShotStrategy::getFirstBuilding(tankRay, -0.5f, maxdistance))
175 {
176 shoot=true;
177 // try to not aim at teammates
178 for (int i=0; i <= World::getWorld()->getCurMaxPlayers(); i++)
179 {
180 Player *p = 0;
181 if (i < World::getWorld()->getCurMaxPlayers())
182 p = World::getWorld()->getPlayer(i);
183 else
184 p = LocalPlayer::getMyTank();
185 if (!p || p->getId() == getId() || validTeamTarget(p) ||
186 !p->isAlive()) continue;
187 float relpos[3] = {getPosition()[0] - p->getPosition()[0],
188 getPosition()[1] - p->getPosition()[1],
189 getPosition()[2] - p->getPosition()[2]
190 };
191 Ray ray(relpos, dir);
192 float impact = rayAtDistanceFromOrigin(ray, 5 * BZDBCache::tankRadius);
193 if (impact > 0 && impact < shotRange)
194 {
195 shoot=false;
196 timerForShot = 0.1f;
197 break;
198 }
199 }
200 if (shoot && fireShot())
201 {
202 // separate shot by 0.2 - 0.8 sec (experimental value)
203 timerForShot = float(bzfrand()) * 0.6f + 0.2f;
204 }
205 }
206 }
207 }
208 }
209
doUpdateMotion(float dt)210 void RobotPlayer::doUpdateMotion(float dt)
211 {
212 if (isAlive())
213 {
214 bool evading = false;
215 // record previous position
216 const float oldAzimuth = getAngle();
217 const float* oldPosition = getPosition();
218 float position[3];
219 position[0] = oldPosition[0];
220 position[1] = oldPosition[1];
221 position[2] = oldPosition[2];
222 float azimuth = oldAzimuth;
223 float tankAngVel = BZDB.eval(StateDatabase::BZDB_TANKANGVEL);
224 float tankSpeed = BZDBCache::tankSpeed;
225
226
227 // basically a clone of Roger's evasive code
228 for (int t=0; t <= World::getWorld()->getCurMaxPlayers(); t++)
229 {
230 Player *p = 0;
231 if (t < World::getWorld()->getCurMaxPlayers())
232 p = World::getWorld()->getPlayer(t);
233 else
234 p = LocalPlayer::getMyTank();
235 if (!p || p->getId() == getId())
236 continue;
237 const int maxShots = p->getMaxShots();
238 for (int s = 0; s < maxShots; s++)
239 {
240 ShotPath* shot = p->getShot(s);
241 if (!shot || shot->isExpired())
242 continue;
243 // ignore invisible bullets completely for now (even when visible)
244 if (shot->getFlag() == Flags::InvisibleBullet)
245 continue;
246
247 const float* shotPos = shot->getPosition();
248 if ((fabs(shotPos[2] - position[2]) > BZDBCache::tankHeight) && (shot->getFlag() != Flags::GuidedMissile))
249 continue;
250 const float dist = TargetingUtils::getTargetDistance(position, shotPos);
251 if (dist < 150.0f)
252 {
253 const float *shotVel = shot->getVelocity();
254 float shotAngle = atan2f(shotVel[1], shotVel[0]);
255 float shotUnitVec[2] = {cosf(shotAngle), sinf(shotAngle)};
256
257 float trueVec[2] = {(position[0]-shotPos[0])/dist,(position[1]-shotPos[1])/dist};
258 float dotProd = trueVec[0]*shotUnitVec[0]+trueVec[1]*shotUnitVec[1];
259
260 if (dotProd > 0.97f)
261 {
262 float rotation;
263 float rotation1 = (float)((shotAngle + M_PI/2.0) - azimuth);
264 if (rotation1 < -1.0f * M_PI) rotation1 += (float)(2.0 * M_PI);
265 if (rotation1 > 1.0f * M_PI) rotation1 -= (float)(2.0 * M_PI);
266
267 float rotation2 = (float)((shotAngle - M_PI/2.0) - azimuth);
268 if (rotation2 < -1.0f * M_PI) rotation2 += (float)(2.0 * M_PI);
269 if (rotation2 > 1.0f * M_PI) rotation2 -= (float)(2.0 * M_PI);
270
271 if (fabs(rotation1) < fabs(rotation2))
272 rotation = rotation1;
273 else
274 rotation = rotation2;
275 setDesiredSpeed(1.0f);
276 setDesiredAngVel(rotation);
277 evading = true;
278 }
279 }
280 }
281 }
282
283 // when we are not evading, follow the path
284 if (!evading && dt > 0.0 && pathIndex < (int)path.size())
285 {
286 float distance;
287 float v[2];
288 const float* endPoint = path[pathIndex].get();
289 // find how long it will take to get to next path segment
290 v[0] = endPoint[0] - position[0];
291 v[1] = endPoint[1] - position[1];
292 distance = hypotf(v[0], v[1]);
293 float tankRadius = BZDBCache::tankRadius;
294 // smooth path a little by turning early at corners, might get us stuck, though
295 if (distance <= 2.5f * tankRadius)
296 pathIndex++;
297
298 float segmentAzimuth = atan2f(v[1], v[0]);
299 float azimuthDiff = segmentAzimuth - azimuth;
300 if (azimuthDiff > M_PI) azimuthDiff -= (float)(2.0 * M_PI);
301 else if (azimuthDiff < -M_PI) azimuthDiff += (float)(2.0 * M_PI);
302 if (fabs(azimuthDiff) > 0.01f)
303 {
304 // drive backward when target is behind, try to stick to last direction
305 if (drivingForward)
306 drivingForward = fabs(azimuthDiff) < M_PI/2*0.9 ? true : false;
307 else
308 drivingForward = fabs(azimuthDiff) < M_PI/2*0.3 ? true : false;
309 setDesiredSpeed(drivingForward ? 1.0f : -1.0f);
310 // set desired turn speed
311 if (azimuthDiff >= dt * tankAngVel)
312 setDesiredAngVel(1.0f);
313 else if (azimuthDiff <= -dt * tankAngVel)
314 setDesiredAngVel(-1.0f);
315 else
316 setDesiredAngVel(azimuthDiff / dt / tankAngVel);
317 }
318 else
319 {
320 drivingForward = true;
321 // tank doesn't turn while moving forward
322 setDesiredAngVel(0.0f);
323 // find how long it will take to get to next path segment
324 if (distance <= dt * tankSpeed)
325 {
326 pathIndex++;
327 // set desired speed
328 setDesiredSpeed(distance / dt / tankSpeed);
329 }
330 else
331 setDesiredSpeed(1.0f);
332 }
333 }
334 }
335 LocalPlayer::doUpdateMotion(dt);
336 }
337
explodeTank()338 void RobotPlayer::explodeTank()
339 {
340 LocalPlayer::explodeTank();
341 target = NULL;
342 path.clear();
343 }
344
restart(const float * pos,float _azimuth)345 void RobotPlayer::restart(const float* pos, float _azimuth)
346 {
347 LocalPlayer::restart(pos, _azimuth);
348 // no target
349 path.clear();
350 target = NULL;
351 pathIndex = 0;
352
353 }
354
getTargetPriority(const Player * _target) const355 float RobotPlayer::getTargetPriority(const
356 Player* _target) const
357 {
358 // don't target teammates or myself
359 if (!this->validTeamTarget(_target))
360 return 0.0f;
361
362 // go after closest player
363 // FIXME -- this is a pretty stupid heuristic
364 const float worldSize = BZDBCache::worldSize;
365 const float* p1 = getPosition();
366 const float* p2 = _target->getPosition();
367
368 float basePriority = 1.0f;
369 // give bonus to non-paused player
370 if (!_target->isPaused())
371 basePriority += 2.0f;
372 // give bonus to non-deadzone targets
373 if (obstacleList)
374 {
375 float nearest[2];
376 const BzfRegion* targetRegion = findRegion (p2, nearest);
377 if (targetRegion && targetRegion->isInside(p2))
378 basePriority += 1.0f;
379 }
380 return basePriority
381 - 0.5f * hypotf(p2[0] - p1[0], p2[1] - p1[1]) / worldSize;
382 }
383
setObstacleList(std::vector<BzfRegion * > * _obstacleList)384 void RobotPlayer::setObstacleList(std::vector<BzfRegion*>*
385 _obstacleList)
386 {
387 obstacleList = _obstacleList;
388 }
389
getTarget() const390 const Player* RobotPlayer::getTarget() const
391 {
392 return target;
393 }
394
setTarget(const Player * _target)395 void RobotPlayer::setTarget(const Player* _target)
396 {
397 static int mailbox = 0;
398
399 path.clear();
400 target = _target;
401 if (!target) return;
402
403 // work backwards (from target to me)
404 float proj[3];
405 getProjectedPosition(target, proj);
406 const float *p1 = proj;
407 const float* p2 = getPosition();
408 float q1[2], q2[2];
409 BzfRegion* headRegion = findRegion(p1, q1);
410 BzfRegion* tailRegion = findRegion(p2, q2);
411 if (!headRegion || !tailRegion)
412 {
413 // if can't reach target then forget it
414 return;
415 }
416
417 mailbox++;
418 headRegion->setPathStuff(0.0f, NULL, q1, mailbox);
419 RegionPriorityQueue queue;
420 queue.insert(headRegion, 0.0f);
421 BzfRegion* next;
422 while (!queue.isEmpty() && (next = queue.remove()) != tailRegion)
423 findPath(queue, next, tailRegion, q2, mailbox);
424
425 // get list of points to go through to reach the target
426 next = tailRegion;
427 do
428 {
429 p1 = next->getA();
430 path.push_back(p1);
431 next = next->getTarget();
432 }
433 while (next && next != headRegion);
434 if (next || tailRegion == headRegion)
435 path.push_back(q1);
436 else
437 path.clear();
438 pathIndex = 0;
439 }
440
findRegion(const float p[2],float nearest[2]) const441 BzfRegion* RobotPlayer::findRegion(const float p[2],
442 float nearest[2]) const
443 {
444 nearest[0] = p[0];
445 nearest[1] = p[1];
446 const int count = obstacleList->size();
447 for (int o = 0; o < count; o++)
448 if ((*obstacleList)[o]->isInside(p))
449 return (*obstacleList)[o];
450
451 // point is outside: find nearest region
452 float distance = maxDistance;
453 BzfRegion* nearestRegion = NULL;
454 for (int i = 0; i < count; i++)
455 {
456 float currNearest[2];
457 float currDistance = (*obstacleList)[i]->getDistance(p, currNearest);
458 if (currDistance < distance)
459 {
460 nearestRegion = (*obstacleList)[i];
461 distance = currDistance;
462 nearest[0] = currNearest[0];
463 nearest[1] = currNearest[1];
464 }
465 }
466 return nearestRegion;
467 }
468
getRegionExitPoint(const float p1[2],const float p2[2],const float a[2],const float targetPoint[2],float mid[2],float & priority)469 float RobotPlayer::getRegionExitPoint(
470 const float p1[2], const float p2[2],
471 const float a[2], const float targetPoint[2],
472 float mid[2], float& priority)
473 {
474 float b[2];
475 b[0] = targetPoint[0] - a[0];
476 b[1] = targetPoint[1] - a[1];
477 float d[2];
478 d[0] = p2[0] - p1[0];
479 d[1] = p2[1] - p1[1];
480
481 float vect = d[0] * b[1] - d[1] * b[0];
482 float t = 0.0f; // safe value
483 if (fabs(vect) > ZERO_TOLERANCE)
484 {
485 // compute intersection along (p1,d) with (a,b)
486 t = (a[0] * b[1] - a[1] * b[0] - p1[0] * b[1] + p1[1] * b[0]) / vect;
487 if (t > 1.0f)
488 t = 1.0f;
489 else if (t < 0.0f)
490 t = 0.0f;
491 }
492
493 mid[0] = p1[0] + t * d[0];
494 mid[1] = p1[1] + t * d[1];
495
496 const float distance = hypotf(a[0] - mid[0], a[1] - mid[1]);
497 // priority is to minimize distance traveled and distance left to go
498 priority = distance + hypotf(targetPoint[0] - mid[0], targetPoint[1] - mid[1]);
499 // return distance traveled
500 return distance;
501 }
502
findPath(RegionPriorityQueue & queue,BzfRegion * region,BzfRegion * targetRegion,const float targetPoint[2],int mailbox)503 void RobotPlayer::findPath(RegionPriorityQueue& queue,
504 BzfRegion* region,
505 BzfRegion* targetRegion,
506 const float targetPoint[2],
507 int mailbox)
508 {
509 const int numEdges = region->getNumSides();
510 for (int i = 0; i < numEdges; i++)
511 {
512 BzfRegion* neighbor = region->getNeighbor(i);
513 if (!neighbor) continue;
514
515 const float* p1 = region->getCorner(i).get();
516 const float* p2 = region->getCorner((i+1)%numEdges).get();
517 float mid[2], priority;
518 float total = getRegionExitPoint(p1, p2, region->getA(),
519 targetPoint, mid, priority);
520 priority += region->getDistance();
521 if (neighbor == targetRegion)
522 total += hypotf(targetPoint[0] - mid[0], targetPoint[1] - mid[1]);
523 total += region->getDistance();
524 if (neighbor->test(mailbox) || total < neighbor->getDistance())
525 {
526 neighbor->setPathStuff(total, region, mid, mailbox);
527 queue.insert(neighbor, priority);
528 }
529 }
530 }
531
532
533 // Local Variables: ***
534 // mode: C++ ***
535 // tab-width: 4 ***
536 // c-basic-offset: 4 ***
537 // indent-tabs-mode: nil ***
538 // End: ***
539 // ex: shiftwidth=4 tabstop=4
540