1 /*
2
3 *************************************************************************
4
5 ArmageTron -- Just another Tron Lightcycle Game in 3D.
6 Copyright (C) 2004 Armagetron Advanced Team (http://sourceforge.net/projects/armagetronad/)
7
8 **************************************************************************
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 ***************************************************************************
25
26 */
27
28 #include "rSDL.h"
29
30 // declaration
31 #ifndef ARMAGETRONAD_SRC_TRON_GCYCLEMOVEMENT_H_INCLUDED
32 #include "gCycleMovement.h"
33 #endif
34
35 #include "tMath.h"
36
37 #include "nConfig.h"
38
39 #include "ePlayer.h"
40 #include "eDebugLine.h"
41 #include "eGrid.h"
42 #include "eLagCompensation.h"
43 #include "eTeam.h"
44
45 #include "eTimer.h"
46
47 #include "gWall.h"
48 #include "gSensor.h"
49 #include "gAIBase.h"
50
51 #include "tRecorder.h"
52
53 // #define DEBUG_RUBBER
54
55 #ifdef DEBUG_RUBBER
56 #include <fstream>
57 #endif
58
59 #undef INLINE_DEF
60 #define INLINE_DEF
61
62 #ifndef DEDICATED
63 #define MAXRUBBER 1
64 #else
65 #define MAXRUBBER 3
66 #endif
67
68 #ifdef DEBUG
69 #define DEBUGOUTPUT
70 #endif
71
72 #ifdef DEBUGOUTPUT
73 #include "tSysTime.h"
74 static int sg_cycleDebugPrintLevel = 0;
75 #endif
76
77 // get rubber values in effect
78 void sg_RubberValues( ePlayerNetID const * player, REAL speed, REAL & max, REAL & effectiveness );
79
80 // *****************************************************************
81
sg_ArchiveCoord(eCoord & coord,int level)82 static void sg_ArchiveCoord( eCoord & coord, int level )
83 {
84 static char const * section = "_COORD";
85 tRecorderSync< eCoord >::Archive( section, level, coord );
86 }
87
sg_ArchiveReal(REAL & real,int level)88 static void sg_ArchiveReal( REAL & real, int level )
89 {
90 static char const * section = "_REAL";
91 tRecorderSync< REAL >::Archive( section, level, real );
92 }
93
94 // *****************************************************************
95 // version feature indicating that proper verlet integration should be used
96 static nVersionFeature sg_verletIntegration( 7 );
97
98 // strength of brake
99 REAL sg_brakeCycle=30;
100 static nSettingItem<REAL> c_ab("CYCLE_BRAKE",
101 sg_brakeCycle);
102
103 REAL sg_cycleBrakeRefill = 0.0;
104 REAL sg_cycleBrakeDeplete = 0.0;
105
106 // it should look this way, but a VersionFeature is used to control the application of these settings,
107 // so the nonwatched itmes suffice.
108 static nSettingItemWatched<REAL> sg_cycleBrakeRefillConf("CYCLE_BRAKE_REFILL",sg_cycleBrakeRefill, nConfItemVersionWatcher::Group_Annoying, 2 );
109 static nSettingItemWatched<REAL> sg_cycleBrakeDepleteConf("CYCLE_BRAKE_DEPLETE",sg_cycleBrakeDeplete, nConfItemVersionWatcher::Group_Annoying, 2 );
110
111 // static nSettingItem<REAL> sg_cycleBrakeRefillConf("CYCLE_BRAKE_REFILL",sg_cycleBrakeRefill );
112 // static nSettingItem<REAL> sg_cycleBrakeDepleteConf("CYCLE_BRAKE_DEPLETE",sg_cycleBrakeDeplete );
113
114 // cycle width: it won't fit into tunnels that are smaller than this
115 REAL sg_cycleWidth = 0;
116 static nSettingItemWatched<REAL> c_cw("CYCLE_WIDTH",
117 sg_cycleWidth, nConfItemVersionWatcher::Group_Bumpy, 14 );
118
119 REAL sg_cycleWidthSide = 0;
120 static nSettingItemWatched<REAL> c_cws("CYCLE_WIDTH_SIDE",
121 sg_cycleWidthSide, nConfItemVersionWatcher::Group_Bumpy, 14 );
122 // calculate the gridning distance sparks should start flying at
sg_GetSparksDistance()123 REAL sg_GetSparksDistance()
124 {
125 if ( sg_cycleWidth < 2 * sg_cycleWidthSide )
126 return sg_cycleWidth;
127 else if ( sg_cycleWidthSide > 0 )
128 return sg_cycleWidthSide * 2;
129 else
130 return .25; // return 0.2.8.2 default
131 }
132
133 // amout of rubber you use per meter when you squeeze inside a too tight tunnel
134 // when just barely squeezed
135 REAL sg_cycleWidthRubberMin = 1;
136 static nSettingItemWatched<REAL> c_cwrmax("CYCLE_WIDTH_RUBBER_MIN",
137 sg_cycleWidthRubberMin, nConfItemVersionWatcher::Group_Bumpy, 14 );
138 // when squeezed to a point
139 REAL sg_cycleWidthRubberMax = 1;
140 static nSettingItemWatched<REAL> c_cwrmin("CYCLE_WIDTH_RUBBER_MAX",
141 sg_cycleWidthRubberMax, nConfItemVersionWatcher::Group_Bumpy, 14 );
142
143 // base speed of cycle im m/s
144 static REAL sg_speedCycle=10;
145 static nSettingItem<REAL> c_s("CYCLE_SPEED",sg_speedCycle);
146
147 // minimal speed
148 static REAL sg_speedCycleMin=.25;
149 static nSettingItemWatched<REAL> c_smin("CYCLE_SPEED_MIN",
150 sg_speedCycleMin,
151 nConfItemVersionWatcher::Group_Bumpy,
152 9);
153
154 // maximal speed
155 static REAL sg_speedCycleMax=0;
156 static nSettingItemWatched<REAL> c_smax("CYCLE_SPEED_MAX",
157 sg_speedCycleMax,
158 nConfItemVersionWatcher::Group_Bumpy,
159 14);
160
161 REAL sg_speedCycleDecayBelow = 5;
162 static nSettingItemWatched<REAL> c_sdb("CYCLE_SPEED_DECAY_BELOW",
163 sg_speedCycleDecayBelow,
164 nConfItemVersionWatcher::Group_Bumpy,
165 8);
166
167 REAL sg_speedCycleDecayAbove = .1;
168 static nSettingItemWatched<REAL> c_sda("CYCLE_SPEED_DECAY_ABOVE",
169 sg_speedCycleDecayAbove,
170 nConfItemVersionWatcher::Group_Bumpy,
171 8);
172
173 // start speed of cycle im m/s
174 static REAL sg_speedCycleStart=20;
175 static tSettingItem<REAL> c_st("CYCLE_START_SPEED",
176 sg_speedCycleStart);
177
178 // min time between turns
179 REAL sg_delayCycle = .1;
180 static nSettingItem<REAL> c_d("CYCLE_DELAY",
181 sg_delayCycle);
182 //bonus for turns in the same direcion
183 REAL sg_delayCycleDoublebindBonus = 1.;
184 static nSettingItemWatched<REAL> c_d_d_b("CYCLE_DELAY_DOUBLEBIND_BONUS",
185 sg_delayCycleDoublebindBonus, nConfItemVersionWatcher::Group_Bumpy, 14 );
186
187 // number of turns buffered exactly
188 int sg_cycleTurnMemory = 3;
189 static tSettingItem<int> c_tm("CYCLE_TURN_MEMORY",
190 sg_cycleTurnMemory);
191
192 REAL sg_delayCycleTimeBased = 1;
193 static nSettingItemWatched<REAL> c_dt("CYCLE_DELAY_TIMEBASED",
194 sg_delayCycleTimeBased,
195 nConfItemVersionWatcher::Group_Bumpy,
196 7);
197
198 // extra factor to sg_delayCycle applied locally
199 #ifdef DEDICATED
200 REAL sg_delayCycleBonus=.95;
201 static tSettingItem<REAL> c_db("CYCLE_DELAY_BONUS",
202 sg_delayCycleBonus);
203 #else
204 REAL sg_delayCycleBonus=1;
205 #endif
206
207 REAL sg_cycleTurnSpeedFactor=.95;
208 static nSettingItemWatched<REAL> c_ctf("CYCLE_TURN_SPEED_FACTOR",
209 sg_cycleTurnSpeedFactor,
210 nConfItemVersionWatcher::Group_Bumpy,
211 7);
212
213 // wall acceleration
214 static REAL sg_accelerationCycle=10;
215 static nSettingItem<REAL> c_a("CYCLE_ACCEL",
216 sg_accelerationCycle);
217
218 // acceleration multiplicators
219 REAL sg_accelerationCycleSelf = 1;
220 static nSettingItemWatched<REAL> c_aco("CYCLE_ACCEL_SELF",
221 sg_accelerationCycleSelf,
222 nConfItemVersionWatcher::Group_Bumpy,
223 8);
224
225 REAL sg_accelerationCycleTeam = 1;
226 static nSettingItemWatched<REAL> c_act("CYCLE_ACCEL_TEAM",
227 sg_accelerationCycleTeam,
228 nConfItemVersionWatcher::Group_Bumpy,
229 14);
230
231 REAL sg_accelerationCycleEnemy = 1;
232 static nSettingItemWatched<REAL> c_ace("CYCLE_ACCEL_ENEMY",
233 sg_accelerationCycleEnemy,
234 nConfItemVersionWatcher::Group_Bumpy,
235 14);
236
237 REAL sg_accelerationCycleRim = 0;
238 static nSettingItemWatched<REAL> c_acr("CYCLE_ACCEL_RIM",
239 sg_accelerationCycleRim,
240 nConfItemVersionWatcher::Group_Bumpy,
241 8);
242
243 REAL sg_accelerationCycleSlingshot = 1;
244 static nSettingItemWatched<REAL> c_acs("CYCLE_ACCEL_SLINGSHOT",
245 sg_accelerationCycleSlingshot,
246 nConfItemVersionWatcher::Group_Bumpy,
247 8);
248
249 REAL sg_accelerationCycleTunnel = 1;
250 static nSettingItemWatched<REAL> c_acu("CYCLE_ACCEL_TUNNEL",
251 sg_accelerationCycleTunnel,
252 nConfItemVersionWatcher::Group_Bumpy,
253 14);
254
255 // acceleration offset
256 static REAL sg_accelerationCycleOffs=2;
257 static nSettingItem<REAL> c_ao("CYCLE_ACCEL_OFFSET",
258 sg_accelerationCycleOffs);
259
260
261 // when is a eWall near?
262 static REAL sg_nearCycle=6;
263 static nSettingItem<REAL> c_n("CYCLE_WALL_NEAR",
264 sg_nearCycle);
265
266 // boost settings, absolute speed increase applied when you break from a wall
267 REAL sg_boostCycleSelf = 0;
268 static nSettingItemWatched<REAL> c_bco("CYCLE_BOOST_SELF",
269 sg_boostCycleSelf,
270 nConfItemVersionWatcher::Group_Bumpy,
271 14);
272
273 REAL sg_boostCycleTeam = 0;
274 static nSettingItemWatched<REAL> c_bct("CYCLE_BOOST_TEAM",
275 sg_boostCycleTeam,
276 nConfItemVersionWatcher::Group_Bumpy,
277 14);
278
279 REAL sg_boostCycleEnemy = 0;
280 static nSettingItemWatched<REAL> c_bce("CYCLE_BOOST_ENEMY",
281 sg_boostCycleEnemy,
282 nConfItemVersionWatcher::Group_Bumpy,
283 14);
284
285 REAL sg_boostCycleRim = 0;
286 static nSettingItemWatched<REAL> c_bcr("CYCLE_BOOST_RIM",
287 sg_boostCycleRim,
288 nConfItemVersionWatcher::Group_Bumpy,
289 14);
290
291 // boostFactor settings, speed factor when you break from a wall
292 REAL sg_boostFactorCycleSelf = 1;
293 static nSettingItemWatched<REAL> c_bfco("CYCLE_BOOSTFACTOR_SELF",
294 sg_boostFactorCycleSelf,
295 nConfItemVersionWatcher::Group_Bumpy,
296 14);
297
298 REAL sg_boostFactorCycleTeam = 1;
299 static nSettingItemWatched<REAL> c_bfct("CYCLE_BOOSTFACTOR_TEAM",
300 sg_boostFactorCycleTeam,
301 nConfItemVersionWatcher::Group_Bumpy,
302 14);
303
304 REAL sg_boostFactorCycleEnemy = 1;
305 static nSettingItemWatched<REAL> c_bfce("CYCLE_BOOSTFACTOR_ENEMY",
306 sg_boostFactorCycleEnemy,
307 nConfItemVersionWatcher::Group_Bumpy,
308 14);
309
310 REAL sg_boostFactorCycleRim = 1;
311 static nSettingItemWatched<REAL> c_bfcr("CYCLE_BOOSTFACTOR_RIM",
312 sg_boostFactorCycleRim,
313 nConfItemVersionWatcher::Group_Bumpy,
314 14);
315
316 // tolerance for packet loss
317 static REAL sg_packetLossTolerance = 0;
318 static tSettingItem<REAL> conf_packetLossTolerance ("CYCLE_PACKETLOSS_TOLERANCE", sg_packetLossTolerance);
319
320 // tolerance for packet misses: if an intermediate packet is missing
321 static REAL sg_packetMissTolerance = 3;
322 static tSettingItem<REAL> conf_packetMissTolerance ("CYCLE_PACKETMISS_TOLERANCE", sg_packetMissTolerance);
323
324 // niceness when crashing a eWall
325 REAL sg_rubberCycle=MAXRUBBER;
326 static nSettingItem<REAL> c_r("CYCLE_RUBBER",
327 sg_rubberCycle);
328
329 REAL sg_rubberCycleTimeBased = 0;
330 static nSettingItemWatched<REAL> c_rtb("CYCLE_RUBBER_TIMEBASED",
331 sg_rubberCycleTimeBased,
332 nConfItemVersionWatcher::Group_Visual,
333 7);
334
335 // allow legacy rubber code
336 bool sg_rubberCycleLegacy=true;
337 static nSettingItem<bool> c_rl("CYCLE_RUBBER_LEGACY",
338 sg_rubberCycleLegacy);
339
340 // niceness when crashing a eWall, influence of your ping
341 static REAL sg_rubberCyclePing=3;
342 static nSettingItem<REAL> c_rp("CYCLE_PING_RUBBER",
343 sg_rubberCyclePing);
344
345 // timescale rubber is restored on
346 REAL sg_rubberCycleTime=10;
347 static nSettingItemWatched<REAL> c_rt("CYCLE_RUBBER_TIME",
348 sg_rubberCycleTime,
349 nConfItemVersionWatcher::Group_Visual,
350 7);
351
352 // max logarithmic approximation speed when rubber is in effect
353 static REAL sg_rubberCycleSpeed=40;
354 static nSettingItemWatched<REAL> c_rs("CYCLE_RUBBER_SPEED",
355 sg_rubberCycleSpeed,
356 nConfItemVersionWatcher::Group_Cheating,
357 4);
358
359 #ifdef DEDICATED
360 #define MINDISTANCE_FACTOR 1
361 #else
362 #define MINDISTANCE_FACTOR 0
363 #endif
364
365 // minimal distance to a wall rubber will allow
366 static REAL sg_rubberCycleMinDistance=.001 * MINDISTANCE_FACTOR;
367 static nSettingItemWatched<REAL> c_rmd("CYCLE_RUBBER_MINDISTANCE",
368 sg_rubberCycleMinDistance,
369 nConfItemVersionWatcher::Group_Annoying,
370 4);
371
372 // the length of the wall times this value is added to the previous value
373 static REAL sg_rubberCycleMinDistanceRatio=.0001 * MINDISTANCE_FACTOR;
374 static nSettingItemWatched<REAL> c_rmdr("CYCLE_RUBBER_MINDISTANCE_RATIO",
375 sg_rubberCycleMinDistanceRatio,
376 nConfItemVersionWatcher::Group_Annoying,
377 4);
378
379 // on an empty rubber reservoir, this value is added as well
380 static REAL sg_rubberCycleMinDistanceReservoir=0.005 * MINDISTANCE_FACTOR;
381 static nSettingItemWatched<REAL> c_rmdres("CYCLE_RUBBER_MINDISTANCE_RESERVOIR",
382 sg_rubberCycleMinDistanceReservoir,
383 nConfItemVersionWatcher::Group_Annoying,
384 7);
385
386 // and on a not so well prepared grind (if your last turn was not too long ago), this value.
387 static REAL sg_rubberCycleMinDistanceUnprepared=0.005 * MINDISTANCE_FACTOR;
388 static nSettingItemWatched<REAL> c_rmdup("CYCLE_RUBBER_MINDISTANCE_UNPREPARED",
389 sg_rubberCycleMinDistanceUnprepared ,
390 nConfItemVersionWatcher::Group_Annoying,
391 7);
392
393 // this is the distance the preparation lengt is compared with
394 static REAL sg_rubberCycleMinDistancePreparation=0.2;
395 static nSettingItemWatched<REAL> c_rmdp("CYCLE_RUBBER_MINDISTANCE_PREPARATION",
396 sg_rubberCycleMinDistancePreparation,
397 nConfItemVersionWatcher::Group_Annoying,
398 7);
399
400 // when connecting to older peers that may be rippable, multiply the minimum distance by this number if the
401 // wall in front of you is the rim
402 static REAL sg_rubberCycleMinDistanceLegacy=1;
403 static nSettingItem<REAL> c_rmdl("CYCLE_RUBBER_MINDISTANCE_LEGACY",
404 sg_rubberCycleMinDistanceLegacy);
405
406 // this feature tracks whether the rip bug was fixed
407 static nVersionFeature sg_nonRippable(4);
408
409 // when adjusting to a wall, allow to get closer at least by this amount
410 static REAL sg_rubberCycleMinAdjust=.05;
411 static nSettingItemWatched<REAL> c_rma("CYCLE_RUBBER_MINADJUST",
412 sg_rubberCycleMinAdjust,
413 nConfItemVersionWatcher::Group_Annoying,
414 4);
415
416 // during this fraction of the cycle delay time, rubber effectiveness will be reduced...
417 static REAL sg_rubberCycleDelay=0;
418 static nSettingItemWatched<REAL> c_rcd("CYCLE_RUBBER_DELAY",
419 sg_rubberCycleDelay,
420 nConfItemVersionWatcher::Group_Visual,
421 6);
422
423 // by this factor ( meaning that rubber usage goes up by the inverse )
424 static REAL sg_rubberCycleDelayBonus=.5;
425 static nSettingItemWatched<REAL> c_rcdb("CYCLE_RUBBER_DELAY_BONUS",
426 sg_rubberCycleDelayBonus,
427 nConfItemVersionWatcher::Group_Visual,
428 6);
429
430 // rubber usage gets increased by this amout after each turn...
431 static REAL sg_rubberCycleMalusTurn=0;
432 static nSettingItemWatched<REAL> c_rctm("CYCLE_RUBBER_MALUS_TURN",
433 sg_rubberCycleMalusTurn,
434 nConfItemVersionWatcher::Group_Visual,
435 6);
436
437 // but the effect wears off after about this many seconds
438 static REAL sg_rubberCycleMalusTime=5;
439 static nSettingItem<REAL> c_rcmd("CYCLE_RUBBER_MALUS_TIME",
440 sg_rubberCycleMalusTime);
441
442 // time tolerance when interpreting client commands
443 static REAL sg_timeTolerance=.1;
444 static nSettingItemWatched<REAL> c_tt( "CYCLE_TIME_TOLERANCE",
445 sg_timeTolerance,
446 nConfItemVersionWatcher::Group_Visual,
447 6 );
448
449 static int sg_cycleMaxRefCount = 30000;
450 static tSettingItem<int> conf_sgCycleMaxRefCount ("CYCLE_MAX_REFCOUNT", sg_cycleMaxRefCount );
451
clamp(REAL & c,REAL min,REAL max)452 static inline bool clamp(REAL &c, REAL min, REAL max){
453 tASSERT(min <= max);
454
455 if (!finite(c))
456 {
457 c = 0;
458 return true;
459 }
460
461 if (c<min)
462 {
463 c = min;
464 return true;
465 }
466
467 if (c>max)
468 {
469 c = max;
470 return true;
471 }
472
473 return false;
474 }
475
blocks(const gSensor & s,const gCycleMovement * c,int lr)476 static void blocks(const gSensor &s, const gCycleMovement *c, int lr)
477 {
478 if ( nCLIENT == sn_GetNetState() )
479 return;
480
481 if (s.type == gSENSOR_RIM)
482 gAIPlayer::CycleBlocksRim(c, lr);
483 else if (s.type == gSENSOR_TEAMMATE || ( s.type == gSENSOR_ENEMY && s.ehit ) )
484 {
485 gPlayerWall *w = dynamic_cast<gPlayerWall*>(s.ehit->GetWall());
486 if (w)
487 {
488 // int turn = c->Grid()->WindingNumber();
489 // int halfTurn = turn >> 1;
490
491 // calculate the winding number.
492 int windingBefore = c->WindingNumber(); // we start driving in c's direction
493 // we need to make a sharp turn in the lr-direction
494 // windingBefore += lr * halfTurn;
495
496 // after the transfer, we need to drive in the direction of the other
497 // wall:
498 int windingAfter = w->WindingNumber();
499
500 // if the other wall drives in the opposite direction, we
501 // need to turn around again:
502 // if (s.lr == lr)
503 // windingAfter -= lr * halfTurn;
504
505 // make the winding difference a multiple of the winding number
506 /*
507 int compensation = ((windingAfter - windingBefore - halfTurn) % turn)
508 + halfTurn;
509 while (compensation < -halfTurn)
510 compensation += turn;
511 */
512
513 // only if the two walls are parallel/antiparallel, there is true blocking.
514 if (((windingBefore - windingAfter) & 1) == 0)
515 gAIPlayer::CycleBlocksWay(c, w->Cycle(),
516 lr, s.lr,
517 w->Pos(s.ehit->Ratio(s.before_hit)),
518 - windingAfter + windingBefore);
519 }
520 }
521 }
522
523 // enemy influence settings
524 static REAL sg_enemyFriendTimePenalty = 2500.0f; //!< penalty for teammate influence
525 // REAL sg_enemySelfTimePenalty = 5000.0f; //!< penalty for self influence
526 static REAL sg_enemyDeadTimePenalty = 0.0f; //!< penalty for influence from dead players
527 REAL sg_suicideTimeout = 10000.0f; //!< influences older than this don't count as kill cause
528 static REAL sg_enemyCurrentTimeInfluence = 0.0f; //!< blends in the current time into the relevant time
529
530 static tSettingItem<REAL> sg_enemyFriendTimePenaltyConf( "ENEMY_TEAMMATE_PENALTY", sg_enemyFriendTimePenalty );
531 static tSettingItem<REAL> sg_enemyDeadTimePenaltyConf( "ENEMY_DEAD_PENALTY", sg_enemyDeadTimePenalty );
532 static tSettingItem<REAL> sg_suicideTimeoutConf( "ENEMY_SUICIDE_TIMEOUT", sg_suicideTimeout );
533 static tSettingItem<REAL> sg_enemyCurrentTimeInfluenceConf( "ENEMY_CURRENTTIME_INFLUENCE", sg_enemyCurrentTimeInfluence );
534
535 // the last enemy possibly responsible for our death
GetEnemy() const536 const ePlayerNetID* gEnemyInfluence::GetEnemy() const
537 {
538 return lastEnemyInfluence.GetPointer();
539 }
540
GetTime() const541 REAL gEnemyInfluence::GetTime() const
542 {
543 return lastTime;
544 }
545
gEnemyInfluence()546 gEnemyInfluence::gEnemyInfluence()
547 {
548 lastTime = -sg_suicideTimeout;
549 }
550
551 // add the result of the sensor scan to our data
AddSensor(const gSensor & sensor,REAL timePenalty,gCycleMovement * thisCycle)552 void gEnemyInfluence::AddSensor( const gSensor& sensor, REAL timePenalty, gCycleMovement * thisCycle )
553 {
554 // the client has no need for this, it does not execute AI code
555 if ( sn_GetNetState() == nCLIENT )
556 return;
557
558 // check if the sensor hit an enemy wall
559 // if ( sensor.type != gSENSOR_ENEMY )
560 // return;
561
562 // get the wall
563 if ( !sensor.ehit )
564 return;
565
566 eWall* wall = sensor.ehit->GetWall();
567 if ( !wall )
568 return;
569
570 AddWall( wall, sensor.before_hit, timePenalty, thisCycle );
571 }
572
573 // add the interaction with a wall to our data
AddWall(const eWall * wall,eCoord const & pos,REAL timePenalty,gCycleMovement * thisCycle)574 void gEnemyInfluence::AddWall( const eWall * wall, eCoord const & pos, REAL timePenalty, gCycleMovement * thisCycle )
575 {
576 // the client has no need for this, it does not execute AI code
577 if ( sn_GetNetState() == nCLIENT )
578 return;
579
580 // see if it is a player wall
581 gPlayerWall const * playerWall = dynamic_cast<gPlayerWall const *>( wall );
582 if ( !playerWall )
583 return;
584
585 // get the approximate time the wall was drawn
586 REAL alpha = .5f;
587 // try to get a more accurate value
588 if ( playerWall->Edge() )
589 {
590 // get the position of the collision point
591 alpha = playerWall->Edge()->Ratio( pos );
592 }
593 REAL timeBuilt = playerWall->Time( alpha );
594
595 AddWall( playerWall, timeBuilt - timePenalty, thisCycle );
596 }
597
598 // add the interaction with a wall to our data
AddWall(const gPlayerWall * wall,REAL timeBuilt,gCycleMovement * thisCycle)599 void gEnemyInfluence::AddWall( const gPlayerWall * wall, REAL timeBuilt, gCycleMovement * thisCycle )
600 {
601 // the client has no need for this, it does not execute AI code
602 if ( sn_GetNetState() == nCLIENT )
603 return;
604
605 if ( !wall )
606 return;
607
608 // get the cycle
609 gCycle *cycle = wall->Cycle();
610 if ( !cycle )
611 return;
612
613 // don't count self influence
614 if ( thisCycle == cycle )
615 return;
616
617 REAL time = timeBuilt;
618 if ( thisCycle )
619 {
620 REAL currentTime = thisCycle->LastTime();
621 time += ( currentTime - time ) * sg_enemyCurrentTimeInfluence;
622 }
623
624 // get the player
625 ePlayerNetID* player = cycle->Player();
626 if ( !player )
627 return;
628
629 // don't accept milkers.
630 if ( thisCycle && !ePlayerNetID::Enemies( thisCycle->Player(), player ) )
631 {
632 return;
633 }
634
635 // if the player is not our enemy, add extra time penalty
636 if ( thisCycle->Player() && player->CurrentTeam() == thisCycle->Player()->CurrentTeam() )
637 {
638 // the time shall be at most the time of the last turn, it should count as suicide if
639 // I drive into your three mile long wall
640 if ( time > cycle->GetLastTurnTime() )
641 time = cycle->GetLastTurnTime();
642 time -= sg_enemyFriendTimePenalty;
643 }
644 const ePlayerNetID* pInfluence = this->lastEnemyInfluence.GetPointer();
645
646 // calculate effective last time. Add malus if the player is dead.
647 REAL lastEffectiveTime = lastTime;
648 if ( !pInfluence || !pInfluence->Object() || !pInfluence->Object()->Alive() )
649 {
650 lastEffectiveTime -= sg_enemyDeadTimePenalty;
651 }
652
653 // same for the current influence
654 REAL effectiveTime = time;
655 if ( !cycle->Alive() )
656 {
657 effectiveTime -= sg_enemyDeadTimePenalty;
658 }
659
660 // if the new influence is newer, take it.
661 if ( effectiveTime > lastEffectiveTime || !bool(lastEnemyInfluence) )
662 {
663 lastEnemyInfluence = player;
664 lastTime = time;
665 }
666 }
667
668 // *******************************************************************************************
669 // *
670 // * gCycleMovement
671 // *
672 // *******************************************************************************************
673 //!
674 //!
675 // *******************************************************************************************
676
677 // gCycleMovement::gCycleMovement()
678 // {
679 // this->Init_gCycleCore();
680 // }
681
682 // *******************************************************************************************
683 // *
684 // * gCycleMovement
685 // *
686 // *******************************************************************************************
687 //!
688 //! @param other the source to copy from
689 //!
690 // *******************************************************************************************
691
692 // gCycleMovement::gCycleMovement( gCycleMovement const & other )
693 // {
694 // this->Init_gCycleCore();
695 // this->CopyFrom( other );
696 //}
697
698 // *******************************************************************************************
699 // *
700 // * operator =
701 // *
702 // *******************************************************************************************
703 //!
704 //! @param other the source to copy from
705 //! @return a reference to this
706 //!
707 // *******************************************************************************************
708
operator =(gCycleMovement const & other)709 gCycleMovement & gCycleMovement::operator= ( gCycleMovement const & other )
710 {
711 this->CopyFrom( other );
712 return *this;
713 }
714
715 // *******************************************************************************************
716 // *
717 // * init_gCycleCore
718 // *
719 // *******************************************************************************************
720 //!
721 //!
722 // *******************************************************************************************
723
724 //void gCycleMovement::Init_gCycleCore()
725 //{
726 // assert(0); // TODO: implement me
727 //}
728
729 // *******************************************************************************************
730 // *
731 // * finit_gCycleCore
732 // *
733 // *******************************************************************************************
734 //!
735 //!
736 // *******************************************************************************************
737
738 //void gCycleMovement::Finit_gCycleCore()
739 //{
740 // assert(0); // TODO: implement me
741 //}
742
743 // ----------------------------------------------------------------------------------------------------------
744
745 static float sg_speedMultiplier = 1.0f;
746 static nSettingItem<float> conf_mult ("REAL_CYCLE_SPEED_FACTOR", sg_speedMultiplier);
747
748 // *******************************************************************************************
749 // *
750 // * RubberSpeed
751 // *
752 // *******************************************************************************************
753 //!
754 //! @return the rubber speed (decay rate of the distance to the wall in front)
755 //!
756 // *******************************************************************************************
757
RubberSpeed()758 float gCycleMovement::RubberSpeed()
759 {
760 return sg_rubberCycleSpeed;
761 }
762
763 // *******************************************************************************************
764 // *
765 // * SpeedMultiplier
766 // *
767 // *******************************************************************************************
768 //!
769 //! @return the number all speed settings get multiplied by
770 //!
771 // *******************************************************************************************
772
SpeedMultiplier(void)773 float gCycleMovement::SpeedMultiplier( void )
774 {
775 return sg_speedMultiplier;
776 }
777
778 // *******************************************************************************************
779 // *
780 // * SetSpeedMultiplier
781 // *
782 // *******************************************************************************************
783 //!
784 //! @param mul the number all speed settings get multiplied by
785 //!
786 // *******************************************************************************************
787
SetSpeedMultiplier(REAL mul)788 void gCycleMovement::SetSpeedMultiplier( REAL mul )
789 {
790 conf_mult.Set( mul );
791 }
792
793 // for the given maximal acceleration, calculate the top speed
sg_MaxSpeed(REAL maxAcceleration)794 static REAL sg_MaxSpeed( REAL maxAcceleration )
795 {
796 if ( sg_speedCycleDecayAbove > 0 )
797 return sg_speedCycle + maxAcceleration / sg_speedCycleDecayAbove;
798 else
799 return sg_speedCycle * 100;
800 }
801
802 // *******************************************************************************************
803 // *
804 // * MaximalSpeed
805 // *
806 // *******************************************************************************************
807 //!
808 //! @return the maximal speed a cycle can reach
809 //!
810 // *******************************************************************************************
811
MaximalSpeed(void)812 float gCycleMovement::MaximalSpeed( void )
813 {
814 // determine the maximal acceleration by walls
815 REAL maxWallAcceleration = 0;
816 REAL wallAcceleration = sg_accelerationCycleTeam * sg_accelerationCycle;
817 if ( wallAcceleration > maxWallAcceleration )
818 maxWallAcceleration = wallAcceleration;
819 wallAcceleration = sg_accelerationCycleEnemy * sg_accelerationCycle;
820 if ( wallAcceleration > maxWallAcceleration )
821 maxWallAcceleration = wallAcceleration;
822 wallAcceleration = sg_accelerationCycleRim * sg_accelerationCycle;
823 if ( wallAcceleration > maxWallAcceleration )
824 maxWallAcceleration = wallAcceleration;
825
826 // self acceleration is tricky: slingshot countermeasures have to be taken into account
827 REAL wallAccelerationSelf = sg_accelerationCycleSelf * sg_accelerationCycle;
828
829 {
830 // different combinations are now possible to get a maximum. It could be a single wall:
831 REAL wallAccelerationSingle = maxWallAcceleration;
832 if ( wallAccelerationSingle < wallAccelerationSelf )
833 wallAccelerationSingle = wallAccelerationSelf;
834
835 // it could be a slingshot, one arbitrary wall and one own wall:
836 REAL wallAccelerationSlingshot = ( wallAccelerationSingle + wallAccelerationSelf ) * sg_accelerationCycleSlingshot;
837
838 // or a tunnel, two foreign walls:
839 REAL wallAccelerationTunnel = ( maxWallAcceleration ) * sg_accelerationCycleTunnel;
840
841
842 // take the maximum
843 if ( maxWallAcceleration < wallAccelerationSlingshot )
844 maxWallAcceleration = wallAccelerationSlingshot;
845 if ( maxWallAcceleration < wallAccelerationTunnel )
846 maxWallAcceleration = wallAccelerationTunnel;
847 }
848
849 // use wall accel formula to take wall distance into account
850 maxWallAcceleration *= ( 1/sg_accelerationCycleOffs - 1/(sg_accelerationCycleOffs+sg_nearCycle ) );
851
852 // maximal sustainable speed from that
853 REAL maxSpeed = sg_MaxSpeed( maxWallAcceleration );
854
855 // what if the brake is a booster?
856 if ( sg_brakeCycle < 0 )
857 {
858 // what if it can be applied infinitely?
859 REAL maxSpeedBoost = sg_MaxSpeed( maxWallAcceleration - sg_brakeCycle );
860
861 // but the boost can permanently only be applied at this efficiency
862 REAL efficiency = 1;
863 if ( sg_cycleBrakeRefill + sg_cycleBrakeDeplete > 0 )
864 efficiency = sg_cycleBrakeRefill/(sg_cycleBrakeRefill + sg_cycleBrakeDeplete);
865
866 // maximal permanent speed
867 REAL maxSpeedPermanent = sg_MaxSpeed( maxWallAcceleration - sg_brakeCycle*efficiency );
868
869 // if the boost is limited, don't let the result be larger than what you can achieve by
870 // accelerating from the max speed attainable from walls
871 if ( sg_cycleBrakeDeplete > 0 )
872 {
873 REAL boostTime = 1/sg_cycleBrakeDeplete;
874 REAL maxSpeedBoost2 = maxSpeedPermanent - boostTime * sg_brakeCycle;
875 if ( maxSpeedBoost2 < maxSpeedBoost )
876 maxSpeedBoost = maxSpeedBoost2;
877 }
878
879 // take over the result
880 maxSpeed = maxSpeedBoost;
881 }
882
883 // start speed
884 if ( sg_speedCycleStart > maxSpeed )
885 maxSpeed = sg_speedCycleStart;
886
887 // apply multiplier
888 return sg_speedMultiplier * maxSpeed;
889 }
890
891 // ----------------------------------------------------------------------------------------------------------
892
893 // *******************************************************************************************
894 // *
895 // * WindingNumber
896 // *
897 // *******************************************************************************************
898 //!
899 //! @return number of right turns taken minus the number of left turns
900 //!
901 // *******************************************************************************************
902
WindingNumber(void) const903 int gCycleMovement::WindingNumber( void ) const
904 {
905 return windingNumber_;
906 }
907
908 // *******************************************************************************************
909 // *
910 // * SetWindingNumberWrapped
911 // *
912 // *******************************************************************************************
913 //!
914 //! @param newWindingNumberWrapped new wrapped winding number taken from the current driving direction
915 //!
916 // *******************************************************************************************
917
SetWindingNumberWrapped(int newWindingNumberWrapped)918 void gCycleMovement::SetWindingNumberWrapped ( int newWindingNumberWrapped )
919 {
920 // calculate the difference in the wrapped winding number
921 int difference = newWindingNumberWrapped - windingNumberWrapped_;
922
923 // wrap it into the interval [-WN/2,WN/2]
924 if (2 * difference <= -Grid()->WindingNumber())
925 difference += Grid()->WindingNumber();
926 if (2 * difference >= Grid()->WindingNumber())
927 difference -= Grid()->WindingNumber();
928
929 // commit changes
930 windingNumberWrapped_ = newWindingNumberWrapped;
931 windingNumber_ += difference;
932 }
933
934 // *******************************************************************************************
935 // *
936 // * Direction
937 // *
938 // *******************************************************************************************
939 //!
940 //! @return the currend driving direction
941 //!
942 // *******************************************************************************************
943
Direction(void) const944 eCoord gCycleMovement::Direction( void ) const
945 {
946 return dirDrive;
947 }
948
949 // *******************************************************************************************
950 // *
951 // * LastDirection
952 // *
953 // *******************************************************************************************
954 //!
955 //! @return the last driving direction
956 //!
957 // *******************************************************************************************
958
LastDirection(void) const959 eCoord gCycleMovement::LastDirection( void ) const
960 {
961 return lastDirDrive;
962 }
963
964 // *******************************************************************************************
965 // *
966 // * Speed
967 // *
968 // *******************************************************************************************
969 //!
970 //! @return the current speed
971 //!
972 // *******************************************************************************************
973
Speed(void) const974 REAL gCycleMovement::Speed( void ) const
975 {
976 REAL ret = verletSpeed_ + .5f * lastTimestep_ * acceleration;
977 return ret > 0 ? ret : 0;
978 }
979
980 // *******************************************************************************************
981 // *
982 // * Alive
983 // *
984 // *******************************************************************************************
985 //!
986 //! @return true if the cycle is still alive
987 //!
988 // *******************************************************************************************
989
Alive() const990 bool gCycleMovement::Alive() const
991 {
992 return alive_ > 0;
993 }
994
995 // *******************************************************************************************
996 // *
997 // * Vulnerable
998 // *
999 // *******************************************************************************************
1000 //!
1001 //! @return true if the cycle is vulnerable
1002 //!
1003 // *******************************************************************************************
1004
Vulnerable() const1005 bool gCycleMovement::Vulnerable() const
1006 {
1007 return true;
1008 }
1009
1010 // *******************************************************************************************
1011 // *
1012 // * CanMakeTurn
1013 // *
1014 // *******************************************************************************************
1015 //!
1016 //! @param direction the direction of the planned turn
1017 //! @return true if a new turn is possible right now
1018 //!
1019 // *******************************************************************************************
1020
CanMakeTurn(int direction) const1021 bool gCycleMovement::CanMakeTurn( int direction ) const
1022 {
1023 return pendingTurns.empty() && CanMakeTurn( lastTime, direction );
1024 }
1025
1026 // *******************************************************************************************
1027 // *
1028 // * CanMakeTurn
1029 // *
1030 // *******************************************************************************************
1031 //!
1032 //! @param time the time to check
1033 //! @param direction the direction of the planned turn
1034 //! @return true if a new turn is possible at the given time
1035 //!
1036 // *******************************************************************************************
1037
CanMakeTurn(REAL time,int direction) const1038 bool gCycleMovement::CanMakeTurn( REAL time, int direction ) const
1039 {
1040 return time >= GetNextTurn(direction);
1041 }
1042
1043 // *******************************************************************************************
1044 // *
1045 // * GetTurnDelay
1046 // *
1047 // *******************************************************************************************
1048 //!
1049 //! @return the delay between turns in seconds
1050 //!
1051 // *******************************************************************************************
1052
GetTurnDelay(void) const1053 REAL gCycleMovement::GetTurnDelay( void ) const
1054 {
1055 // the basic delay as it was before 0.2.8 looked like this:
1056 REAL baseDelay = sg_delayCycle*sg_delayCycleBonus/SpeedMultiplier();
1057
1058 // we're modifying it by a power law to make speed turns easier or harder:
1059 REAL speedFactor = verletSpeed_/(sg_speedCycle*SpeedMultiplier());
1060
1061 return baseDelay * pow( speedFactor, sg_delayCycleTimeBased-1 );
1062 }
1063
1064 //! @return the delay between turns in seconds
GetTurnDelayDb(void) const1065 REAL gCycleMovement::GetTurnDelayDb( void ) const
1066 {
1067 // the basic delay as it was before 0.2.8 looked like this:
1068 REAL baseDelay = sg_delayCycle*sg_delayCycleBonus/SpeedMultiplier()*sg_delayCycleDoublebindBonus;
1069
1070 // we're modifying it by a power law to make speed turns easier or harder:
1071 REAL speedFactor = verletSpeed_/(sg_speedCycle*SpeedMultiplier());
1072
1073 return baseDelay * pow( speedFactor, sg_delayCycleTimeBased-1 );
1074 }
1075
1076 // *******************************************************************************************
1077 // *
1078 // * GetNextTurn
1079 // *
1080 // *******************************************************************************************
1081 //!
1082 //! @return the time of the next possible turn
1083 //!
1084 // *******************************************************************************************
1085
GetNextTurn(int direction) const1086 REAL gCycleMovement::GetNextTurn( int direction ) const
1087 {
1088 float right,left;
1089 #ifdef DEBUG_X
1090 std::cerr << "GetNextTurn: " << direction << std::endl;
1091 #endif
1092 if(direction == 1) {
1093 right = lastTurnTimeRight_ + GetTurnDelayDb();
1094 left = lastTurnTimeLeft_ + GetTurnDelay();
1095 } else {
1096 right = lastTurnTimeLeft_ + GetTurnDelayDb();
1097 left = lastTurnTimeRight_ + GetTurnDelay();
1098 }
1099 #ifdef DEBUG_X
1100 std::cerr << "GetTurnDelay: " << GetTurnDelay() << std::endl;
1101 std::cerr << "GetTurnDelayDb: " << GetTurnDelayDb() << std::endl;
1102 std::cerr << "lastTurnTimeRight_: " << lastTurnTimeRight_ << std::endl;
1103 std::cerr << "lastTurnTimeLeft_: " << lastTurnTimeLeft_ << std::endl;
1104 std::cerr << "right: " << right << std::endl;
1105 std::cerr << "left: " << left << std::endl;
1106 #endif
1107 return left > right ? left : right;
1108 }
1109
1110 // *******************************************************************************************
1111 // *
1112 // * AddDestination
1113 // *
1114 // *******************************************************************************************
1115 //!
1116 //!
1117 // *******************************************************************************************
1118
AddDestination(void)1119 void gCycleMovement::AddDestination( void )
1120 {
1121 if ( sn_GetNetState() == nCLIENT )
1122 {
1123 gDestination* dest = tNEW(gDestination)(*this);
1124 // dest->position = dest->position + dest->direction.Turn( 0, 10.0f );
1125 AddDestination( dest );
1126 }
1127 }
1128
1129 // *******************************************************************************************
1130 // *
1131 // * AddDestination
1132 // *
1133 // *******************************************************************************************
1134 //!
1135 //! @param dest the destination to add
1136 //!
1137 // *******************************************************************************************
1138
AddDestination(gDestination * dest)1139 void gCycleMovement::AddDestination( gDestination * dest )
1140 {
1141 // con << "got new dest " << dest->position << "," << dest->direction
1142 // << "," << dest->distance << "\n";
1143
1144 dest->InsertIntoList(&destinationList);
1145
1146 // if the next destination already has been used, this destination will never be used
1147 if (dest->next && dest->next->hasBeenUsed){
1148 delete dest;
1149 return;
1150 }
1151
1152 this->NotifyNewDestination( dest );
1153
1154 // repeat insertion: position may have changed
1155 dest->InsertIntoList(&destinationList);
1156 }
1157
1158 // *******************************************************************************************
1159 // *
1160 // * GetCurrentDestination
1161 // *
1162 // *******************************************************************************************
1163 //!
1164 //! @return the destination this cycle is driving towards right now
1165 //!
1166 // *******************************************************************************************
1167
GetCurrentDestination(void) const1168 gDestination * gCycleMovement::GetCurrentDestination( void ) const
1169 {
1170 return currentDestination;
1171 }
1172
1173 // *******************************************************************************************
1174 // *
1175 // * AdvanceDestination
1176 // *
1177 // *******************************************************************************************
1178 //!
1179 //!
1180 // *******************************************************************************************
1181
AdvanceDestination(void)1182 void gCycleMovement::AdvanceDestination( void )
1183 {
1184 // not implemented
1185 tASSERT(0);
1186 }
1187
1188 // *******************************************************************************************
1189 // *
1190 // * NotifyNewDestination
1191 // *
1192 // *******************************************************************************************
1193 //!
1194 //! @param dest the new destination the cycle is notified about
1195 //!
1196 // *******************************************************************************************
1197
NotifyNewDestination(gDestination * dest)1198 void gCycleMovement::NotifyNewDestination( gDestination * dest )
1199 {
1200 this->OnNotifyNewDestination( dest );
1201 }
1202
1203 // *******************************************************************************************
1204 // *
1205 // * DoIsDestinationUsed
1206 // *
1207 // *******************************************************************************************
1208 //!
1209 //! @param dest the destination to test
1210 //! @return true if the destination is still in active use
1211 //!
1212 // *******************************************************************************************
1213
DoIsDestinationUsed(const gDestination * dest) const1214 bool gCycleMovement::DoIsDestinationUsed( const gDestination * dest ) const
1215 {
1216 return ( destinationList == currentDestination || destinationList == lastDestination );
1217 }
1218
1219 // *******************************************************************************************
1220 // *
1221 // * DistanceToDestination
1222 // *
1223 // *******************************************************************************************
1224 //!
1225 //! @param dest the destination to measure the distance to
1226 //! @return the distance to the destination
1227 //!
1228 // *******************************************************************************************
1229
DistanceToDestination(gDestination & dest) const1230 REAL gCycleMovement::DistanceToDestination( gDestination & dest ) const
1231 {
1232 // read future direction from destination
1233 eCoord dirTurned = dest.direction;
1234
1235 REAL divisor = ( dirDrive * dirTurned );
1236 if ( divisor < EPS && divisor > -EPS )
1237 {
1238 REAL F = eCoord::F( dirTurned, dirDrive );
1239 if ( F > 0 )
1240 {
1241 // destination direction and driving direction coincide; we have to
1242 // make up a new turned direction
1243
1244 // no need to worry if brake status changed
1245 if ( ( braking != 0 ) != dest.braking )
1246 {
1247 return eCoord::F( dest.position - pos, dirDrive )/dirDrive.NormSquared();
1248 }
1249
1250 // we'd have to turn in this direction to reach the destination
1251 int side = (dest.position - pos) * dirDrive > 0 ? -1 : 1;
1252
1253 // pretend to turn in that direction and fetch driving vector
1254 int w = windingNumberWrapped_;
1255 Grid()->Turn(w, side);
1256 dirTurned = Grid()->GetDirection( w );
1257
1258 // recalculate divisor
1259 divisor = ( dirDrive * dirTurned );
1260 tASSERT( fabs( divisor ) > EPS );
1261 }
1262 else
1263 {
1264 // destination direction and driving direction are opposed. This must be a grave error,
1265 // so we'll make something up
1266 return eCoord::F( dest.position - pos, dirDrive )/dirDrive.NormSquared();
1267 }
1268 }
1269
1270 // calculate when a turn would need to be made that aligns
1271 // this cycle with the destination
1272 return ( ( dest.position - pos ) * dirTurned ) / divisor;
1273 }
1274
1275 // *******************************************************************************************
1276 // *
1277 // * OnNotifyNewDestination
1278 // *
1279 // *******************************************************************************************
1280 //!
1281 //! @param dest the new destination
1282 //!
1283 // *******************************************************************************************
1284
OnNotifyNewDestination(gDestination * dest)1285 void gCycleMovement::OnNotifyNewDestination( gDestination * dest )
1286 {
1287 // go back one destination if the new destination appears to be older than the current one
1288 if ((!currentDestination || currentDestination == dest->next ) &&
1289 sn_GetNetState()!=nSTANDALONE && ( Owner() != ::sn_myNetID || !destinationList ) )
1290 {
1291 currentDestination=dest;
1292 // con << "setting new cd\n";
1293 }
1294 }
1295
1296 // *******************************************************************************************
1297 // *
1298 // * OnDropTempWall
1299 // *
1300 // *******************************************************************************************
1301 //!
1302 //! @param wall the wall the other cycle is grinding
1303 //! @param pos the position of the grind
1304 //! @param dir the direction the raycast triggering the gridding comes from
1305 //!
1306 // *******************************************************************************************
1307
OnDropTempWall(gPlayerWall * wall,eCoord const & pos,eCoord const & dir)1308 void gCycleMovement::OnDropTempWall( gPlayerWall * wall, eCoord const & pos, eCoord const & dir )
1309 {
1310 }
1311
1312 // *******************************************************************************************
1313 // *
1314 // * GetDestinationBefore
1315 // *
1316 // *******************************************************************************************
1317 //!
1318 //! @param sync the sync data received from the server
1319 //! @param first the first candidate for the return value
1320 //! @return the destination entry that was last processed on the server
1321 //!
1322 // *******************************************************************************************
1323
GetDestinationBefore(const SyncData & sync,gDestination * first)1324 gDestination * gCycleMovement::GetDestinationBefore( const SyncData & sync, gDestination * first )
1325 {
1326 // message IDs smaller than 16 don't exist
1327 if ( sync.messageID != 1 )
1328 {
1329 gDestination * ret = first;
1330
1331 // deterimine last passed destination by the message ID
1332 while ( ret && ret->messageID != sync.messageID )
1333 ret = ret->next;
1334
1335 // return match
1336 return ret;
1337 }
1338 else
1339 {
1340 // calculate the distance of the last turn of the sync
1341 REAL syncLastTurnDistance = sync.distance - ( sync.pos - sync.lastTurn ).Norm();
1342
1343 // message ID not available; must use heuristics
1344 gDestination * run = first; // destination iterator
1345 gDestination * bestMatch = NULL; // the best message that fit the sync data and that lies before the sync
1346 REAL bestMatchDistance = 1E+20; // the distance of the best message to the sync
1347 bool braking = false; // braking causes trouble here. Activate extra checks if brakes are involved
1348 while ( run )
1349 {
1350 // calculate discrepancy of last turn distance of sync to the current message's distance
1351 REAL distanceBefore = syncLastTurnDistance - run->distance;
1352 REAL distanceAfter = run->distance - sync.distance;
1353
1354
1355 // the allowed values for run->distance are inside the interval [ syncLastTurnDistance,sync.distance ]
1356 // distance is a metric that is positive outside of the interval and
1357 // negative inside it, with minimum in the center.
1358 REAL distance = distanceBefore < distanceAfter ? distanceBefore : distanceAfter;
1359
1360 // activate brake trouble compensation
1361 if ( distance < 0 && ( run->braking || sync.braking ) )
1362 {
1363 // void previous match
1364 if ( !braking )
1365 bestMatchDistance += 1;
1366 braking = true;
1367 }
1368
1369 // clamp distance to nonnegative values to give points inside the allowed interval
1370 // equal chances
1371 if ( distance < 0 )
1372 distance = 0;
1373
1374 // prefer destinations close to the end of the allowed interval if braking is involved, else destinations close to the beginning
1375 if ( braking )
1376 {
1377 distance += fabs( distanceAfter + .01 * distanceBefore ) * .0001;
1378 }
1379 else
1380 {
1381 distance += fabs( distanceBefore ) * .1;
1382 }
1383
1384 // see if brake status and driving direction match; this is a must
1385 if ( eCoord::F( run->direction, sync.dir ) > .9*sync.dir.NormSquared() && run->braking == ( sync.braking != 0 ) )
1386 {
1387 if ( !bestMatch || distance < bestMatchDistance )
1388 {
1389 bestMatch = run;
1390 bestMatchDistance = distance;
1391 }
1392 }
1393 run = run->next;
1394 }
1395
1396 // con << bestMatchDistance << "\n";
1397
1398 // return match
1399 return bestMatch;
1400 }
1401 }
1402
1403 // *******************************************************************************************
1404 // *
1405 // * EdgeIsDangerous
1406 // *
1407 // *******************************************************************************************
1408 //!
1409 //! @param wall the wall to check for danger
1410 //! @param time the time of the possible collision
1411 //! @param alpha the local wall coordinate of the collision
1412 //! @return true if the wall is dangerous
1413 //!
1414 // *******************************************************************************************
1415
EdgeIsDangerous(const eWall * wall,REAL time,REAL alpha) const1416 bool gCycleMovement::EdgeIsDangerous( const eWall * wall, REAL time, REAL alpha ) const
1417 {
1418 if (!wall)
1419 return false;
1420
1421 const gPlayerWall *w = dynamic_cast<const gPlayerWall*>(wall);
1422 if (w)
1423 {
1424 // have we entered a hole? Is the wall breaking down? Have we passed behind the end?
1425 if ( !w->IsDangerous( alpha, time ) )
1426 return false;
1427
1428 // get time the wall was built
1429 // REAL builtTime = w->Time(alpha);
1430
1431 //gCycleMovement *otherPlayer=w->CycleMovement();
1432 //if (otherPlayer==this && time < builtTime+2.5*GetTurnDelay() )
1433 // return false; // impossible to make such a small circle
1434 // no, not impossible, just moderately unlikely.
1435 }
1436
1437 return true; // it is a real eWall.
1438 }
1439
1440 // *******************************************************************************************
1441 // *
1442 // * Turn
1443 // *
1444 // *******************************************************************************************
1445 //!
1446 //! @param dir negative for left turns, positive for right turns
1447 //! @return true if the turning was successful
1448 //!
1449 // *******************************************************************************************
1450
Turn(REAL dir)1451 bool gCycleMovement::Turn( REAL dir )
1452 {
1453 if (dir>0)
1454 return Turn(1);
1455 else if (dir<0)
1456 return Turn(-1);
1457 else
1458 return false;
1459 }
1460
1461 // *******************************************************************************************
1462 // *
1463 // * Turn
1464 // *
1465 // *******************************************************************************************
1466 //!
1467 //! @param dir +1 for right turns, -1 for left turns
1468 //! @return true if the turning was successful
1469 //!
1470 // *******************************************************************************************
1471
Turn(int dir)1472 bool gCycleMovement::Turn( int dir )
1473 {
1474 return DoTurn( dir );
1475 }
1476
sg_DropTempWall(eCoord const & dir,gSensor const & sensor)1477 static void sg_DropTempWall( eCoord const & dir, gSensor const & sensor )
1478 {
1479 tASSERT( sensor.ehit );
1480
1481 if (sn_GetNetState() != nCLIENT )
1482 {
1483 // if the wall is temporary, let its cycle drop it so it gets copied into the grid
1484 // and makes less phasing problems
1485 // don't drop parallel walls
1486 eCoord vec = sensor.ehit->Vec();
1487 if ( fabs( dir * vec ) < vec.Norm() * .5 )
1488 return;
1489
1490 // get the wall
1491 eWall * ew = sensor.ehit->GetWall();
1492 tASSERT( ew );
1493 gPlayerWall* w = dynamic_cast< gPlayerWall * >( ew );
1494
1495 // check if wall exists
1496 if ( w )
1497 {
1498 // get the cycle
1499 gCycleMovement* other = w->CycleMovement();
1500
1501 // let it drop wall
1502 if ( other )
1503 other->DropTempWall( w, sensor.before_hit, dir );
1504 }
1505 }
1506 }
1507
1508 //! information about obstacle encountered by MaxSpaceAhead
1509 struct gMaxSpaceAheadHitInfo
1510 {
1511 eCoord pos; //!< the location it was hit at
1512 REAL offset; //!< offset from mindistance values, to be subtracted from wall distacne
1513
1514 tJUST_CONTROLLED_PTR< eHalfEdge const > edge; //!< the edge that was hit
1515 tJUST_CONTROLLED_PTR< gPlayerWall > playerWall; //!< the player wall that was hit
1516 REAL wallAlpha; //!< the wall alpha value of the hit
1517
gMaxSpaceAheadHitInfogMaxSpaceAheadHitInfo1518 gMaxSpaceAheadHitInfo()
1519 : offset(0), wallAlpha(.5)
1520 {}
1521 };
1522
1523 // clearer of that data
gMaxSpaceAheadHitInfoClearer(gMaxSpaceAheadHitInfo * & info)1524 gMaxSpaceAheadHitInfoClearer::gMaxSpaceAheadHitInfoClearer( gMaxSpaceAheadHitInfo * & info )
1525 : info_( info ){}
1526
~gMaxSpaceAheadHitInfoClearer()1527 gMaxSpaceAheadHitInfoClearer::~gMaxSpaceAheadHitInfoClearer()
1528 {
1529 gMaxSpaceAheadHitInfo * info = info_;
1530 if ( info )
1531 {
1532 // we can't have the edges lingering around with possibly incomplete data
1533 info->edge = NULL;
1534 info->playerWall = NULL;
1535 }
1536 }
1537
sg_Gap(gSensor const & front,gSensor const & side,eCoord const & dir,REAL norm,REAL def,REAL & tolerance)1538 static REAL sg_Gap( gSensor const & front, gSensor const & side, eCoord const & dir, REAL norm, REAL def, REAL & tolerance )
1539 {
1540 if ( side.ehit && side.ehit->Other() )
1541 {
1542 // determine the adistance of the two endpoints of the side edge
1543 // to the wall in front of us
1544 REAL gap1 = ( front.ehit->Vec()*( *side.ehit->Point() - *front.ehit->Point() ) )/norm;
1545 REAL gap2 = ( front.ehit->Vec()*( *side.ehit->Other()->Point() - *front.ehit->Point() ) )/norm;
1546
1547 // correct for orientation using the current driving direction
1548 REAL sign = (dir * front.ehit->Vec())/norm;
1549 if ( sign != 0 )
1550 {
1551 sign = 1/sign;
1552 gap1 *= sign;
1553 gap2 *= sign;
1554 }
1555
1556 // if both values are positive, update the cache with the smaller one
1557 tolerance = ( fabs(gap1) + fabs(gap2) ) * EPS * 10;
1558 REAL minGap = gap1 < gap2 ? gap1 : gap2;
1559
1560 return minGap;
1561 }
1562 else
1563 {
1564 // return something close to the front wall
1565 tolerance = EPS * 10 * front.hit;
1566 return def;
1567 }
1568 }
1569
1570 static REAL sg_rubberCycleMinDistanceGap = .0f; // if != 0, CYCLE_RUBBER_MINDISTANCE effectively is never bigger than this value times the size of any detected gaps the cylce can squeeze through.
1571 static REAL sg_rubberCycleMinDistanceGapSide = .5f; // Gaps may be detected only if the cycle is able to drive into them in this time
1572
1573 static nSettingItemWatched<REAL> c_rcmdg("CYCLE_RUBBER_MINDISTANCE_GAP",
1574 sg_rubberCycleMinDistanceGap, nConfItemVersionWatcher::Group_Bumpy, 14 );
1575 static nSettingItem<REAL> c_rcmdgs("CYCLE_RUBBER_MINDISTANCE_GAP_SIDE",
1576 sg_rubberCycleMinDistanceGapSide);
1577
1578 // *******************************************************************************************
1579 // *
1580 // * MaxSpaceAhead
1581 // *
1582 // *******************************************************************************************
1583 //! determines how much a given cycle is allowed to drive ahead without getting too close to the next wall. Looks exactly lookAhead into the future.
1584 //!
1585 //! @param cycle the cycle to investigate
1586 //! @param lookAhead minimum distance to look ahead
1587 //! @param rubber expected to be used up until we hit the wall
1588 //! @param extra info storage space
1589 //! @return distance from the cycle to the next wall
1590 //!
1591 // *******************************************************************************************
1592
1593 // *******************************************************************************************
1594 // *
1595 // * GetMaxSpaceAhead
1596 // *
1597 // *******************************************************************************************
1598 //! determines how much this cycle is allowed to drive ahead without getting too close to the next wall. Looks exactly lookAhead into the future.
1599 //!
1600 //! @param maxReport maximal distance to report
1601 //! @return distance from the cycle to the next wall
1602 //!
1603 // *******************************************************************************************
1604
GetMaxSpaceAhead(REAL maxReport) const1605 REAL gCycleMovement::GetMaxSpaceAhead( REAL maxReport ) const
1606 {
1607 // refresh hit info if required
1608 if ( refreshSpaceAhead_ )
1609 {
1610 refreshSpaceAhead_ = false;
1611
1612 // make sure the raycast is long enoigh
1613 REAL lookAhead = maxSpaceMaxCast_;
1614 if ( maxReport > lookAhead )
1615 {
1616 lookAhead = maxReport;
1617 }
1618
1619 sg_ArchiveReal( lookAhead, 9 );
1620
1621 // store data here for later
1622 gMaxSpaceAheadHitInfo info;
1623
1624 // calculate the relevant minimal distance
1625 REAL mindistance = sg_rubberCycleMinDistance;
1626 {
1627 // get rubber values
1628 REAL rubber_granted, rubberEffectiveness;
1629 sg_RubberValues( player, verletSpeed_, rubber_granted, rubberEffectiveness );
1630
1631 // add the reservoir dependant term
1632 if ( rubber_granted > 0 )
1633 {
1634 // rubber usage speed
1635 REAL rubberUsageSpeed = verletSpeed_ * ( 1 - rubberSpeedFactor ) / rubberEffectiveness;
1636 // rubber used till end of frame
1637 REAL rubberUsed = rubberUsageSpeed * lastTimestep_;
1638
1639 // fill ratio of rubber at the end of the next frame
1640 REAL filling = ( GetRubber() + rubberUsed )/rubber_granted;
1641 if ( filling > 1 )
1642 filling = 1;
1643 mindistance += sg_rubberCycleMinDistanceReservoir * (1-filling);
1644 }
1645
1646 // add the bad preparation dependant term
1647 if ( sg_rubberCycleMinDistancePreparation > 0 )
1648 {
1649 REAL badPreparation = sg_rubberCycleMinDistancePreparation/( sg_rubberCycleMinDistancePreparation + ( this->LastTime() - this->GetLastTurnTime() ) );
1650 mindistance += sg_rubberCycleMinDistanceUnprepared * badPreparation;
1651 }
1652 }
1653 sg_ArchiveReal( mindistance, 9 );
1654
1655 // since we are going to subtract the rubber min distance from the found hit, we'll still have to llok a bit further:
1656 lookAhead += mindistance * sg_rubberCycleMinDistanceLegacy * 2;
1657
1658 // be a little nice and don't drive into the eWall if turning is allowed
1659 gSensor fr( const_cast< gCycleMovement* >(this), this->Position(), this->Direction() );
1660 {
1661 REAL speed = this->Speed();
1662 if ( speed > 0 )
1663 fr.SetInverseSpeed( 1 / speed );
1664 }
1665 fr.detect( lookAhead );
1666
1667 info.edge = fr.ehit;
1668 info.pos = fr.before_hit;
1669
1670 if ( fr.ehit )
1671 {
1672 {
1673 // get the wall of the hit
1674 eWall * w = info.edge->GetWall();
1675 if ( !w && info.edge->Other() )
1676 {
1677 info.edge = info.edge->Other();
1678 w = info.edge->GetWall();
1679 }
1680
1681 gPlayerWall * wall = dynamic_cast< gPlayerWall * >( w );
1682 if ( wall && wall->Cycle() )
1683 {
1684 // get the position of the hit and store everything
1685 info.wallAlpha = info.edge->Ratio( info.pos );
1686 info.playerWall = wall;
1687 }
1688 }
1689
1690 #ifdef DEBUG
1691 {
1692 gSensor fr2( const_cast< gCycleMovement* >( this ), this->Position(), this->Direction() );
1693 fr2.detect( lookAhead );
1694 }
1695 #endif
1696
1697 REAL stopDistance = 0.1;
1698 if ( fr.ehit )
1699 {
1700 REAL norm = fr.ehit->Vec().Norm();
1701 stopDistance = mindistance + sg_rubberCycleMinDistanceRatio * norm;
1702
1703 ::sg_DropTempWall( this->Direction(), fr );
1704
1705 // enforce "open" play: every successive grind to a wall can get closer and closer.
1706
1707 REAL rubberCycleMinDistanceGapDistance = sg_rubberCycleMinDistanceGapSide * Speed();
1708
1709
1710 if ( sg_rubberCycleMinDistanceGap > 0 )
1711 {
1712 // determine the width of the gap previous grinders left
1713 for ( int dir = -1; dir < 2; dir += 2 )
1714 {
1715 // see if cached value is still good
1716 REAL & gapCache = gap_[(dir+1)/2];
1717 bool & keepLooking = keepLookingForGap_[(dir+1)/2];
1718
1719 if ( gapCache > fr.hit && keepLooking )
1720 {
1721 // determine next direction when turning into dir
1722 int wn = windingNumberWrapped_;
1723 Grid()->Turn(wn, dir);
1724 eCoord dirCast = Grid()->GetDirection(wn);
1725
1726 bool gapFound = false;
1727 for ( int back = -1; back <= 2; ++back )
1728 {
1729 // determine next direction when turning into dir
1730 int wn2 = wn;
1731 Grid()->Turn(wn2, back);
1732 eCoord dirCast2 = Grid()->GetDirection(wn2);
1733
1734 // send out a side sensor
1735 gSensor side( const_cast< gCycleMovement * >( this ),
1736 this->Position(),
1737 ( dirCast + dirCast2 ) * .5 );
1738
1739 side.detect( rubberCycleMinDistanceGapDistance );
1740
1741 // only allow non-hit default search for the ray that goes straight to the side
1742 if ( back != 0 && !side.ehit )
1743 continue;
1744
1745 REAL tolerance;
1746 REAL minGap = sg_Gap( fr, side, dirDrive, norm, fr.hit * .5, tolerance );
1747
1748 while ( minGap > tolerance )
1749 {
1750 // last test: see if there really is a gap after that wall ends
1751 gSensor side2( const_cast< gCycleMovement * >( this ),
1752 this->Position() + this->Direction() * ( fr.hit - minGap * .9 ),
1753 dirCast );
1754 side2.detect( rubberCycleMinDistanceGapDistance );
1755
1756 // if this sensor did not hit or hit farther than the first sensor, the gap is real
1757 if ( fabs(side2.hit - side.hit) < tolerance )
1758 {
1759 // true gap not found yet
1760 REAL dumpTolerance;
1761 REAL lastMinGap = minGap;
1762 minGap = sg_Gap( fr, side2, dirDrive, norm, minGap * .5, dumpTolerance );
1763 // no improvement? give up.
1764 if ( minGap >= lastMinGap * .9 )
1765 break;
1766 }
1767 else
1768 {
1769 gapFound = true;
1770
1771 // true gap found, is it smaller than the last one?
1772 if ( minGap < gapCache )
1773 {
1774 gapCache = minGap;
1775
1776 // bail out of outer loop
1777 back = 100;
1778 }
1779
1780 // bail out of inner loop
1781 break;
1782 }
1783 }
1784 }
1785
1786 // no gap to see anywhere
1787 if ( ! gapFound )
1788 {
1789 // don't waste time looking from now on
1790 keepLooking = false;
1791
1792 // if there was no gap detected so far, there is no gap.
1793 if ( gapCache > 5E+19 )
1794 gapCache = 0;
1795 }
1796 }
1797 }
1798
1799 // fetch cache, ignoring zeroes
1800 REAL gap = ( ( gap_[0] > 0 ? gap_[0] : 1E+30 ) < ( gap_[1] > 0 ? gap_[1] : 1E+30 ) ) ? gap_[0] : gap_[1];
1801 if ( gap > 0 )
1802 {
1803 REAL minDistanceGap = gap * sg_rubberCycleMinDistanceGap;
1804 if ( stopDistance > minDistanceGap )
1805 stopDistance = minDistanceGap;
1806 }
1807 }
1808 }
1809 sg_ArchiveReal( stopDistance, 9 );
1810
1811
1812 // revert to almost old rubber logic if old clients are connected. This may cause rips, but we don't care.
1813 if ( sg_rubberCycleLegacy && !sg_nonRippable.Supported() && stopDistance > .001 )
1814 stopDistance = .001;
1815
1816 // if there is a rippable peer connected and the wall is the rim wall, add extra distance
1817 //if ( fr.type == gSENSOR_RIM && !sg_nonRippable.Supported() )
1818 // stopDistance *= sg_rubberCycleMinDistanceLegacy;
1819
1820 REAL space = fr.hit;
1821 sg_ArchiveReal( space, 9 );
1822
1823 // see if we just did a turn
1824 REAL distSinceLastTurn = this->GetDistanceSinceLastTurn();
1825
1826 // we want to get closer to the wall by at least some percentage
1827 REAL maxStop = ( distSinceLastTurn + space ) * ( 1 - sg_rubberCycleMinAdjust );
1828 if ( maxStop < stopDistance )
1829 {
1830 stopDistance = maxStop;
1831 }
1832
1833 sg_ArchiveReal( stopDistance, 9 );
1834
1835 // add safety
1836 REAL safety = this->Position().Norm() * 2 * EPS;
1837
1838 info.offset = stopDistance + safety;
1839
1840 sg_ArchiveReal( space, 9 );
1841
1842 // create new hit info
1843 if ( !maxSpaceHit_ )
1844 maxSpaceHit_ = tNEW( gMaxSpaceAheadHitInfo );
1845
1846 // store information
1847 *maxSpaceHit_ = info;
1848 }
1849 else
1850 {
1851 // delete information
1852 delete maxSpaceHit_;
1853 maxSpaceHit_ = NULL;
1854 }
1855 }
1856
1857 // information up to date? Good, just take the distance to the collision point.
1858 REAL ret = 1E+30;
1859 if ( maxSpaceHit_ )
1860 {
1861 ret = eCoord::F( dirDrive, maxSpaceHit_->pos - pos ) - maxSpaceHit_->offset;
1862 }
1863
1864 // clamp it and return.
1865 if ( ret > maxReport )
1866 ret = maxReport;
1867 return ret;
1868 }
1869
1870 // *******************************************************************************************
1871 // *
1872 // * MaxSpaceAhead
1873 // *
1874 // *******************************************************************************************
1875 //! determines how much a given cycle is allowed to drive ahead without getting too close to the next wall. Looks at least lookAhead into the future, but never reports more than maxReport as result. The next timestep is assumed to be ts seconds long.
1876
1877 //! @param cycle the cycle to investigate
1878 //! @param ts the next timestep
1879 //! @param lookAhead minimum distance to look ahead
1880 //! @param maxReport maximum return value
1881 //! @return distance from the cycle to the next wall
1882 //!
1883 // *******************************************************************************************
1884
1885 /*
1886 float MaxSpaceAhead( const gCycleMovement* cycle, float ts, float lookAhead, float maxReport )
1887 {
1888 // lookahead should be at least the next expected timestep ( times two for safety )
1889 REAL step=cycle->Speed() * ts * 2;
1890 if ( lookAhead < step )
1891 lookAhead = step;
1892
1893 // lookahead should be at least maxReport
1894 if ( lookAhead < maxReport )
1895 lookAhead = maxReport;
1896
1897 REAL space = MaxSpaceAhead( cycle, lookAhead );
1898
1899 if ( space < maxReport )
1900 return space;
1901 else
1902 return maxReport;
1903 }
1904 */
1905
1906 // feature indicating that a client sends the time of turn commands
1907 static nVersionFeature sg_CommandTime( 4 );
1908
1909 // server side feature of lag sliding correction code
1910 static nVersionFeature sg_AntiLag( 5 );
1911
1912 // flag indicating whether to use the logic to prevent lag sliding only when every connected client can benefit from it
1913 static bool sg_fairAntiLagSliding=true;
1914 static nSettingItem<bool> c_fals("CYCLE_FAIR_ANTILAG",sg_fairAntiLagSliding);
1915
1916 // check if we should apply anti-lag-sliding code
sg_UseAntiLagSliding(const eNetGameObject * obj)1917 static bool sg_UseAntiLagSliding( const eNetGameObject* obj )
1918 {
1919 tASSERT( obj );
1920
1921 // cant do anyting for old clients that don't send the time of commands
1922 if ( !sg_CommandTime.Supported( obj->Owner() ) )
1923 return false;
1924
1925 // likewise if the server does not support anti lag code and this is the client
1926 if ( sn_GetNetState() == nCLIENT && !sg_AntiLag.Supported( obj->Owner() ) )
1927 return false;
1928
1929 // check whether the command time is sent by everyone or at least the object owner ( depending on fairness )
1930 if ( sg_fairAntiLagSliding )
1931 {
1932 return sg_CommandTime.Supported();
1933 }
1934 else
1935 {
1936 // we already checked whether the command time was sent
1937 return true;
1938 }
1939 }
1940
1941 // while an object of this class exists, turn delay is ignored
1942 class gTurnDelayOverride
1943 {
1944 public:
gTurnDelayOverride(bool override)1945 explicit gTurnDelayOverride( bool override )
1946 {
1947 delay_ = sg_delayCycle;
1948 if ( override )
1949 sg_delayCycle = 0.0f;
1950 }
1951
gTurnDelayOverride(REAL factor)1952 explicit gTurnDelayOverride( REAL factor )
1953 {
1954 delay_ = sg_delayCycle;
1955 sg_delayCycle *= factor;
1956 }
1957
~gTurnDelayOverride()1958 ~gTurnDelayOverride()
1959 {
1960 sg_delayCycle = delay_;
1961 }
1962 private:
1963 REAL delay_;
1964 };
1965
1966 static nVersionFeature sg_noRedundantBrakeCommands( 13 );
1967
1968 // *******************************************************************************************
1969 // *
1970 // * Timestep
1971 // *
1972 // *******************************************************************************************
1973 //!
1974 //! @param currentTime the time to simulate up to
1975 //! @return true if the cycle is to be deleted
1976 //!
1977 // *******************************************************************************************
1978
Timestep(REAL currentTime)1979 bool gCycleMovement::Timestep( REAL currentTime )
1980 {
1981 /*
1982 static int count = 1;
1983 ++count;
1984 if ( count == 0 )
1985 {
1986 int xc;
1987 xc = 0;
1988 }
1989 */
1990
1991 // request regeneration of maximum space
1992 refreshSpaceAhead_ = true;
1993
1994 // clear out dangerous info when we're done
1995 gMaxSpaceAheadHitInfoClearer hitInfoClearer( maxSpaceHit_ );
1996
1997 // clamp stuff to finite values
1998 clamp( rubber, 0, sg_rubberCycle );
1999
2000 // keep this cycle alive
2001 tJUST_CONTROLLED_PTR< gCycleMovement > keep( this->GetRefcount()>0 ? this : 0 );
2002
2003 // don't make a fuss about negative timesteps
2004 if ( currentTime < lastTime )
2005 return TimestepCore( currentTime );
2006
2007 // remove old destinations
2008 //REAL lag = 1;
2009 //if ( player )
2010 // lag = player->ping;
2011 // REAL lagDistance = lag * Speed() * 10;
2012
2013 while(destinationList && destinationList->hasBeenUsed && !IsDestinationUsed( destinationList ) )
2014 delete destinationList;
2015
2016 // calculate timestep
2017 REAL dt = currentTime - lastTime;
2018
2019 sg_ArchiveReal( dt, 9 );
2020
2021 // if (currentTime > lastTime)
2022 {
2023 int timeout=10;
2024 bool forceTurn = false; // force turning at the next iteration
2025 bool overrideTurnDelay=false; // override the turn delay system, turn immediately
2026
2027 // only simulate forward in time
2028 while (pendingTurns.empty() && currentDestination && timeout > 0 )
2029 {
2030 timeout --;
2031
2032 // the distance to the next destination
2033 REAL dist_to_dest = DistanceToDestination( *currentDestination );
2034 sg_ArchiveReal( dist_to_dest, 9 );
2035
2036 REAL ts=currentTime - lastTime;
2037 sg_ArchiveReal( ts, 9 );
2038
2039 sg_ArchiveReal( verletSpeed_, 9 );
2040 sg_ArchiveReal( acceleration, 9 );
2041
2042 // our speed
2043 REAL avgspeed=verletSpeed_;
2044 CalculateAcceleration();
2045 if (acceleration > 0)
2046 avgspeed += acceleration * SpeedMultiplier() * ts * .5;
2047
2048 // will rubber slow the cycle down to a crawl, so that the command time will be
2049 // a better indication when to turn than the position?
2050 bool rubberActive = false;
2051
2052 // check if the path to the destination is clear for the next timesteps
2053 sg_ArchiveReal( rubberSpeedFactor, 9 );
2054 REAL distToWall=1E+30;
2055 if ( rubberSpeedFactor < .999 )
2056 {
2057 // take rubber activity into account
2058 rubberActive = true;
2059 avgspeed *= rubberSpeedFactor;
2060 if ( avgspeed < EPS )
2061 avgspeed = EPS;
2062
2063 sg_ArchiveReal( avgspeed, 9 );
2064
2065 // don't drive into a wall, turn before getting too close
2066 REAL lookahead = ts * avgspeed * 2;
2067
2068 REAL dist_to_wall = GetMaxSpaceAhead( lookahead );
2069
2070 if ( dist_to_dest > dist_to_wall )
2071 dist_to_dest = dist_to_wall;
2072 }
2073
2074 // static bool breakp = false;
2075
2076 // the time left until the turn happened on the client
2077 // REAL timeLeft = currentDestination->GetGameTime() - lastTime;
2078
2079 // the earliest and latest allowed time to turn
2080 REAL turnTime = currentDestination->GetGameTime();
2081 REAL earliestTurnTime = turnTime - sg_timeTolerance - 100;
2082 REAL latestTurnTime = turnTime + sg_timeTolerance;
2083
2084 // if rubber is active and anti lag sliding code should be enabled,
2085 // then allow no too early or late turns
2086 if ( sg_UseAntiLagSliding( this ) )
2087 {
2088 if ( rubberActive )
2089 {
2090 // smoothly make the allowed time interval smaller with increased rubber use
2091 earliestTurnTime = turnTime - sg_timeTolerance * rubberSpeedFactor;
2092 latestTurnTime = turnTime + sg_timeTolerance * rubberSpeedFactor;
2093
2094 // clamp latest turn time so we don't drive into the wall
2095 if ( verletSpeed_ > 0 )
2096 {
2097 REAL maxRubber, effectiveness;
2098 sg_RubberValues( Player(), verletSpeed_, maxRubber, effectiveness );
2099
2100 // see when we'll die (be a little careful, the .9 is a fudge factor)
2101 REAL rubberLeft = (maxRubber - rubber)*.9;
2102 REAL stepLeft = rubberLeft + distToWall;
2103 REAL timeLeft = stepLeft/verletSpeed_;
2104 REAL deathTime = lastTime + timeLeft;
2105
2106 // can't do a thing if the player wants to die
2107 if ( deathTime < turnTime )
2108 deathTime = turnTime;
2109
2110 // clamp latest possible time
2111 if ( latestTurnTime > deathTime )
2112 latestTurnTime = deathTime;
2113 }
2114 }
2115 else
2116 {
2117 // just clamp the latest turn time
2118 earliestTurnTime = turnTime - sg_timeTolerance;
2119 }
2120 }
2121
2122 sg_ArchiveReal( dist_to_dest, 9 );
2123
2124 REAL simulateAhead = MaxSimulateAhead();
2125
2126 if ( dist_to_dest > ( ts + simulateAhead ) * avgspeed && currentTime < latestTurnTime )
2127 break; // no need to worry; we won't reach the next destination
2128
2129 if ( currentTime < earliestTurnTime && sg_CommandTime.Supported( Owner() ) )
2130 break; // the turn is too far in the future
2131
2132 // if ( currentTime < turnTime + EPS )
2133 // simulateAhead = 0;
2134
2135 // determine whether to turn left or right (coping with weird axis settings)
2136 int turnTo=0;
2137 // and whether between the last and next destination, there was one missing that
2138 // we didn't receive.
2139 bool missed=false;
2140
2141 {
2142 REAL t = currentDestination->direction * dirDrive;
2143 bool turn = true;
2144
2145 missed = (fabs(t)<.01);
2146 if (int(braking) != int(currentDestination->braking))
2147 {
2148 turn = false;
2149 missed=!missed;
2150 }
2151
2152 // detect false misses: if the last destination's message ID is just
2153 // one below the current destination's message ID, it's a fake
2154 if ( missed && lastDestination && lastDestination->messageID == currentDestination->messageID-1 )
2155 {
2156 missed = false;
2157 if ( ( dirDrive - currentDestination->direction ).NormSquared() < EPS )
2158 {
2159 turn = false;
2160 turnTo = 0;
2161 }
2162 }
2163
2164 // if the destination is dead ahead, it can't be a fake, either
2165 if ( missed && sn_GetNetState() == nSERVER && !sg_noRedundantBrakeCommands.Supported( Owner() ) )
2166 {
2167 // calculate difference in expected position at the destination's time and the transmitted position
2168 REAL timeToDest = currentDestination->GetGameTime() - lastTime;
2169 eCoord posDelta = pos + dirDrive * ( timeToDest * ( verletSpeed_ + .5f * acceleration * timeToDest ) ) - currentDestination->position;
2170 REAL deltaParallel = eCoord::F( posDelta, dirDrive );
2171 REAL deltaOrthogonal = posDelta * dirDrive;
2172
2173 // if it's small, that's not a miss
2174 REAL tolerance = verletSpeed_ * GetTurnDelay();
2175 if ( fabs(deltaParallel) < tolerance && fabs(deltaOrthogonal) < tolerance * .5 )
2176 {
2177 missed = false;
2178 if ( ( dirDrive - currentDestination->direction ).NormSquared() < EPS )
2179 {
2180 turn = false;
2181 }
2182 }
2183 }
2184
2185 // see if we missed a turn by, say, just counting?
2186 if ( turns < currentDestination->turns - 1 )
2187 missed = true;
2188
2189 if ( turn )
2190 {
2191 // the direction we need to drive in
2192 // see which direction we drive into after a left or right turn
2193 int wn = windingNumberWrapped_;
2194 Grid()->Turn(wn, 1);
2195 eCoord dirPlus = Grid()->GetDirection(wn);
2196 wn = windingNumberWrapped_;
2197 Grid()->Turn(wn, -1);
2198 eCoord dirMinus = Grid()->GetDirection(wn);
2199
2200 if ( missed )
2201 {
2202 eCoord dirTurn = (currentDestination->position - pos);
2203
2204 // see witch of the alternatives comes closer to the desired direction
2205 turnTo = ( ( fabs( dirMinus * dirTurn ) - .1 * eCoord::F( dirMinus, dirTurn ) )/dirTurn.NormSquared() < ( fabs( dirPlus * dirTurn ) - .1 * eCoord::F( dirPlus, dirTurn ) )/dirTurn.NormSquared() ) ? -1 : +1;
2206 }
2207 else
2208 {
2209 // just see which axis gets closer
2210 eCoord dirTurn = currentDestination->direction;
2211
2212 turnTo = ( ( dirMinus - dirTurn ).NormSquared() < ( dirPlus - dirTurn ).NormSquared() ) ? -1 : +1;
2213
2214 }
2215 }
2216 }
2217
2218 // can we turn already?
2219 bool canTurn = ( turnTo == 0 || CanMakeTurn(turnTo) || overrideTurnDelay );
2220
2221 if ( lastTime >= earliestTurnTime && canTurn && ( forceTurn || dist_to_dest < 0.01 || timeout <= 0 || lastTime >= latestTurnTime ) ){
2222 forceTurn = false;
2223
2224 #ifdef DEBUG
2225 if ( turnTo != 0 )
2226 {
2227 static REAL checkFactor = .9f;
2228 gTurnDelayOverride check( checkFactor );
2229 if ( !CanMakeTurn( turnTo ) )
2230 {
2231 con << "Early turn!\n";
2232 // st_Breakpoint();
2233 }
2234 }
2235 #endif
2236
2237
2238 // con << timeLeft << ", " << earlyTurnTolerance << ", " << rubberActive << ", " << dist_to_dest << "\n";
2239
2240 // the destination is very close or we gave up. Now is the time to turn towards
2241 // it or turn to the direction it gives us
2242
2243 // bring us to the exact location to avoid lag sliding due to
2244 // disagreement between client and server.
2245 // but only if we are reasonably close ( don't want cheating )
2246 // and use a correct move that kills us if we cross a wall.
2247
2248 /*
2249 if ( dist_to_dest < .1 && dist_to_dest > -.1 )
2250 {
2251 Move( pos + dirDrive * dist_to_dest, currentTime, currentTime );
2252 }
2253 #ifdef DEBUG
2254 else
2255 {
2256 if ( breakp )
2257 {
2258 int x;
2259 x = 0;
2260 }
2261 con << "gCycle::Timestep: Could not completely reach destination.\n";
2262 breakp = true;
2263 }
2264 #endif
2265 */
2266
2267 // con << "Turn: " << lastTime << ", " << dist_to_dest << ", " << currentDestination->position << ", " << pos << "\n";
2268
2269 //eDebugLine::SetTimeout(2);
2270 //eDebugLine::SetColor( 0,1,1 );
2271 //eDebugLine::Draw( currentDestination->position, 0, currentDestination->position, 8 );
2272 //eDebugLine::SetColor( 0,0,1 );
2273 //eDebugLine::Draw( pos, 0, pos, 8 );
2274
2275 bool used = false; // flag indicating whether the current destination has been used
2276
2277 if (!missed){ // then we did not miss a destination
2278 used = true;
2279
2280 if (turnTo != 0)
2281 {
2282 #ifdef DEBUG
2283 #ifdef DEDICATED
2284 eCoord slide = this->pos - currentDestination->position;
2285 if ( Player() && slide.NormSquared() > .01 )
2286 con << "Lag slide for " << Player()->GetUserName() << ": " << slide << ", rubberSpeedFactor " << rubberSpeedFactor << "\n";
2287 #endif
2288 #endif
2289 gTurnDelayOverride override( overrideTurnDelay );
2290 Turn(turnTo);
2291 }
2292 else{
2293 AccelerationDiscontinuity();
2294 braking = currentDestination->braking;
2295 if (sn_GetNetState()!=nCLIENT)
2296 RequestSync();
2297 }
2298
2299 /*
2300 con << "turning alon " << currentDestination->position << ","
2301 << currentDestination->direction << ","
2302 << currentDestination->distance << "\n";
2303 */
2304 }
2305 else
2306 {
2307
2308 // Uh oh. Turn commands are missing. We should wait as long as possible, it must
2309 // already be on its way.
2310 if ( lastTime > currentTime - Lag()*sg_packetMissTolerance )
2311 return !Alive();
2312
2313 if ( turns >= currentDestination->turns - 1 )
2314 {
2315 // OK, we missed exactly one turn. Don't panic. Just turn
2316 // towards the destination:
2317 REAL side = (currentDestination->position - pos) * dirDrive;
2318 if ( fabs(side)>verletSpeed_ * GetTurnDelay() * .2 )
2319 {
2320 gTurnDelayOverride override( overrideTurnDelay );
2321 Turn(turnTo);
2322 }
2323 else
2324 used = true;
2325 }
2326 else
2327 {
2328 // missed more than one turn. Drat. Ignore and hope for the best.
2329 // st_Breakpoint();
2330 ++turns;
2331 }
2332 /*
2333 con << "turning to " << currentDestination->position << ","
2334 << currentDestination->direction << ","
2335 << currentDestination->distance << "\n";
2336 */
2337
2338 }
2339
2340 overrideTurnDelay = false;
2341
2342 if ( used )
2343 {
2344 // only the server marks destinations as used; the client has to reuse them sometimes.
2345 currentDestination->hasBeenUsed = (sn_GetNetState()!=nCLIENT);
2346 lastDestination = currentDestination;
2347
2348 // advance
2349 currentDestination = currentDestination->next;
2350 }
2351
2352
2353 while (currentDestination && currentDestination->hasBeenUsed)
2354 {
2355 // breakp = false;
2356 currentDestination = currentDestination->next;
2357 }
2358 }
2359 else
2360 {
2361 // ok, the dest is right ahead, but not close enough.
2362 // how long does it take at least
2363 // (therefore the strange average speed) to get there?
2364 sg_ArchiveReal( avgspeed, 9 );
2365 REAL tsTodo = dist_to_dest/avgspeed;
2366 /*
2367 if ( tsTodo > timeLeft )
2368 {
2369 tsTodo = timeLeft;
2370 }
2371 */
2372 sg_ArchiveReal( tsTodo, 9 );
2373
2374 // we can't turn now, simulate until we can
2375 if ( !canTurn )
2376 {
2377 REAL nextTurn = GetNextTurn(turnTo);
2378 REAL turnStep = nextTurn - lastTime;
2379
2380 // clamp timestep values
2381 if ( turnTime < nextTurn )
2382 turnTime = nextTurn;
2383 if ( earliestTurnTime < nextTurn )
2384 earliestTurnTime = nextTurn;
2385 if ( latestTurnTime < nextTurn )
2386 latestTurnTime = nextTurn;
2387
2388 if ( currentTime - lastTime > turnStep )
2389 {
2390 tsTodo = turnStep;
2391
2392 // if we can simulate to the turn in the next step, do so, overriding
2393 // the turn delay then.
2394 if ( tsTodo < ts + simulateAhead && tsTodo > 0 )
2395 {
2396 overrideTurnDelay = true;
2397 }
2398 }
2399 else
2400 {
2401 // not enough time to simulate to turn possibility; skip out of loop
2402 break;
2403 }
2404 }
2405 else
2406 {
2407 sg_ArchiveReal( tsTodo, 9 );
2408 // don't turn too late
2409 REAL maxts = latestTurnTime - lastTime;
2410 sg_ArchiveReal( maxts, 9 );
2411 if ( tsTodo > maxts )
2412 {
2413 // force turn on next iteration, we'll be there
2414 forceTurn = true;
2415 tsTodo = maxts;
2416 }
2417
2418 // don't turn too early
2419 REAL mints = earliestTurnTime - lastTime;
2420 // sg_ArchiveReal( mints, 9 );
2421 if ( tsTodo < mints )
2422 {
2423 tsTodo = mints;
2424 }
2425 }
2426
2427 if ( tsTodo < 0 )
2428 {
2429 // should never happen
2430 st_Breakpoint();
2431 return !Alive();
2432 }
2433 if ( tsTodo > ts + simulateAhead )
2434 {
2435 tsTodo = ts + simulateAhead ;
2436 forceTurn = false;
2437
2438 // quit from here if there is nothing to do
2439 if ( tsTodo <= EPS )
2440 break;
2441 }
2442 #ifdef DEBUG
2443 if ( tsTodo < 0 )
2444 con << "Negative timestep!\n";
2445 #endif
2446 sg_ArchiveReal( tsTodo, 9 );
2447
2448 // core simulation
2449 if ( tsTodo > EPS )
2450 {
2451 TimestepCore( lastTime + tsTodo, false );
2452 }
2453 else
2454 {
2455 // already nothing to do, turn on next iteration
2456 forceTurn = true;
2457 }
2458 }
2459 }
2460 }
2461
2462 // simulate exactly to the time of the next turn if it is in reach
2463 if ( !pendingTurns.empty())
2464 {
2465 REAL nextTurn = GetNextTurn(pendingTurns.front());
2466 if(currentTime>nextTurn) {
2467 if ( nextTurn > lastTime )
2468 TimestepCore( nextTurn );
2469
2470 //con << "Executing delayed turn at time " << lastTime << "\n";
2471 Turn(pendingTurns.front());
2472 pendingTurns.pop_front();
2473 }
2474 }
2475
2476 // do the rest of the timestep
2477 bool ret = false;
2478 if ( currentTime > lastTime )
2479 ret = TimestepCore( currentTime );
2480
2481 return ret;
2482 }
2483
2484 // *******************************************************************************************
2485 // *
2486 // * AddRef
2487 // *
2488 // *******************************************************************************************
2489 //!
2490 //!
2491 // *******************************************************************************************
2492
AddRef(void)2493 void gCycleMovement::AddRef( void )
2494 {
2495 eNetGameObject::AddRef();
2496 if ( GetRefcount() > sg_cycleMaxRefCount && Alive() )
2497 {
2498 // during the kill, further refcounts will be added, so we need to pump
2499 // up the limit
2500 int backup = sg_cycleMaxRefCount;
2501 sg_cycleMaxRefCount += 100;
2502 Kill();
2503 sg_cycleMaxRefCount = backup;
2504 }
2505 }
2506
2507 // *******************************************************************************************
2508 // *
2509 // * gCycleMovement
2510 // *
2511 // *******************************************************************************************
2512 //!
2513 //! @param grid the grid the cycle will live on
2514 //! @param pos start position
2515 //! @param dir start direction
2516 //! @param player player owning this cycle
2517 //! @param autodelete should the cycle get deleted when it gets killed?
2518 //!
2519 // *******************************************************************************************
2520
gCycleMovement(eGrid * grid,const eCoord & pos,const eCoord & dir,ePlayerNetID * player,bool autodelete)2521 gCycleMovement::gCycleMovement( eGrid * grid, const eCoord & pos, const eCoord & dir, ePlayerNetID * player, bool autodelete )
2522 :eNetGameObject(grid, pos,dir,player,autodelete),
2523 destinationList(NULL),currentDestination(NULL),lastDestination(NULL),
2524 dirDrive(dir),
2525 acceleration(0),
2526 lastTimestep_(0),
2527 verletSpeed_(sg_speedCycleStart * SpeedMultiplier()),
2528 pendingTurns()
2529 {
2530 windingNumberWrapped_ = windingNumber_ = Grid()->DirectionWinding(dir);
2531
2532 MyInitAfterCreation();
2533 }
2534
2535 // *******************************************************************************************
2536 // *
2537 // * gCycleMovement
2538 // *
2539 // *******************************************************************************************
2540 //!
2541 //! @param message network message to read everything from
2542 //!
2543 // *******************************************************************************************
2544
gCycleMovement(nMessage & message)2545 gCycleMovement::gCycleMovement( nMessage & message )
2546 :eNetGameObject(message),
2547 destinationList(NULL),currentDestination(NULL),lastDestination(NULL),
2548 dirDrive(1,0),
2549 acceleration(0),
2550 lastTimestep_(0),
2551 verletSpeed_(5)
2552 {
2553 windingNumberWrapped_ = windingNumber_ = 2;
2554
2555 // MyInitAfterCreation();
2556 }
2557
2558 // *******************************************************************************************
2559 // *
2560 // * ~gCycleMovement
2561 // *
2562 // *******************************************************************************************
2563 //!
2564 //!
2565 // *******************************************************************************************
2566
~gCycleMovement(void)2567 gCycleMovement::~gCycleMovement( void )
2568 {
2569 lastDestination = NULL;
2570 currentDestination = NULL;
2571
2572 while(destinationList)
2573 {
2574 gDestination* dest = destinationList;
2575 delete dest;
2576 }
2577
2578 verletSpeed_=distance=0;
2579
2580 delete maxSpaceHit_;
2581 maxSpaceHit_ = NULL;
2582 }
2583
RequestSync(bool ack)2584 void gCycleMovement::RequestSync(bool ack)
2585 {
2586 // no more syncs when you're dead
2587 if ( !Alive() )
2588 {
2589 return;
2590 }
2591
2592 // delegate
2593 eNetGameObject::RequestSync( ack );
2594 }
2595
RequestSync(int user,bool ack)2596 void gCycleMovement::RequestSync(int user,bool ack)
2597 {
2598 // no more syncs when you're dead
2599 if ( !Alive() )
2600 {
2601 return;
2602 }
2603
2604 // delegate
2605 eNetGameObject::RequestSync( user, ack );
2606 }
2607
OnRemoveFromGame()2608 void gCycleMovement::OnRemoveFromGame()
2609 {
2610 delete maxSpaceHit_;
2611 maxSpaceHit_ = NULL;
2612
2613 eNetGameObject::OnRemoveFromGame();
2614 }
2615
2616 // *******************************************************************************************
2617 // *
2618 // * CopyFrom
2619 // *
2620 // *******************************************************************************************
2621 //!
2622 //! @param other the cycle to copy everything from
2623 //!
2624 // *******************************************************************************************
2625
CopyFrom(const gCycleMovement & other)2626 void gCycleMovement::CopyFrom( const gCycleMovement & other )
2627 {
2628 #ifdef DEBUG_X
2629 // calculate position update
2630 eCoord posUpdate = other.Position() - this->Position();
2631
2632 // only update direction if the positions are out of sync
2633 REAL lag = 1;
2634 if ( player )
2635 lag = player->ping;
2636
2637 REAL tol = this->speed * lag;
2638 if ( posUpdate.NormSquared() > tol*tol )// && eCoord::F( dirDrive, other.dirDrive ) < .5 )
2639 {
2640 con << "Out of sync!\n";
2641 // dir = other.Direction();
2642
2643 // get second opinion
2644 tJUST_CONTROLLED_PTR<gCycleExtrapolator> extrapolator = tNEW( gCycleExtrapolator )(grid, pos, dir );
2645 gCycleExtrapolator& secondOpinion = *extrapolator;
2646 secondOpinion.CopyFrom( sg_usedMessage, *this );
2647 eGameObject::TimestepThis( other.lastTime, &secondOpinion );
2648 }
2649 #endif
2650
2651 dirDrive = other.dirDrive;
2652
2653 // transfer position and time
2654 currentFace = other.currentFace;
2655 pos = other.Position();
2656 lastTime = other.LastTime();
2657 // Move( other.Position(), LastTime(), other.LastTime() );
2658 // Move( other.Position() + other.Direction() * ( ( lastTime - other.LastTime() ) * other.Speed() ), LastTime(), other.LastTime() );
2659
2660 // std::cout << "copy: " << brakingReservoir << ":" << braking << "\n";
2661
2662 // transfer additional data
2663 team = other.team;
2664 distance = other.distance;
2665 lastTimestep_ = other.lastTimestep_;
2666 verletSpeed_ = other.verletSpeed_;
2667 acceleration = other.acceleration;
2668 rubber = other.rubber;
2669 rubberMalus = other.rubberMalus;
2670 brakingReservoir= other.brakingReservoir;
2671 windingNumber_ = other.windingNumber_;
2672 windingNumberWrapped_ = other.windingNumberWrapped_;
2673
2674 tASSERT(finite(distance));
2675
2676 // std::cout << "copy: " << brakingReservoir << ":" << braking << "\n";
2677
2678 #ifdef DEBUG_X
2679 if ( turns != other.turns )
2680 {
2681 con << "Client/Server turn mismatch:" << turns << " != " << other.turns << "\n";
2682 }
2683 #endif
2684
2685 // update number of turns if the player is not turning wildly
2686 REAL right = GetNextTurn(1);
2687 REAL left = GetNextTurn(-1);
2688 if ( lastTime > (right > left ? right : left) + 2 * GetTurnDelay() )
2689 turns = other.turns;
2690 }
2691
2692 static nVersionFeature sg_sendCorrectLastTurn(8);
2693
2694 // *******************************************************************************************
2695 // *
2696 // * CopyFrom
2697 // *
2698 // *******************************************************************************************
2699 //!
2700 //! @param sync the network sync data to copy the most important data from
2701 //! @param other the cycle to copy the rest of the information from
2702 //!
2703 // *******************************************************************************************
2704
CopyFrom(const SyncData & sync,const gCycleMovement & other)2705 void gCycleMovement::CopyFrom( const SyncData & sync, const gCycleMovement & other )
2706 {
2707 // fetch values from sync
2708 dir = dirDrive = sync.dir;
2709 lastTimestep_ = 0;
2710 verletSpeed_ = sync.speed;
2711 rubber = sync.rubber;
2712 rubberMalus = sync.rubberMalus;
2713 braking = sync.braking;
2714 distance = sync.distance;
2715 turns = sync.turns;
2716 brakingReservoir= sync.brakingReservoir;
2717 // std::cout << "fromsync: " << brakingReservoir << ":" << braking << "\n";
2718
2719 tASSERT(finite(distance));
2720
2721 // reset winding number and acceleration
2722 this->SetWindingNumberWrapped( Grid()->DirectionWinding(dirDrive) );
2723 acceleration = 0;
2724
2725 // fetch values from other
2726 // rubber = other.rubber;
2727 SetPlayer( other.Player() );
2728 currentFace = other.currentFace;
2729
2730 {
2731 //this->currentDestination = other.destinationList;
2732 //while ( currentDestination && currentDestination->messageID != sync.messageID )
2733 // currentDestination = currentDestination->next;
2734
2735 // deterimine last passed destination by the message ID
2736 this->currentDestination = GetDestinationBefore( sync, other.destinationList );
2737
2738 bool trustDestination = true;
2739 if ( currentDestination && sn_GetNetState() == nCLIENT && !sg_sendCorrectLastTurn.Supported(0) )
2740 {
2741 // the server may send wrong information in the rare case that
2742 // our last turn command was not promptly executed. Sanity check:
2743 // if the relevant information from the alleged last destination
2744 // differs from the current state, it is already the next destination.
2745 if ( ( currentDestination->braking != (bool)braking ) || fabs( currentDestination->direction * dirDrive ) > .01 )
2746 trustDestination = false;
2747 }
2748
2749 // we only need the next one
2750 if ( trustDestination && currentDestination )
2751 currentDestination = currentDestination->next;
2752 }
2753
2754 // let extrapolator find its face ( and set position and time )
2755 MoveSafely( sync.pos, sync.time, sync.time );
2756
2757 // set last turn
2758 lastTurnTimeRight_ = lastTurnTimeLeft_ = -100;
2759 }
2760
2761 // *******************************************************************************************
2762 // *
2763 // * InitAfterCreation
2764 // *
2765 // *******************************************************************************************
2766 //!
2767 //!
2768 // *******************************************************************************************
2769
InitAfterCreation(void)2770 void gCycleMovement::InitAfterCreation( void )
2771 {
2772 #ifdef DEBUG
2773 if (!finite(verletSpeed_))
2774 st_Breakpoint();
2775 #endif
2776 eNetGameObject::InitAfterCreation();
2777 #ifdef DEBUG
2778 if (!finite(verletSpeed_))
2779 st_Breakpoint();
2780 #endif
2781 MyInitAfterCreation();
2782 }
2783
2784 // version feature indicating that proper scaling of the base acceleration with the speed multiplier is used
2785 static nVersionFeature sg_correctAccelerationScaling( 8 );
2786
2787 // calculate essential rubber values
sg_RubberValues(ePlayerNetID const * player,REAL speed,REAL & max,REAL & effectiveness)2788 void sg_RubberValues( ePlayerNetID const * player, REAL speed, REAL & max, REAL & effectiveness )
2789 {
2790 // base values
2791 max=sg_rubberCycle;
2792 effectiveness=1;
2793
2794 // make rubber more effective for high ping players
2795 if ( player )
2796 {
2797 if ( max > 0 )
2798 // either by increasing the effectiveness...
2799 effectiveness *= ( max + player->ping * sg_rubberCyclePing )/max;
2800 else
2801 // or the reservoir.
2802 max += player->ping * sg_rubberCyclePing;
2803 }
2804
2805 {
2806 // modify rubber effectiveness by a speed dependant power law
2807 REAL speedFactor = speed/(sg_speedCycle*gCycleMovement::SpeedMultiplier());
2808
2809 effectiveness *= pow( speedFactor, sg_rubberCycleTimeBased );
2810 }
2811 }
2812
2813 // *******************************************************************************************
2814 // *
2815 // * AccelerationDiscontinuity
2816 // *
2817 // *******************************************************************************************
2818 //!
2819 //!
2820 // *******************************************************************************************
2821
AccelerationDiscontinuity()2822 void gCycleMovement::AccelerationDiscontinuity()
2823 {
2824 // make fake 0 timestep
2825 verletSpeed_ = Speed();
2826 lastTimestep_ = 0;
2827 }
2828
2829 // *******************************************************************************************
2830 // *
2831 // * CalculateAcceleration
2832 // *
2833 // *******************************************************************************************
2834 //!
2835 //!
2836 // *******************************************************************************************
2837
CalculateAcceleration()2838 void gCycleMovement::CalculateAcceleration()
2839 {
2840 tASSERT( good( verletSpeed_ ) );
2841
2842 // reset usage variables
2843 brakeUsage = 0.0f;
2844 rubberUsage = 0.0f;
2845
2846 // calculate acceleration
2847 acceleration=0;
2848
2849 // brake: it's only available since this version...
2850 static nVersionFeature brakeDepletion(2);
2851
2852 // and servers starting from this version disable it my modifying config items
2853 static nVersionFeature brakeDepletionHandledWithConfig(10);
2854
2855 // simply use the configured brake always on the server
2856 // and on the client if the server should have disabled it, but does not.
2857
2858 if ( sn_GetNetState() != nCLIENT || brakeDepletion.Supported() || brakeDepletionHandledWithConfig.Supported(0) )
2859 {
2860 if(braking)
2861 {
2862 if ( brakingReservoir > 0.0 )
2863 {
2864 brakeUsage = sg_cycleBrakeDeplete;
2865 acceleration-=sg_brakeCycle * SpeedMultiplier();
2866 }
2867 else
2868 brakingReservoir = 0.0f;
2869 }
2870 else
2871 {
2872 if ( brakingReservoir < 1.0 )
2873 {
2874 brakeUsage = -sg_cycleBrakeRefill;
2875 }
2876 else
2877 brakingReservoir = 1.0f;
2878 }
2879 }
2880 else
2881 {
2882 if(braking)
2883 {
2884 acceleration-=sg_brakeCycle * SpeedMultiplier();
2885 }
2886 }
2887
2888 sg_ArchiveReal( acceleration, 9 );
2889
2890 REAL baseSpeed = sg_speedCycle * SpeedMultiplier();
2891 if ( verletSpeed_ <= ( sg_correctAccelerationScaling.Supported() ? baseSpeed : sg_speedCycle ) )
2892 acceleration+=( baseSpeed - verletSpeed_) * sg_speedCycleDecayBelow;
2893 else
2894 acceleration+=( baseSpeed - verletSpeed_) * sg_speedCycleDecayAbove;
2895
2896 tASSERT( good( acceleration ) );
2897 sg_ArchiveReal( acceleration, 9 );
2898
2899 // sense near wall behind us, accelerate more
2900 REAL totalWallAcceleration = 0; // total acceleration by walls
2901 REAL tunnelWidth = 0; // with of the tunnel the cycle is in
2902 REAL sideWidth = sg_cycleWidthSide * 2; // minimal distance to wall
2903 bool slingshot = true; // flag indicating whether the cycle is between two walls
2904 bool oneOwnWall = false; // flag indicating whether one of the walls is your own
2905 for(int d=1;d>=-1;d-=2){
2906 // the direction to cast the acceleration rays in
2907 eCoord dirCast = dirDrive.Turn(-1,d);
2908 gSensor rear(this,pos,dirCast);
2909 rear.detect(sg_nearCycle);
2910
2911 enemyInfluence.AddSensor( rear, 0, this );
2912
2913 if (rear.ehit && rear.hit < sg_cycleWidth + .1f )
2914 blocks(rear, this, -d);
2915
2916 if ( 0 != rear.ehit )
2917 {
2918 sg_ArchiveReal( rear.hit, 9 );
2919
2920 // update the minimal wall distance
2921 if ( sideWidth > rear.hit )
2922 sideWidth = rear.hit;
2923
2924 // drop walls that are grinded
2925 if ( rear.hit < verletSpeed_ * .01 )
2926 ::sg_DropTempWall( dirCast, rear );
2927
2928 // see if the wall is parallel to the driving direction, only then should it add speed
2929 eCoord wallVec = rear.ehit->Vec();
2930 if ( fabs( eCoord::F( wallVec, dirDrive ) ) > .9 * dirDrive.NormSquared() )
2931 {
2932 // enemyInfluence.AddSensor( rear, 1 );
2933 REAL wallAcceleration=SpeedMultiplier() * sg_accelerationCycle * ((1/(rear.hit+sg_accelerationCycleOffs))
2934 -(1/(sg_nearCycle+sg_accelerationCycleOffs)));
2935
2936 tunnelWidth += rear.hit;
2937
2938 // apply modificators
2939 switch (rear.type)
2940 {
2941 case gSENSOR_SELF:
2942 wallAcceleration *= sg_accelerationCycleSelf;
2943 oneOwnWall = true;
2944 break;
2945 case gSENSOR_TEAMMATE:
2946 wallAcceleration *= sg_accelerationCycleTeam;
2947 break;
2948 case gSENSOR_ENEMY:
2949 wallAcceleration *= sg_accelerationCycleEnemy;
2950 break;
2951 case gSENSOR_RIM:
2952 wallAcceleration *= sg_accelerationCycleRim;
2953 break;
2954 case gSENSOR_NONE:
2955 wallAcceleration = 0;
2956 slingshot = false;
2957 break;
2958
2959 }
2960
2961 sg_ArchiveReal( wallAcceleration, 9 );
2962 totalWallAcceleration += wallAcceleration;
2963 }
2964 else
2965 {
2966 slingshot = false;
2967 }
2968 }
2969 else
2970 {
2971 slingshot = false;
2972 }
2973
2974 sg_ArchiveReal( totalWallAcceleration, 9 );
2975 }
2976
2977 // kill cycle if it is inside a too narrow channel
2978 if ( ( slingshot && tunnelWidth < sg_cycleWidth ) || sideWidth < sg_cycleWidthSide )
2979 {
2980 tunnelWidth = 0;
2981 REAL sideWidth = sg_cycleWidthSide * 2;
2982
2983 // check again with sensors to the front, both sensor pairs need
2984 // to see a narrow tunnel
2985 for(int d=1;d>=-1;d-=2)
2986 {
2987 // the direction to cast the acceleration rays in
2988 eCoord dirCast = dirDrive.Turn(1,d);
2989 gSensor front(this,pos,dirCast);
2990 front.detect(sg_nearCycle);
2991
2992 if ( front.ehit && front.ehit->Other() )
2993 {
2994 sg_ArchiveReal( front.hit, 9 );
2995
2996 // update the minimal wall distance
2997 if ( sideWidth > front.hit )
2998 sideWidth = front.hit;
2999
3000 tunnelWidth += front.hit;
3001 }
3002 else
3003 {
3004 tunnelWidth += sg_cycleWidth;
3005 }
3006 }
3007
3008 if ( tunnelWidth < sg_cycleWidth || sideWidth < sg_cycleWidthSide )
3009 {
3010 // determine the space available measured in the space allowed
3011 REAL available1 = 1;
3012 REAL available2 = 1;
3013 if ( sg_cycleWidth > 0 )
3014 available1 = tunnelWidth/sg_cycleWidth;
3015 if ( sg_cycleWidthSide > 0 )
3016 available2 = sideWidth/sg_cycleWidthSide;
3017 REAL available = available1 < available2 ? available1 : available2;
3018
3019 // get rubber values
3020 // REAL rubberGranted, rubberEffectiveness;
3021 // sg_RubberValues( player, verletSpeed_, rubberGranted, rubberEffectiveness );
3022
3023 // calculate rubber usage from squeezing
3024 rubberUsage = sg_cycleWidthRubberMax + ( sg_cycleWidthRubberMin - sg_cycleWidthRubberMax ) * available;
3025 }
3026 }
3027
3028 // apply slingshot/tunnel multiplier
3029 if ( slingshot )
3030 {
3031 if ( oneOwnWall )
3032 totalWallAcceleration *= sg_accelerationCycleSlingshot;
3033 else
3034 totalWallAcceleration *= sg_accelerationCycleTunnel;
3035 }
3036
3037 // apply wall acceleration
3038 acceleration += totalWallAcceleration;
3039
3040 tASSERT( good( acceleration ) );
3041 sg_ArchiveReal( acceleration, 9 );
3042
3043 tASSERT( good( verletSpeed_ ) );
3044 }
3045
3046 // *******************************************************************************************
3047 // *
3048 // * ApplyAcceleration
3049 // *
3050 // *******************************************************************************************
3051 //!
3052 //! @param dt length of timestep
3053 //!
3054 // *******************************************************************************************
3055
ApplyAcceleration(REAL dt)3056 void gCycleMovement::ApplyAcceleration( REAL dt )
3057 {
3058 sg_ArchiveReal( verletSpeed_, 9 );
3059 sg_ArchiveReal( dt, 9 );
3060 sg_ArchiveReal( acceleration, 9 );
3061
3062 // the speed needs to be simulated for this half frame and half of the last frame
3063 REAL verletTimestep = sg_verletIntegration.Supported() ? .5 * ( dt + lastTimestep_ ) : dt;
3064 lastTimestep_ = dt;
3065
3066 sg_ArchiveReal( verletTimestep, 9 );
3067
3068 // don't use euler timesteps for large cycle speed decays
3069 bool properDecay = false;
3070 REAL maxTimestep = verletTimestep > dt ? verletTimestep : dt;
3071 if ( sg_speedCycleDecayBelow * maxTimestep > .1 || sg_speedCycleDecayAbove * maxTimestep > .1 )
3072 {
3073 REAL speedDecay = 0;
3074 REAL baseSpeed = sg_speedCycle * SpeedMultiplier();
3075 if ( verletSpeed_ < ( sg_correctAccelerationScaling.Supported() ? baseSpeed : sg_speedCycle ) )
3076 speedDecay = sg_speedCycleDecayBelow;
3077 else
3078 speedDecay = sg_speedCycleDecayAbove;
3079
3080 if ( speedDecay * maxTimestep > .1 && dt > EPS )
3081 {
3082 // ok, really, a better simulation is needed
3083 properDecay = true;
3084
3085 // that's what CalculateAcceleration extrapolates
3086 REAL decayAcceleration = ( baseSpeed - verletSpeed_) * speedDecay;
3087 // throw it away
3088 acceleration -= decayAcceleration;
3089
3090 tASSERT( good( acceleration ) );
3091
3092 // adapt base speed as the limit speed with the current decay and acceleration
3093 baseSpeed += acceleration/speedDecay;
3094
3095 // do a proper decay
3096 verletSpeed_ = baseSpeed + ( verletSpeed_ - baseSpeed ) * exp( -speedDecay * verletTimestep );
3097
3098 // calculate new acceleration based purely on the decay, the external acceleration
3099 // is factored into baseSpeed now. Add extra decay factor so that
3100 // Speed() returns the most accurate current speed available.
3101 acceleration = ( baseSpeed - verletSpeed_) * ( 1 - exp( -speedDecay * dt * .5f ) ) / ( .5f * dt );
3102
3103 tASSERT( good( acceleration ) );
3104 }
3105 }
3106
3107 // if decay wasn't handled properly (because it didn't need to), use euler/verlet
3108 tASSERT( good( acceleration ) );
3109 if ( !properDecay )
3110 verletSpeed_+=acceleration*verletTimestep;
3111 tASSERT( good( verletSpeed_ ) );
3112
3113 // clamp speed
3114 REAL minSpeed = sg_speedCycle*SpeedMultiplier()*sg_speedCycleMin;
3115 REAL maxSpeed = ( 100 + sg_speedCycle*SpeedMultiplier() )* 100000;
3116 if ( sg_speedCycleMax > 0 )
3117 {
3118 maxSpeed = sg_speedCycle*SpeedMultiplier()*sg_speedCycleMax;
3119 }
3120
3121 sg_ArchiveReal( minSpeed, 9 );
3122 sg_ArchiveReal( maxSpeed, 9 );
3123 sg_ArchiveReal( acceleration, 9 );
3124
3125 if ( clamp( verletSpeed_, minSpeed, maxSpeed ) )
3126 acceleration = 0;
3127
3128 sg_ArchiveReal( acceleration, 9 );
3129
3130 sg_ArchiveReal( verletSpeed_, 9 );
3131 }
3132
3133 // *******************************************************************************************
3134 // *
3135 // * DoTurn
3136 // *
3137 // *******************************************************************************************
3138 //!
3139 //! @param dir +1 for right turns, -1 for left turns
3140 //! @return true of the turn could be executed right now, false if it was queued
3141 //!
3142 // *******************************************************************************************
3143
DoTurn(int dir)3144 bool gCycleMovement::DoTurn( int dir )
3145 {
3146 if ( turns == 0 )
3147 turns = 1;
3148
3149 if (dir > 1) dir = 1;
3150 if (dir < -1) dir = -1;
3151
3152 if ( CanMakeTurn( lastTime, dir ) )
3153 {
3154 // request regeneration of maximum space
3155 refreshSpaceAhead_ = true;
3156
3157 // notify that no rubber is currently used (may be a lie, but a timestep correcting
3158 // it will surely follow)
3159 rubberSpeedFactor = 1;
3160
3161 // store last postion
3162 lastTurnPos_ = pos;
3163
3164 turns++;
3165 /*
3166 if( sn_GetNetState() != nSERVER )
3167 {
3168 // simulate lost turns
3169 turns+=2;
3170 }
3171 */
3172
3173 AccelerationDiscontinuity();
3174 verletSpeed_ *= sg_cycleTurnSpeedFactor;
3175 rubberMalus += sg_rubberCycleMalusTurn;
3176
3177 gap_[0] = gap_[1] = 1E+30;
3178 keepLookingForGap_[0] = keepLookingForGap_[1] = true;
3179
3180 // turn winding numbers
3181 int wn = windingNumberWrapped_;
3182 Grid()->Turn(wn, dir);
3183 this->SetWindingNumberWrapped( wn );
3184
3185 eCoord nextDirDrive = Grid()->GetDirection(windingNumberWrapped_);
3186
3187 // send out a sensor a bit backwards and forwards into the turn direction to
3188 // copy all temporary walls into the grid
3189 {
3190 REAL range = .1 * Speed();
3191 eCoord dirCast = nextDirDrive;
3192 gSensor gridder1( this, Position(), dirCast );
3193 gridder1.detect( range );
3194 if ( gridder1.ehit )
3195 ::sg_DropTempWall( nextDirDrive, gridder1 );
3196
3197 gSensor gridder3( this, Position() - dirCast * (range*.5), dirCast );
3198 gridder3.detect( range );
3199 if ( gridder3.ehit )
3200 ::sg_DropTempWall( nextDirDrive, gridder3 );
3201
3202 // the ray backwards should detect walls that affected the acceleration;
3203 // they can also give a boost. Increase the range.
3204 if ( range < sg_nearCycle )
3205 range = sg_nearCycle;
3206
3207 gSensor gridder2( this, Position(), -dirCast );
3208 gridder2.detect( range );
3209 if ( gridder2.ehit )
3210 {
3211 ::sg_DropTempWall( nextDirDrive, gridder2 );
3212
3213 // apply the boost. Calculate wall distance
3214 REAL dist = gridder2.hit;
3215
3216 // calculate the factor acceleration would be multiplied with
3217 REAL accellerationFactorOffset = 1/(sg_nearCycle+sg_accelerationCycleOffs);
3218 REAL accelerationFactor = (1/(dist+sg_accelerationCycleOffs)) - accellerationFactorOffset;
3219 // this would be the maximal acceleration factor
3220 REAL accelerationFactorMax = (1/sg_accelerationCycleOffs) - accellerationFactorOffset;
3221
3222 if( accelerationFactorMax > 0 && dist < sg_nearCycle )
3223 {
3224 // select boost settings according to wall type
3225 // apply modificators
3226 REAL boost = 0, boostFactor = 1;
3227 switch (gridder2.type)
3228 {
3229 case gSENSOR_SELF:
3230 boost = sg_boostCycleSelf;
3231 boostFactor = sg_boostFactorCycleSelf;
3232 break;
3233 case gSENSOR_TEAMMATE:
3234 boost = sg_boostCycleTeam;
3235 boostFactor = sg_boostFactorCycleTeam;
3236 break;
3237 case gSENSOR_ENEMY:
3238 boost = sg_boostCycleEnemy;
3239 boostFactor = sg_boostFactorCycleEnemy;
3240 break;
3241 case gSENSOR_RIM:
3242 boost = sg_boostCycleRim;
3243 boostFactor = sg_boostFactorCycleRim;
3244 break;
3245 case gSENSOR_NONE:
3246 break;
3247 }
3248
3249 // apply acceleration factor to boost
3250 boostFactor = 1 + ( boostFactor - 1 ) * accelerationFactor / accelerationFactorMax;
3251 boost *= SpeedMultiplier() * accelerationFactor / accelerationFactorMax;
3252
3253 // apply boost to speed
3254 verletSpeed_ = verletSpeed_ * boostFactor + boost;
3255 tASSERT( good( verletSpeed_ ) );
3256 }
3257 }
3258
3259 // if edges have been inserted into the grid, find a new current face.
3260 FindCurrentFace();
3261 }
3262
3263 // update driving directions
3264 lastDirDrive = dirDrive;
3265
3266 if(dir == 1)
3267 lastTurnTimeRight_ = lastTime;
3268 else
3269 lastTurnTimeLeft_ = lastTime;
3270
3271 dirDrive = nextDirDrive;
3272
3273 #ifdef DEBUGOUTPUT
3274 if ( sg_cycleDebugPrintLevel > 0 )
3275 con << Player()->GetName() << " turned " << pos << "," << dirDrive << " " << tSysTimeFloat() << "\n";
3276 #endif
3277
3278 return true;
3279 }
3280 else {
3281 int maxPendingTurns=sg_cycleTurnMemory;
3282 int size = pendingTurns.size();
3283 // std::cerr << "size of " << &pendingTurns << ": " << size << std::endl;
3284 if (size <= maxPendingTurns)
3285 pendingTurns.push_back(dir);
3286 else {
3287 if(pendingTurns.empty()) return false; //just to be sure
3288 if(pendingTurns.back() != dir) {
3289 pendingTurns.pop_back(); //opposite turns cancel so the cycle still moves into the expected direction
3290 }
3291 else {
3292 pendingTurns.push_back(dir); //add it anyways...
3293 }
3294 }
3295 }
3296
3297 return false;
3298 }
3299
3300 // *******************************************************************************************
3301 // *
3302 // * RightBeforeDeath
3303 // *
3304 // *******************************************************************************************
3305 //!
3306 //! @param numTries number of times this function will be called approximately before the cycle will be killed
3307 //!
3308 // *******************************************************************************************
3309
RightBeforeDeath(int numTries)3310 void gCycleMovement::RightBeforeDeath( int numTries )
3311 {
3312 }
3313
3314 // *******************************************************************************************
3315 // *
3316 // * Die
3317 // *
3318 // *******************************************************************************************
3319 //!
3320 //! @param time the time of death
3321 //!
3322 // *******************************************************************************************
3323
Die(REAL time)3324 void gCycleMovement::Die( REAL time )
3325 {
3326 // only do something if you are alive
3327 if ( alive_ == 1 )
3328 {
3329 alive_ = -1;
3330 deathTime = time;
3331 }
3332
3333 // or complete death if you died only recently
3334 if ( alive_ == -1 )
3335 {
3336 alive_ = 0;
3337 }
3338 }
3339
3340 class gRecursionGuard
3341 {
3342 public:
gRecursionGuard(bool & guard)3343 gRecursionGuard( bool & guard )
3344 : guard_( guard )
3345 {
3346 guard_ = false;
3347 }
~gRecursionGuard()3348 ~gRecursionGuard()
3349 {
3350 guard_ = true;
3351 }
3352 private:
3353 bool & guard_;
3354 };
3355
3356 // *******************************************************************************************
3357 // *
3358 // * TimestepCore
3359 // *
3360 // *******************************************************************************************
3361 //!
3362 //! @param currentTime time to simulate up to
3363 //! @return true if the cycle is to be killed
3364 //!
3365 // *******************************************************************************************
3366
TimestepCore(REAL currentTime,bool calculateAcceleration)3367 bool gCycleMovement::TimestepCore( REAL currentTime, bool calculateAcceleration )
3368 {
3369 // eCoord oldpos=pos;
3370 REAL lastSpeed=verletSpeed_;
3371
3372 REAL ts=(currentTime-lastTime);
3373
3374 // calculate acceleration
3375 if ( calculateAcceleration )
3376 this->CalculateAcceleration();
3377
3378 // ApplyAcceleration modifies the acceleration, so we need to back it up
3379 REAL lastAcceleration=acceleration;
3380
3381 // calculate when the braking reservoir will run dry and simulate to that point
3382 {
3383 static bool recurse = true;
3384 if (recurse && brakingReservoir > 0 && brakeUsage > 0 && brakingReservoir - ts * brakeUsage < 0 )
3385 {
3386 gRecursionGuard guard( recurse );
3387
3388 // calculate the time the brake will run out
3389 REAL brakeTime = lastTime + brakingReservoir/brakeUsage;
3390 if ( TimestepCore( brakeTime, false ) )
3391 return true;
3392 AccelerationDiscontinuity();
3393 brakingReservoir = -EPS;
3394 return TimestepCore( currentTime );
3395 }
3396 }
3397
3398 // apply acceleration
3399 if ( sg_verletIntegration.Supported() )
3400 this->ApplyAcceleration( ts );
3401
3402 //eDebugLine::SetTimeout( 2 );
3403 //eDebugLine::SetColor(1,1,0);
3404 //eDebugLine::Draw(pos, 4, pos, 4 + 20 * ts);
3405
3406 sg_ArchiveCoord( pos, 9 );
3407 sg_ArchiveReal( ts, 9 );
3408 sg_ArchiveReal( verletSpeed_, 9 );
3409
3410 #ifdef DEBUG
3411 if ( ts > 2.0f )
3412 {
3413 int x;
3414 x = 0;
3415 }
3416
3417 if ( verletSpeed_ > 30.0f )
3418 {
3419 int x;
3420 x = 0;
3421 }
3422
3423 if ( acceleration > 100.0f )
3424 {
3425 int x;
3426 x = 0;
3427 }
3428 #endif
3429
3430 clamp(ts, -10, 10);
3431
3432 REAL step=verletSpeed_*ts;
3433 tASSERT(finite(step));
3434
3435 int numTries = 0;
3436 bool emergency = false;
3437
3438 rubberSpeedFactor = 1;
3439
3440 // be a little nice and don't drive into the wall
3441 REAL rubber_granted, rubberEffectiveness;
3442
3443 // get rubber values
3444 sg_RubberValues( player, verletSpeed_, rubber_granted, rubberEffectiveness );
3445
3446 // rubber effectiveness right now
3447 rubberEffectiveness /= (1 + rubberMalus );
3448
3449 // reduce it further if cycle turned recently
3450 {
3451 REAL delayTime = (lastTurnTimeRight_ > lastTurnTimeLeft_ ? lastTurnTimeRight_ : lastTurnTimeLeft_) + GetTurnDelay() * sg_rubberCycleDelay;
3452 if ( lastTime < delayTime )
3453 {
3454 rubberEffectiveness *= sg_rubberCycleDelayBonus;
3455
3456 // if the target time is after the rubber delay ends...
3457 if( currentTime > delayTime )
3458 {
3459 static bool recurse = true;
3460 if (recurse)
3461 {
3462 gRecursionGuard guard( recurse );
3463
3464 verletSpeed_=lastSpeed;
3465 acceleration=lastAcceleration;
3466 // do two small timesteps
3467 return TimestepCore( delayTime, false ) || TimestepCore( currentTime );
3468 }
3469 }
3470 }
3471 }
3472
3473 sg_ArchiveReal( rubberEffectiveness, 9 );
3474
3475 tASSERT( rubber >= 0 );
3476
3477 // TODO: solve smooth position correction trouble with rubber
3478 if ( player && ( rubber_granted > rubber || sn_GetNetState() == nCLIENT || !Vulnerable() ) && sg_rubberCycleSpeed > 0 && step > -EPS && ( sn_GetNetState() == nCLIENT || rubberEffectiveness > 0 ) )
3479 {
3480 // ignore zero effectiveness, this happens only on the client
3481 if ( rubberEffectiveness <= 0 )
3482 rubberEffectiveness = 1E+20;
3483
3484 // formerly: rubberFactor = .5
3485 REAL beta = ts * sg_rubberCycleSpeed;
3486 REAL neededSpace = 0;
3487 REAL rubberFactor;
3488 if ( beta > .001 )
3489 {
3490 rubberFactor = 1 - exp( -beta );
3491 neededSpace = step/rubberFactor;
3492 }
3493 else
3494 {
3495 rubberFactor = beta; // better accuracy than the full formula
3496
3497 // a lot of factors can be cut out of this one (avoiding a division by zero for ts=0)
3498 neededSpace = verletSpeed_/sg_rubberCycleSpeed;
3499 }
3500
3501 // rubberFactor must not be too close to 1, otherwise we get precision trouble
3502 if ( rubberFactor > .999 )
3503 rubberFactor = .999;
3504
3505 // revert to old rubber logic if old clients are connected
3506 if ( sg_rubberCycleLegacy && !sg_nonRippable.Supported() && rubberFactor < .5f )
3507 rubberFactor = .5f;
3508
3509 // space we need to look ahead
3510 if ( neededSpace < step*3 || ts < -EPS )
3511 neededSpace = step*3;
3512
3513 // determine how long we can drive on
3514 // REAL space = GetMaxSpaceAhead( this, neededSpace, ts * step * rubberFactor / rubberEffectiveness, &hitInfo );
3515 REAL space = GetMaxSpaceAhead( neededSpace );
3516
3517 #ifdef DEBUG_RUBBER
3518 if ( Player() && space < 1E+15)
3519 {
3520 std::ofstream f( Player()->GetUserName() + "_rubber", std::ios::app );
3521 f << lastTime << " " << space << "\n";
3522 }
3523 #endif
3524
3525 // if the available space in front is less than the space needed to slow down via
3526 // the rubber brake, activate rubber and slow down
3527 if ( space < neededSpace )
3528 {
3529 // the minimal space rubber gets active at
3530 REAL rubberStartSpace = verletSpeed_/sg_rubberCycleSpeed;
3531 static bool recurse = true;
3532 if ( space > rubberStartSpace && recurse )
3533 {
3534 // rubber will not be active immediately, simulate to the time it will
3535 gRecursionGuard guard( recurse );
3536
3537 // calculate the time rubber will get active at
3538 REAL ratio = ( space - rubberStartSpace )/step;
3539 if ( ratio > EPS && ratio < 1 - EPS )
3540 {
3541 REAL rubberGetsActiveTime = lastTime + ( currentTime - lastTime ) * ratio;
3542
3543 verletSpeed_=lastSpeed;
3544 acceleration=lastAcceleration;
3545 return TimestepCore( rubberGetsActiveTime, false ) || TimestepCore( currentTime );
3546 }
3547 }
3548 #ifdef DEDICATED
3549 else
3550 {
3551 // see if the wall we're about to hit comes from its cycle's future. If so,
3552 // it is a prediction wall and we shouldn't actually use rubber before we
3553 // have to.
3554 if ( maxSpaceHit_ && maxSpaceHit_->playerWall )
3555 {
3556 gPlayerWall * wall = maxSpaceHit_->playerWall;
3557
3558 // get the position of the hit
3559 REAL alpha = maxSpaceHit_->wallAlpha;
3560
3561 // get the distance of the wall
3562 REAL wallDist = wall->Pos( alpha );
3563 // get the distance the cycle is simulated up to
3564 REAL cycleDist = wall->CycleMovement()->distance;
3565 // comparing these two gives an accurate criterion whether the wall is extrapolated
3566
3567 REAL minLag = se_GameTime() - lastTime - LagThreshold();
3568 if ( cycleDist < wallDist && ( minLag < Lag() || minLag < wall->CycleMovement()->Lag() ) )
3569 {
3570 // it is an extrapolation wall and we are allowed to delay simulation a bit.
3571 // so let's abort here.
3572 verletSpeed_=lastSpeed;
3573 acceleration=lastAcceleration;
3574
3575 return false;
3576 }
3577 }
3578 }
3579 #endif
3580
3581 // see if the obstacle will go away during this timestep.
3582 // if it does, simulate in two steps to make the simulation more accurate.
3583 {
3584 // get the wall
3585 if ( maxSpaceHit_ && maxSpaceHit_->playerWall )
3586 {
3587 gPlayerWall * wall = maxSpaceHit_->playerWall;
3588
3589 // get the position of the hit
3590 REAL alpha = maxSpaceHit_->wallAlpha;
3591
3592 // use binary search to find the time the wall goes away. Not
3593 // the fastest way, but it doesn't depend on wall internals, and
3594 // it shouldn't be called often anyway.
3595 REAL tolerance = 0.001;
3596 if ( !wall->IsDangerous( alpha, currentTime ) && currentTime > lastTime + tolerance )
3597 {
3598 // take movement speed into account, we won't hit the wall for
3599 // another distanceOffset seconds
3600 REAL distanceOffset = 0;
3601 {
3602 REAL speed = Speed();
3603 if ( speed > 0 )
3604 distanceOffset = space/speed;
3605 }
3606
3607 REAL minTime = lastTime + distanceOffset;
3608 REAL maxTime = currentTime + distanceOffset;
3609 while ( minTime + tolerance < maxTime )
3610 {
3611 REAL midTime = .5 * ( minTime + maxTime );
3612 if ( wall->IsDangerous( alpha, midTime ) )
3613 minTime = midTime;
3614 else
3615 maxTime = midTime;
3616 }
3617
3618 maxTime -= distanceOffset;
3619 // minTime -= distanceOffset;
3620
3621 // split simulation into two parts, one up to the point the wall turns harmless
3622 {
3623 static bool recurse = true;
3624 if (recurse)
3625 {
3626 gRecursionGuard guard( recurse );
3627
3628 verletSpeed_=lastSpeed;
3629 acceleration=lastAcceleration;
3630 return TimestepCore( maxTime, false ) || TimestepCore( currentTime );
3631 }
3632 }
3633 }
3634 }
3635 }
3636
3637 /*
3638 // debug output for sensitive space/time diagrams
3639 static REAL lastTimePrinted = 0;
3640 if ( currentTime > lastTimePrinted && Player() )
3641 {
3642 lastTimePrinted = currentTime;
3643 std::ofstream f( Player()->GetUserName() + "_space", std::ios::app );
3644 f << lastTime << " " << log(space) << "\n";
3645 }
3646 */
3647
3648 // notify AIs of it
3649 emergency = true;
3650
3651 // calculate the step the rubber code should do based on the decay factor
3652 // calculated earler
3653 REAL rubberStep = space * rubberFactor;
3654 if ( rubberStep > step )
3655 rubberStep = step;
3656
3657 // clamp the step
3658 if (step<0)
3659 step=0;
3660
3661 // calculate the amount of rubber needed for the desired brake effect
3662 REAL rubberneeded = step - rubberStep;
3663 if (rubberneeded < 0)
3664 rubberneeded = 0;
3665
3666 // clamp rubberneeded to the amout of rubber available
3667 REAL rubberAvailable = ( rubber_granted - rubber ) * rubberEffectiveness;
3668 if ( sn_GetNetState() != nCLIENT && rubberneeded > rubberAvailable && Vulnerable() )
3669 {
3670 // rubber will run out this frame.
3671 // split simulation into two parts, one up to the point rubber runs out
3672 {
3673 REAL ratio = rubberAvailable/rubberneeded;
3674
3675 if ( ratio > .01 && ratio < .99 && currentTime - lastTime > .001 )
3676 {
3677 REAL runOutTime = lastTime + ( currentTime - lastTime ) * ratio;
3678 static bool recurse = true;
3679 if (recurse)
3680 {
3681 gRecursionGuard guard( recurse );
3682 // need many attempts
3683 verletSpeed_=lastSpeed;
3684 acceleration=lastAcceleration;
3685 return TimestepCore( runOutTime, false ) || TimestepCore( currentTime );
3686 }
3687 }
3688 }
3689
3690 rubberneeded = rubberAvailable;
3691 }
3692
3693 // update rubber usage
3694 rubber += rubberneeded / rubberEffectiveness;
3695
3696 numTries = int((sg_rubberCycleTime * ( rubber_granted - rubber ) - 1 )/(sg_rubberCycleTime * step*1.5 + 1));
3697 int numTriesSpace = int(space*10/verletSpeed_);
3698 if ( numTriesSpace < numTries )
3699 numTriesSpace = 0;
3700
3701 if ( step > 0 )
3702 rubberSpeedFactor = 1 - rubberneeded/step;
3703 else
3704 // better algorithm for zero steps
3705 rubberSpeedFactor = space / neededSpace;
3706
3707 // clamp
3708 if ( rubberSpeedFactor < 0 )
3709 rubberSpeedFactor = 0;
3710
3711 // correct the step to take, don't go backwards.
3712 step -= rubberneeded;
3713 if (step<0)
3714 step=0;
3715
3716 //{
3717 // rubber+=step;
3718 // step=0;
3719 //}
3720 }
3721 }
3722
3723 tASSERT( rubber >= 0 );
3724
3725 sg_ArchiveReal( step, 9 );
3726
3727 // move forward
3728 eCoord nextpos;
3729 if ( verletSpeed_ >0 )
3730 nextpos=pos+dirDrive*step;
3731 else
3732 nextpos=pos;
3733
3734 eCoord lastPos = pos;
3735 tJUST_CONTROLLED_PTR< eFace > lastFace = currentFace;
3736 try
3737 {
3738 #ifdef DEBUG
3739 static int run = 0;
3740 run++;
3741 if ( run == -1 )
3742 {
3743 st_Breakpoint();
3744 }
3745 #endif
3746 Move(nextpos,lastTime,currentTime);
3747 #ifdef DEBUG
3748 {
3749 if ( step > 0 && ( nextpos - pos ).NormSquared() > 1 )
3750 {
3751 con << "Wrong move! run = " << run << ", nextpos = " << nextpos << ", pos = " << pos << "\n";
3752 }
3753 }
3754 #endif
3755
3756 tASSERT(finite(distance));
3757 tASSERT(finite(step));
3758 distance += step;
3759 lastTimeAlive_ = currentTime;
3760 }
3761 catch ( gCycleStop const & )
3762 {
3763 // undo simulation done so far and stop
3764 pos = lastPos;
3765 verletSpeed_ = lastSpeed;
3766 acceleration = lastAcceleration;
3767 currentFace = lastFace;
3768 numTries = 0;
3769
3770 // don't simulate further
3771 return false;
3772 }
3773 catch ( gCycleDeath const & )
3774 {
3775 rubberSpeedFactor = 0;
3776
3777 // the cycle should die in this movement. Prevent it if there is rubber left.
3778 // if RUBBER_MINDISTANCE is negative and the player is not an AI, the cycle dies anyway.
3779 if ( rubberEffectiveness <= 0 || step >= (rubber_granted-rubber)*rubberEffectiveness || ( sg_rubberCycleMinDistance < 0 && Player() && Player()->IsHuman() ) )
3780 {
3781 // last survival chance: packet loss protection. Determine whether it should be in effect..
3782 bool toleratePacketLoss = false;
3783 if (!currentDestination)
3784 {
3785 // calculate time tolerance to capture packet loss...
3786 REAL tolerance = Lag() * sg_packetLossTolerance;
3787
3788 // add lag credit on top of that
3789 if ( Owner() > 0 )
3790 tolerance += eLag::Credit( Owner() );
3791
3792 // add lag fluctuation to the mix
3793 if ( sn_GetNetState() == nSERVER && player && player->Owner() != 0 )
3794 {
3795 REAL varianceTolerance = 2 * sqrtf( sn_Connections[ player->Owner() ].ping.GetSnailAverager().GetDataVariance() );
3796 // clamp it, high fluctuations are the player's own problem
3797 if ( varianceTolerance > tolerance )
3798 varianceTolerance = tolerance;
3799 tolerance += varianceTolerance;
3800 }
3801
3802 // if time has not progressed beyond tolerance, protection may be in effect
3803 toleratePacketLoss = ( se_GameTime() - Lag() - lastTimeAlive_ < tolerance );
3804 }
3805
3806 // ... and apply it.
3807 if ( toleratePacketLoss )
3808 {
3809 pos = lastPos;
3810 verletSpeed_ = lastSpeed;
3811 acceleration = lastAcceleration;
3812 currentFace = lastFace;
3813 numTries = 0;
3814 emergency = true;
3815
3816 // don't simulate further
3817 return false;
3818 }
3819 else
3820 {
3821 // no, no straw left. Rethrow and get killed.
3822 rubber = rubber_granted;
3823
3824 // update distance to include the really covered space
3825 tASSERT(finite(distance));
3826 distance += eCoord::F( dirDrive, pos - lastPos )/dirDrive.NormSquared();
3827 tASSERT(finite(distance));
3828
3829 throw;
3830 }
3831 }
3832 else
3833 {
3834 pos = lastPos;
3835 currentFace = lastFace;
3836 rubber += step/rubberEffectiveness;
3837 if ( rubber < 0 )
3838 rubber = 0;
3839
3840 numTries = 0;
3841 emergency = true;
3842 }
3843 }
3844
3845 tASSERT( rubber >= 0 );
3846
3847 // use up rubber from tunneling (calculated by CalculateAcceleration
3848 if ( rubberEffectiveness > 0 )
3849 {
3850 rubber += rubberUsage * ts * verletSpeed_ / rubberEffectiveness; }
3851 else if ( rubberUsage > 0 )
3852 {
3853 rubber = rubber_granted + 10;
3854 }
3855 rubberUsage = 0;
3856
3857 // decide over kill
3858 bool rubberUsedUp = false;
3859 if ( rubber > rubber_granted || ( sg_cycleWidthRubberMax == 0 && sg_cycleWidthRubberMin == 0 ) )
3860 {
3861 if ( sn_GetNetState() != nCLIENT )
3862 {
3863 throw gCycleDeath( pos );
3864 }
3865 else
3866 {
3867 rubber = rubber_granted;
3868 rubberUsedUp = true;
3869 }
3870 }
3871
3872 // use up brake
3873 brakingReservoir -= brakeUsage * ts;
3874 clamp( brakingReservoir, 0, 1 );
3875
3876 // let rubber decay
3877 if ( sg_rubberCycleTime > 0 )
3878 rubber /= (1+ts/sg_rubberCycleTime);
3879 else
3880 rubber = 0;
3881
3882 // let rubber decay
3883 if ( sg_rubberCycleMalusTime > 0 )
3884 rubberMalus /= (1+ts/sg_rubberCycleMalusTime);
3885 else
3886 rubberMalus = 0;
3887
3888
3889 // clamp rubber ( mostly for client side HUD display )
3890 if ( rubber >= rubber_granted )
3891 {
3892 rubber = rubber_granted;
3893 rubberUsedUp = true;
3894 }
3895
3896 // record time rubber went out
3897 if( rubberUsedUp )
3898 {
3899 if ( rubberDepleteTime_ <= 0 )
3900 {
3901 rubberDepleteTime_ = lastTime;
3902 }
3903 }
3904 else
3905 {
3906 rubberDepleteTime_ = 0;
3907 }
3908
3909 lastTime=currentTime;
3910
3911 // give the AI a chance to evade just in time
3912 if (emergency)
3913 {
3914 RightBeforeDeath(numTries);
3915 }
3916
3917 #ifdef DEBUGOUTPUT
3918 if ( sg_cycleDebugPrintLevel > 1 )
3919 con << Player()->GetName() << " moved " << pos << "," << dirDrive << " " << tSysTimeFloat() << "\n";
3920 #endif
3921
3922 /*
3923 // debug output for sensitive rubber/time diagrams
3924 static REAL lastTimePrinted = 0;
3925 if ( currentTime > lastTimePrinted && Player() )
3926 {
3927 lastTimePrinted = currentTime;
3928 std::ofstream f( Player()->GetUserName() + "_rubber", std::ios::app );
3929 f << currentTime << " " << rubber << "\n";
3930 }
3931 */
3932
3933 // apply acceleration
3934 if ( !sg_verletIntegration.Supported() )
3935 this->ApplyAcceleration( ts );
3936
3937 tASSERT(finite(distance));
3938
3939 tASSERT( rubber >= 0 );
3940
3941 // call base timestep
3942 return eNetGameObject::Timestep(currentTime);
3943 }
3944
3945 // *******************************************************************************************
3946 // *
3947 // * MyInitAfterCreation
3948 // *
3949 // *******************************************************************************************
3950 //!
3951 //!
3952 // *******************************************************************************************
3953
MyInitAfterCreation(void)3954 void gCycleMovement::MyInitAfterCreation( void )
3955 {
3956 #ifdef DEBUG
3957 // con << "creating cycle.\n";
3958 #endif
3959 brakingReservoir = 1.0f;
3960 rubberDepleteTime_ = 0.0f;
3961
3962 braking = false;
3963
3964 acceleration = 0;
3965
3966 refreshSpaceAhead_ = true;
3967 maxSpaceMaxCast_ = 0.0;
3968 maxSpaceHit_ = NULL;
3969
3970 dir=dirDrive;
3971 lastDirDrive=dirDrive;
3972 lastTurnPos_=pos;
3973
3974 distance=0;
3975 // wallContDistance = 5;
3976 rubber=0.0f;
3977 rubberMalus=0.0f;
3978 rubberSpeedFactor=1.0f;
3979
3980 gap_[0] = gap_[1] = 1E+30;
3981 keepLookingForGap_[0] = keepLookingForGap_[1] = true;
3982
3983 alive_ = 1;
3984
3985 z=.75;
3986
3987 turns=1;
3988 // pendingTurns.resize(0); //clear it
3989 lastTurnTimeRight_ = lastTurnTimeLeft_=lastTime-10;
3990
3991 lastTimeAlive_ = lastTime;
3992
3993 if (!finite(verletSpeed_)){
3994 st_Breakpoint();
3995 verletSpeed_ = 1;
3996 }
3997
3998 if (verletSpeed_ < .1)
3999 verletSpeed_=.1;
4000
4001 #ifdef DEBUGOUTPUT
4002 if ( sg_cycleDebugPrintLevel > 0 )
4003 con << Player()->GetName() << " created " << pos << "," << dirDrive << " " << tSysTimeFloat() << "\n";
4004 #endif
4005 }
4006
4007 // *******************************************************************************************
4008 // *
4009 // * Init_gCycleCore
4010 // *
4011 // *******************************************************************************************
4012 //!
4013 //!
4014 // *******************************************************************************************
4015
4016 //void gCycleMovement::Init_gCycleCore( void )
4017 //{
4018 // assert(0); // implement me
4019 //}
4020
4021 // *******************************************************************************************
4022 // *
4023 // * Finit_gCycleCore
4024 // *
4025 // *******************************************************************************************
4026 //!
4027 //!
4028 // *******************************************************************************************
4029
4030 //void gCycleMovement::Finit_gCycleCore( void )
4031 //{
4032 // assert(0); // implement me
4033 //}
4034
4035 // *******************************************************************************************
4036 // *
4037 // * gCycleMovement
4038 // *
4039 // *******************************************************************************************
4040 //!
4041 //!
4042 // *******************************************************************************************
4043
4044 //gCycleMovement::gCycleMovement( void )
4045 //{
4046 // assert(0); // implement me
4047 //}
4048
4049 // *******************************************************************************************
4050 // *
4051 // * gCycleMovement
4052 // *
4053 // *******************************************************************************************
4054 //!
4055 //! @param other
4056 //!
4057 // *******************************************************************************************
4058
4059 //gCycleMovement::gCycleMovement( gCycleMovement const & other )
4060 //{
4061 // assert(0); // implement me
4062 //}
4063
4064 // *******************************************************************************************
4065 // *
4066 // * operator =
4067 // *
4068 // *******************************************************************************************
4069 //!
4070 //! @param other
4071 //! @return
4072 //!
4073 // *******************************************************************************************
4074
4075 //gCycleMovement & gCycleMovement::operator =( gCycleMovement const & other )
4076 //{
4077 // assert(0); // implement me
4078 // return gCycleMovement();
4079 //}
4080
4081 // *******************************************************************************************
4082 // *
4083 // * CopyFrom
4084 // *
4085 // *******************************************************************************************
4086 //!
4087 //! @param other
4088 //!
4089 // *******************************************************************************************
4090
4091 //void gCycleMovement::CopyFrom( const gCycleMovement & other )
4092 //{
4093 // assert(0); // implement me
4094 //}
4095
4096 // *******************************************************************************************
4097 // *
4098 // * DoGetDistanceSinceLastTurn
4099 // *
4100 // *******************************************************************************************
4101 //!
4102 //! @return the distance driven since the last turn
4103 //!
4104 // *******************************************************************************************
4105
DoGetDistanceSinceLastTurn(void) const4106 REAL gCycleMovement::DoGetDistanceSinceLastTurn( void ) const
4107 {
4108 return eCoord::F( dirDrive, pos - lastTurnPos_ )/dirDrive.NormSquared();
4109 }
4110
4111 // *******************************************************************************************
4112 // *
4113 // * RubberMalusActive
4114 // *
4115 // *******************************************************************************************
4116 //!
4117 //! @return
4118 //!
4119 // *******************************************************************************************
4120
RubberMalusActive(void)4121 bool gCycleMovement::RubberMalusActive( void )
4122 {
4123 return sg_rubberCycleMalusTurn > 0;
4124 }
4125
4126 // *******************************************************************************************
4127 // *
4128 // * MoveSafely
4129 // *
4130 // *******************************************************************************************
4131 //!
4132 //! @param dest the destination position
4133 //! @param startTime the start time of the movement
4134 //! @param endTime the end time of the movement
4135 //!
4136 // *******************************************************************************************
4137
MoveSafely(const eCoord & dest,REAL startTime,REAL endTime)4138 void gCycleMovement::MoveSafely( const eCoord & dest, REAL startTime, REAL endTime )
4139 {
4140 static bool recursing = false;
4141 if ( !recursing )
4142 {
4143 recursing = true;
4144 try
4145 {
4146 // try a regular move
4147 Move( dest, startTime, endTime );
4148 }
4149 catch( eDeath & death )
4150 {
4151 // and play dead if that doesn't work right
4152 short lastAlive = alive_;
4153 alive_ = 0;
4154 Move( dest, startTime, endTime );
4155 alive_ = lastAlive;
4156 }
4157 }
4158 else
4159 {
4160 // play dead if another safe move is already in process. Sometimes,
4161 // crossing a wall of a live cycle causes that cycle to be moved with
4162 // this function, and "killing" it temporarily avoids an endless
4163 // recursion in that case.
4164 short lastAlive = alive_;
4165 alive_ = 0;
4166 Move( dest, startTime, endTime );
4167 alive_ = lastAlive;
4168 }
4169 }
4170
GetTurnSpeedFactor(void)4171 REAL GetTurnSpeedFactor(void) {
4172 return sg_cycleTurnSpeedFactor;
4173 }
4174
4175 // *******************************************************************************
4176 // *
4177 // * NextInterestingTime
4178 // *
4179 // *******************************************************************************
4180 //!
4181 //! @return
4182 //!
4183 // *******************************************************************************
4184
NextInterestingTime(void) const4185 REAL gCycleMovement::NextInterestingTime( void ) const
4186 {
4187 // default to the last time
4188 REAL ret = LastTime();
4189
4190 // look for a later destination
4191 gDestination * run = currentDestination;
4192 while ( run )
4193 {
4194 REAL time = run->GetGameTime();
4195 if ( time > ret )
4196 ret = time;
4197 run = run->next;
4198 }
4199
4200 return ret;
4201 }
4202
4203
4204