1 /*
2 
3 *************************************************************************
4 
5 ArmageTron -- Just another Tron Lightcycle Game in 3D.
6 Copyright (C) 2000  Manuel Moos (manuel@moosnet.de)
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 "gCycle.h"
29 #include "nConfig.h"
30 #include "rModel.h"
31 //#include "eTess.h"
32 #include "eGrid.h"
33 #include "rTexture.h"
34 #include "eTimer.h"
35 #include "tInitExit.h"
36 #include "tRecorder.h"
37 #include "rScreen.h"
38 #include "rFont.h"
39 #include "gSensor.h"
40 #include "ePlayer.h"
41 #include "eSound.h"
42 #include "eGrid.h"
43 #include "eFloor.h"
44 #include "gSparks.h"
45 #include "gExplosion.h"
46 #include "gWall.h"
47 #include "nKrawall.h"
48 #include "gAIBase.h"
49 #include "eDebugLine.h"
50 #include "eLagCompensation.h"
51 #include "gArena.h"
52 
53 #include "tMath.h"
54 #include <stdlib.h>
55 #include <fstream>
56 #include <memory>
57 
58 #ifndef DEDICATED
59 #define DONTDOIT
60 #include "rRender.h"
61 #endif
62 
63 // TODO: get rid of this
64 #include "tDirectories.h"
65 
66 // also used in gWall.cpp
67 bool sg_gnuplotDebug = false;
68 
69 #define GNUPLOT_DEBUG
70 // #define DELAYEDTURN_DEBUG
71 
72 #ifdef GNUPLOT_DEBUG
73 static tSettingItem<bool> sg_("DEBUG_GNUPLOT",sg_gnuplotDebug);
74 #endif
75 
76 static REAL sg_minDropInterval=0.05;
77 static tSettingItem< REAL > sg_minDropIntervalConf( "CYCLE_MIN_WALLDROP_INTERVAL", sg_minDropInterval );
78 
79 #ifdef DEDICATED
80 static bool sg_predictWalls=true;
81 static tSettingItem< bool > sg_predictWallsConf( "PREDICT_WALLS", sg_predictWalls );
82 #endif
83 
84 //  *****************************************************************
85 
86 static nNOInitialisator<gCycle> cycle_init(320,"cycle");
87 
88 //  *****************************************************************
89 
90 // static nVersionFeature sg_DoubleSpeed( 1 );
91 
92 //tCONTROLLED_PTR(ePlayerNetID)   lastEnemyInfluence;  	// the last enemy wall we encountered
93 //REAL							lastTime;				// the time it was drawn at
94 bool headlights=1;
95 extern bool cycleprograminited;
96 
97 static float sg_cycleSyncSmoothTime = .1f;
98 static tSettingItem<float> conf_smoothTime ("CYCLE_SMOOTH_TIME", sg_cycleSyncSmoothTime);
99 
100 static float sg_cycleSyncSmoothMinSpeed = .2f;
101 static tSettingItem<float> conf_smoothMinSpeed ("CYCLE_SMOOTH_MIN_SPEED", sg_cycleSyncSmoothMinSpeed);
102 
103 static float sg_cycleSyncSmoothThreshold = .2f;
104 static tSettingItem<float> conf_smoothThreshold ("CYCLE_SMOOTH_THRESHOLD", sg_cycleSyncSmoothThreshold);
105 
106 static REAL sg_enemyChatbotTimePenalty = 30.0f;   //!< penalty for victim in chatbot mode
107 static tSettingItem<REAL> sg_enemyChatbotTimePenaltyConf( "ENEMY_CHATBOT_PENALTY", sg_enemyChatbotTimePenalty );
108 extern REAL sg_suicideTimeout;
109 
clamp(REAL & c,REAL min,REAL max)110 static inline void clamp(REAL &c, REAL min, REAL max){
111     tASSERT(min < max);
112 
113     if (!finite(c))
114         c = 0;
115 
116     if (c<min)
117         c = min;
118 
119     if (c>max)
120         c = max;
121 }
122 
123 
124 REAL		gCycle::wallsStayUpDelay=8.0f;	// the time the cycle walls stay up ( negative values: they stay up forever )
125 
126 REAL		gCycle::wallsLength=-1.0f;		// the maximum total length of the walls
127 REAL		gCycle::explosionRadius=4.0f;	// the radius of the holes blewn in by an explosion
128 
129 static		nSettingItem<REAL> *c_pwsud = NULL, *c_pwl = NULL, *c_per = NULL;
130 
PrivateSettings()131 void gCycle::PrivateSettings()
132 {
133     static nSettingItem<REAL> c_wsud("CYCLE_WALLS_STAY_UP_DELAY",wallsStayUpDelay);
134     static nSettingItem<REAL> c_wl("CYCLE_WALLS_LENGTH",wallsLength);
135     static nSettingItem<REAL> c_er("CYCLE_EXPLOSION_RADIUS",explosionRadius);
136 
137     c_pwsud=&c_wsud;
138     c_pwl  =&c_wl;
139     c_per  =&c_er;
140 }
141 
142 // sound speed divisor
143 static REAL sg_speedCycleSound=15;
144 static nSettingItem<REAL> c_ss("CYCLE_SOUND_SPEED",
145                                sg_speedCycleSound);
146 
147 // time after spawning it takes the cycle to start building a wall
148 static REAL sg_cycleWallTime=0.0;
149 static nSettingItemWatched<REAL>
150 sg_cycleWallTimeConf("CYCLE_WALL_TIME",
151                      sg_cycleWallTime,
152                      nConfItemVersionWatcher::Group_Bumpy,
153                      14);
154 
155 // time after spawning during which a cycle can't be killed
156 static REAL sg_cycleInvulnerableTime=0.0;
157 static nSettingItemWatched<REAL>
158 sg_cycleInvulnerableTimeConf("CYCLE_INVULNERABLE_TIME",
159                              sg_cycleInvulnerableTime,
160                              nConfItemVersionWatcher::Group_Bumpy,
161                              12);
162 
163 // time after spawning during which a cycle can't be killed
164 static bool sg_cycleFirstSpawnProtection=false;
165 static nSettingItemWatched<bool>
166 sg_cycleFirstSpawnProtectionConf("CYCLE_FIRST_SPAWN_PROTECTION",
167                                  sg_cycleFirstSpawnProtection,
168                                  nConfItemVersionWatcher::Group_Bumpy,
169                                  12);
170 
171 // time intevals between server-client syncs
172 static REAL sg_syncIntervalEnemy=1;
173 static tSettingItem<REAL> c_sie( "CYCLE_SYNC_INTERVAL_ENEMY",
174                                  sg_syncIntervalEnemy );
175 
176 static REAL sg_syncIntervalSelf=.1;
177 static tSettingItem<REAL> c_sis( "CYCLE_SYNC_INTERVAL_SELF",
178                                  sg_syncIntervalSelf );
179 
180 // flag controlling case-by-case-niceness for older clients
181 static bool sg_avoidBadOldClientSync=true;
182 static tSettingItem<bool> c_sbs( "CYCLE_AVOID_OLDCLIENT_BAD_SYNC",
183                                  sg_avoidBadOldClientSync );
184 
185 // fast forward factor for extrapolating sync
186 static REAL sg_syncFF=10;
187 static tSettingItem<REAL> c_sff( "CYCLE_SYNC_FF",
188                                  sg_syncFF );
189 
190 static int sg_syncFFSteps=1;
191 static tSettingItem<int> c_sffs( "CYCLE_SYNC_FF_STEPS",
192                                  sg_syncFFSteps );
193 
194 // client side bugfix: local tunneling is avoided on syncs
195 static nVersionFeature sg_NoLocalTunnelOnSync( 4 );
196 
sg_GetSyncIntervalSelf(gCycle * cycle)197 static REAL sg_GetSyncIntervalSelf( gCycle* cycle )
198 {
199     if ( sg_NoLocalTunnelOnSync.Supported( cycle->Owner() ) )
200         return sg_syncIntervalSelf;
201     else
202         return sg_syncIntervalEnemy * 10;
203 }
204 
205 // moviepack hack
206 //static bool moviepack_hack=false;       // do we use it?
207 //static tSettingItem<bool> ump("MOVIEPACK_HACK",moviepack_hack);
208 
209 static int score_hole=0;
210 static tSettingItem<int> s_h("SCORE_HOLE",score_hole);
211 
212 static int score_survive=0;
213 static tSettingItem<int> s_sur("SCORE_SURVIVE",score_survive);
214 
215 static int score_die=-2;
216 static tSettingItem<int> s_d("SCORE_DIE",score_die);
217 
218 static int score_kill=3;
219 static tSettingItem<int> s_k("SCORE_KILL",score_kill);
220 
221 static int score_suicide=-4;
222 static tSettingItem<int> s_s("SCORE_SUICIDE",score_suicide);
223 
224 // input control
225 
226 uActionPlayer gCycle::s_brake("CYCLE_BRAKE", -5);
227 static uActionPlayer s_brakeToggle("CYCLE_BRAKE_TOGGLE", -5);
228 
229 static eWavData cycle_run("moviesounds/engine.wav","sound/cyclrun.wav");
230 static eWavData turn_wav("moviesounds/cycturn.wav","sound/expl.wav");
231 static eWavData scrap("sound/expl.wav");
232 
233 // a class of textures where the transparent part of the
234 // image is replaced by the player color
235 class gTextureCycle: public rSurfaceTexture
236 {
237     gRealColor color_; // player color
238     bool wheel; // wheel or body
239 public:
240     gTextureCycle(rSurface const & surface, const gRealColor& color,bool repx=0,bool repy=0,bool wheel=false);
241 
242     virtual void ProcessImage(SDL_Surface *im);
243 
244     virtual void OnSelect(bool enforce);
245 };
246 
gTextureCycle(rSurface const & surface,const gRealColor & color,bool repx,bool repy,bool w)247 gTextureCycle::gTextureCycle(rSurface const & surface, const gRealColor& color,bool repx,bool repy,bool w)
248         :rSurfaceTexture(rTextureGroups::TEX_OBJ,surface,repx,repy),
249         color_(color),wheel(w)
250 {
251     Select();
252 }
253 
ProcessImage(SDL_Surface * im)254 void gTextureCycle::ProcessImage(SDL_Surface *im)
255 {
256 #ifndef DEDICATED
257     // blend transparent texture parts with cycle color
258     tVERIFY(im->format->BytesPerPixel == 4);
259     GLubyte R=int(color_.r*255);
260     GLubyte G=int(color_.g*255);
261     GLubyte B=int(color_.b*255);
262 
263     GLubyte *pixels =reinterpret_cast<GLubyte *>(im->pixels);
264 
265     for(int i=im->w*im->h-1;i>=0;i--){
266         GLubyte alpha=pixels[4*i+3];
267         pixels[4*i  ] = (alpha * pixels[4*i  ] + (255-alpha)*R) >> 8;
268         pixels[4*i+1] = (alpha * pixels[4*i+1] + (255-alpha)*G) >> 8;
269         pixels[4*i+2] = (alpha * pixels[4*i+2] + (255-alpha)*B) >> 8;
270         pixels[4*i+3] = 255;
271     }
272 #endif
273 }
274 
OnSelect(bool enforce)275 void gTextureCycle::OnSelect(bool enforce){
276 #ifndef DEDICATED
277     rISurfaceTexture::OnSelect(enforce);
278 
279     if(rTextureGroups::TextureMode[rTextureGroups::TEX_OBJ]<0){
280         REAL R=color_.r,G=color_.g,B=color_.b;
281         if(wheel){
282             R*=.7;
283             G*=.7;
284             B*=.7;
285         }
286         glColor3f(R,G,B);
287         GLfloat color[4]={R,G,B,1};
288 
289         glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,color);
290         glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,color);
291     }
292 #endif
293 }
294 
295 // from gCycleMovement.cpp
296 extern void sg_RubberValues( ePlayerNetID const * player, REAL speed, REAL & max, REAL & effectiveness );
297 extern REAL sg_brakeCycle;
298 extern REAL sg_cycleBrakeDeplete;
299 
300 // in release mode, default values should always be used. in debug mode, we want to experiment :)
301 #ifdef DEBUG
302 #ifndef DEDICATED
303 #define DEBUGCHATBOT
304 #endif
305 #endif
306 
307 #ifdef DEBUGCHATBOT
308 typedef tSettingItem<REAL> gChatBotSetting;
309 typedef tSettingItem<bool> gChatBotSwitch;
310 #else
311 typedef nSettingItem<REAL> gChatBotSetting;
312 typedef nSettingItem<bool> gChatBotSwitch;
313 #endif
314 
315 static bool sg_chatBotAlwaysActive = false;
316 static gChatBotSwitch sg_chatBotAlwaysActiveConf( "CHATBOT_ALWAYS_ACTIVE", sg_chatBotAlwaysActive );
317 
318 static REAL sg_chatBotNewWallBlindness = .3;
319 static gChatBotSetting sg_chatBotNewWallBlindnessConf( "CHATBOT_NEW_WALL_BLINDNESS",
320         sg_chatBotNewWallBlindness );
321 
322 static REAL sg_chatBotMinTimestep = .3;
323 static gChatBotSetting sg_chatBotMinTimestepConf( "CHATBOT_MIN_TIMESTEP",
324         sg_chatBotMinTimestep );
325 
326 static REAL sg_chatBotDelay = .5;
327 static gChatBotSetting sg_chatBotDelayConf( "CHATBOT_DELAY",
328         sg_chatBotDelay );
329 
330 static REAL sg_chatBotRange = 1;
331 static gChatBotSetting sg_chatBotRangeConf( "CHATBOT_RANGE",
332         sg_chatBotRange );
333 
334 static REAL sg_chatBotDecay = .02;
335 static gChatBotSetting sg_chatBotDecayConf( "CHATBOT_DECAY",
336         sg_chatBotDecay );
337 
338 class gCycleChatBot
339 {
340     gCycleChatBot();
341 public:
342 class Sensor: public gSensor
343     {
344     public:
Sensor(gCycle * o,const eCoord & start,const eCoord & d)345         Sensor(gCycle *o,const eCoord &start,const eCoord &d)
346                 : gSensor(o,start,d)
347                 , hitOwner_( 0 )
348                 , hitTime_ ( 0 )
349                 , hitDistance_( o->MaxWallsLength() )
350                 , lrSuggestion_( 0 )
351                 , windingNumber_( 0 )
352         {
353             if ( hitDistance_ <= 0 )
354                 hitDistance_ = o->GetDistance();
355         }
356 
357         /*
358         // do detection and additional stuff
359         void detect( REAL range )
360         {
361             gSensor::detect( range );
362         }
363         */
364 
PassEdge(const eWall * ww,REAL time,REAL a,int r)365         virtual void PassEdge(const eWall *ww,REAL time,REAL a,int r)
366         {
367             try{
368                 gSensor::PassEdge(ww,time,a,r);
369             }
370             catch( eSensorFinished & e )
371             {
372                 if ( DoExtraDetectionStuff() )
373                     throw;
374             }
375         }
376 
DoExtraDetectionStuff()377         bool DoExtraDetectionStuff()
378         {
379             // move towards the beginning of a wall
380             lrSuggestion_ = -lr;
381 
382             switch ( type )
383             {
384             case gSENSOR_NONE:
385             case gSENSOR_RIM:
386                 lrSuggestion_ = 0;
387                 return true;
388             default:
389                 // unless it is an enemy, follow his wall instead (uncomment for a nasty cowardy campbot)
390                 // lrSuggestion *= -1;
391             case gSENSOR_SELF:
392                 {
393                     // determine whether we're hitting the front or back half of his wall
394                     if ( !ehit )
395                         return true;
396                     eWall * wall = ehit->GetWall();
397                     if ( !wall )
398                         return true;
399                     gPlayerWall * playerWall = dynamic_cast< gPlayerWall * >( wall );
400                     if ( !playerWall )
401                         return true;
402                     hitOwner_ = playerWall->Cycle();
403                     if ( !hitOwner_ )
404                         return true;
405 
406                     // gCycleChatBot & enemyChatBot = Get( hitOwner_ );
407 
408                     REAL wallAlpha = playerWall->Edge()->Ratio( before_hit );
409                     // that's an unreliable source
410                     if ( wallAlpha < 0 )
411                         wallAlpha = 0;
412                     if ( wallAlpha > 1 )
413                         wallAlpha = 1;
414                     hitDistance_   = hitOwner_->GetDistance() - playerWall->Pos( wallAlpha );
415                     hitTime_       = playerWall->Time( wallAlpha );
416                     windingNumber_ = playerWall->WindingNumber();
417 
418                     // don't see new walls
419                     if ( hitTime_ > hitOwner_->LastTime() - sg_chatBotNewWallBlindness && hitOwner_ != owned )
420                     {
421                         ehit = NULL;
422                         hit = 1E+40;
423                         return false;
424                     }
425 
426                     // REAL cycleDistance = hitOwner_->GetDistance();
427 
428                     // REAL wallStart = 0;
429 
430                     /*
431                     if ( gCycle::WallsLength() > 0 )
432                     {
433                         wallStart = cyclePos - playerWall->Cycle()->ThisWallsLength();
434                         if ( wallStart < 0 )
435                             wallStart = 0;
436                     }
437                     */
438                 }
439             }
440 
441             return true;
442         }
443 
444         // check how far the hit wall extends straight into the given direction
HitWallExtends(eCoord const & dir,eCoord const & origin)445         REAL HitWallExtends( eCoord const & dir, eCoord const & origin )
446         {
447             if ( !ehit || !ehit->Other() )
448             {
449                 return 1E+30;
450             }
451 
452             REAL ret = -1E+30;
453             eCoord ends[2] = { *ehit->Point(), *ehit->Other()->Point() };
454             for ( int i = 1; i>=0; --i )
455             {
456                 REAL newRet = eCoord::F( dir, ends[i]-origin );
457                 if ( newRet > ret )
458                     ret = newRet;
459             }
460 
461             return ret;
462         }
463 
464         gCycle * hitOwner_;     // the owner of the hit wall
465         REAL     hitTime_;      // the time the hit wall was built at
466         REAL     hitDistance_;  // the distance of the wall to the cycle that built it
467         short    lrSuggestion_; // sensor's oppinon on whether moving to the left or right of the hit wall is recommended (-1 for left, +1 for right)
468         int      windingNumber_; // the number of turns (with sign) the cycle has taken
469     };
470 
gCycleChatBot(gCycle * owner)471     gCycleChatBot( gCycle * owner )
472             : nextChatAI_( 0 )
473             , timeOnChatAI_( 0 )
474             , lastTurn_( 0 )
475             , nextTurn_ ( 0 )
476             , turnedRecently_ ( 0 )
477             , owner_ ( owner )
478     {
479 #ifdef RLBOT
480         rlDir = 1;
481         rlLastTime = -100;
482 #endif
483     }
484 
485     // describes walls we like. We like enemy walls. We like to go between them.
486     class WallHug
487     {
488     public:
489         gCycle const * owner_;  // the cycle the walls we like belong to
490         REAL lastTimeSeen_;    // the last time we saw such a wall
491 
WallHug()492         WallHug()
493                 : owner_ ( NULL )
494                 , lastTimeSeen_ ( 0 )
495         {
496         }
497     };
498 
499     // promote seen walls to possible wallhug replacements
FindHugReplacement(Sensor const & sensor)500     void FindHugReplacement( Sensor const & sensor )
501     {
502         gCycle const * owner = sensor.hitOwner_;
503         if (!owner)
504             return;
505 
506         // store as possible replacement
507         if ( !hugReplacement_.owner_ && sensor.type != gSENSOR_SELF &&
508                 owner != hugLeft_.owner_ &&
509                 owner != hugRight_.owner_ )
510         {
511             hugReplacement_.owner_ = sensor.hitOwner_;
512             hugReplacement_.lastTimeSeen_ = nextChatAI_;
513         }
514 
515         // update timestamps
516         if ( owner == hugLeft_.owner_ )
517             hugLeft_.lastTimeSeen_ = nextChatAI_;
518         if ( owner == hugRight_.owner_ )
519             hugRight_.lastTimeSeen_ = nextChatAI_;
520     }
521 
522     // determines the distance between two sensors; the size should give the likelyhood
523     // to survive if you pass through a gap between the two selected walls
Distance(Sensor const & a,Sensor const & b)524     REAL Distance( Sensor const & a, Sensor const & b )
525     {
526         // make sure a is left from b
527         if ( a.Direction() * b.Direction() < 0 )
528             return Distance( b, a );
529 
530         bool self = a.type == gSENSOR_SELF || b.type == gSENSOR_SELF;
531         bool rim  = a.type == gSENSOR_RIM || b.type == gSENSOR_RIM;
532 
533         // avoid. own. walls.
534         REAL selfHatred = 1;
535         if ( a.type == gSENSOR_SELF )
536         {
537             selfHatred *= .5;
538             if ( a.lr > 0 )
539             {
540                 selfHatred *= .5;
541                 if ( b.type == gSENSOR_RIM )
542                     selfHatred *= .25;
543             }
544         }
545         if ( b.type == gSENSOR_SELF )
546         {
547             selfHatred *= .5;
548             if ( b.lr < 0 )
549             {
550                 selfHatred *= .5;
551                 if ( a.type == gSENSOR_RIM )
552                     selfHatred *= .25;
553             }
554         }
555 
556         // some big distance to return if we don't know anything better
557         REAL bigDistance = owner_->MaxWallsLength();
558         if ( bigDistance <= 0 )
559             bigDistance = owner_->GetDistance();
560 
561         if ( a.hitOwner_ != b.hitOwner_ )
562         {
563             // different owners? Great, there has to be a way through!
564             REAL ret =
565                 a.hitDistance_ + b.hitDistance_;
566 
567             if ( rim )
568             {
569                 ret = bigDistance * .001 + ret * .01 + ( a.before_hit - b.before_hit).Norm();
570 
571                 // we love going between the rim and enemies
572                 if ( !self )
573                     ret = bigDistance * 2;
574             }
575 
576             // minimal factor should be 1, this path should never return something smaller than the
577             // paths where only one cycle's walls are hit
578             ret *= 16;
579 
580             // or empty space
581             if ( a.type == gSENSOR_NONE || b.type == gSENSOR_NONE )
582                 ret *= 2;
583 
584             return ret * selfHatred;
585         }
586         else if ( rim )
587         {
588             // at least one rim wall? Take the distance between the hit positions.
589             return ( a.before_hit - b.before_hit).Norm() * selfHatred;
590         }
591         else if ( a.type == gSENSOR_NONE && b.type == gSENSOR_NONE )
592         {
593             // empty space! Woo!
594             return owner_->GetDistance() * 256;
595         }
596         else if ( a.lr != b.lr )
597         {
598             // different directions? Also great!
599             return ( fabsf( a.hitDistance_ - b.hitDistance_ ) + .25 * bigDistance ) * selfHatred;
600         }
601         /*
602         else if ( - 2 * a.lr * (a.windingNumber_ - b.windingNumber_ ) > owner_->Grid()->WindingNumber() )
603         {
604             // this looks like a way out to me
605             return fabsf( a.hitDistance_ - b.hitDistance_ ) * 10 * selfHatred;
606         }
607         */
608         else
609         {
610             // well, the longer the wall segment between the two points, the better.
611             return fabsf( a.hitDistance_ - b.hitDistance_ ) * selfHatred;
612         }
613 
614         // default: hit distance
615         return ( a.before_hit - b.before_hit).Norm() * selfHatred;
616     }
617 
Get(gCycle * cycle)618     static gCycleChatBot & Get( gCycle * cycle )
619     {
620         tASSERT( cycle );
621 
622         // create
623         if ( cycle->chatBot_.get() == 0 )
624             cycle->chatBot_ = std::auto_ptr< gCycleChatBot >( new gCycleChatBot( cycle ) );
625 
626         return *cycle->chatBot_;
627     }
628 
CanMakeTurn(uActionPlayer * action)629     bool CanMakeTurn( uActionPlayer * action )
630     {
631         return owner_->CanMakeTurn( ( action == &gCycle::se_turnRight ) ? 1 : -1 );
632     }
633 
634 #ifdef RLBOT
635     int rlDir;
636     REAL rlLastTime;
637 #endif
638 
639     // does the main thinking
Activate(REAL currentTime)640     void Activate( REAL currentTime )
641     {
642 #ifdef RLBOT
643         // hack chatbot for crazy turning
644         {
645             if (!owner_->Alive() || !owner_->Vulnerable() )
646             {
647                 return;
648             }
649             if( fabs( rlLastTime - currentTime) > 1 )
650             {
651                 owner_->Act( &gCycle::se_turnRight, 1 );
652                 rlDir = -1;
653             }
654             else  if ( rlDir > 0 )
655             {
656                 if( CanMakeTurn( &gCycle::se_turnRight ) )
657                 {
658                     owner_->Act( &gCycle::se_turnRight, 1 );
659                     owner_->Act( &gCycle::se_turnRight, 1 );
660                     owner_->Act( &gCycle::se_turnRight, 1 );
661                     rlDir = -1;
662                 }
663             }
664             else
665             {
666                 if( CanMakeTurn( &gCycle::se_turnLeft ) )
667                 {
668                     owner_->Act( &gCycle::se_turnLeft, 1 );
669                     owner_->Act( &gCycle::se_turnLeft, 1 );
670                     owner_->Act( &gCycle::se_turnLeft, 1 );
671                     rlDir = 1;
672                 }
673             }
674             rlLastTime = currentTime;
675             return;
676         }
677 #endif
678 
679         // is it already time for activation?
680         if ( currentTime < nextChatAI_ )
681             return;
682 
683         REAL lookahead = sg_chatBotRange;  // seconds to plan ahead
684         REAL minstep   = sg_chatBotMinTimestep; // minimum timestep between thoughts in seconds
685         REAL maxstep   = sg_chatBotMinTimestep * 4 * ( 1 + .1 * tReproducibleRandomizer::GetInstance().Get() );  // maximum timestep between thoughts in seconds
686 
687         // chat AI wasn't active yet, so don't start immediately
688         if ( nextChatAI_ <= EPS )
689         {
690             nextChatAI_ = sg_chatBotDelay + currentTime;
691             return;
692         }
693 
694         timeOnChatAI_ += currentTime - nextChatAI_;
695 
696         // cylce data
697         REAL speed = owner_->Speed();
698         eCoord dir = owner_->Direction();
699         eCoord pos = owner_->Position();
700 
701         // make chat AI worse over time
702         if ( sn_GetNetState() != nSTANDALONE )
703         {
704             REAL qualityFactor = ( timeOnChatAI_ * sg_chatBotDecay );
705             if ( qualityFactor > 1 )
706             {
707                 minstep *= qualityFactor;
708                 // maxstep *= qualityFactor;
709             }
710         }
711 
712         REAL range= speed * lookahead;
713         eCoord scanDir = dir * range;
714 
715         REAL frontFactor = .5;
716 
717         Sensor front(owner_,pos,scanDir);
718         front.detect(frontFactor);
719         owner_->enemyInfluence.AddSensor( front, sg_enemyChatbotTimePenalty, owner_ );
720 
721         REAL minMoveOn = 0, maxMoveOn = 0, moveOn = 0;
722 
723         // get extra time we get through rubber usage
724         REAL rubberGranted, rubberEffectiveness;
725         sg_RubberValues( owner_->player, speed, rubberGranted, rubberEffectiveness );
726         REAL rubberTime = ( rubberGranted - owner_->GetRubber() )*rubberEffectiveness/speed;
727         REAL rubberRatio = owner_->GetRubber()/rubberGranted;
728 
729         if ( front.ehit )
730         {
731             turnedRecently_ = false;
732 
733             // these checks can hit our last wall and fail. Temporarily set it to NULL.
734             tJUST_CONTROLLED_PTR< gNetPlayerWall > lastWall = owner_->lastWall;
735             owner_->lastWall = NULL;
736 
737             REAL narrowFront = 1;
738 
739             // cast four diagonal rays
740             Sensor forwardLeft ( owner_, pos, scanDir.Turn(+1,+1 ) );
741             Sensor backwardLeft( owner_, pos, scanDir.Turn(-1,+narrowFront) );
742             forwardLeft.detect(1);
743             backwardLeft.detect(1);
744             Sensor forwardRight ( owner_, pos, scanDir.Turn(+1,-1 ) );
745             Sensor backwardRight( owner_, pos, scanDir.Turn(-1,-narrowFront) );
746             forwardRight.detect(1);
747             backwardRight.detect(1);
748 
749             // do we have a hug replacement candiate? If so, take it.
750             if ( hugReplacement_.owner_ && !hugLeft_.owner_ && !hugRight_.owner_ )
751             {
752                 // first time hugging? let the status quo decide.
753                 int lr = 0;
754                 if ( backwardLeft.hitOwner_ == hugReplacement_.owner_ )
755                     lr--;
756                 if ( forwardLeft.hitOwner_ == hugReplacement_.owner_ )
757                     lr--;
758                 if ( backwardRight.hitOwner_ == hugReplacement_.owner_ )
759                     lr++;
760                 if ( forwardRight.hitOwner_ == hugReplacement_.owner_ )
761                     lr++;
762 
763                 if ( lr > 0 )
764                     hugRight_ = hugReplacement_;
765                 if ( lr < 0 )
766                     hugLeft_ = hugReplacement_;
767 
768                 hugReplacement_.owner_ = 0;
769             }
770 
771             if ( hugReplacement_.owner_ )
772             {
773                 if( hugLeft_.lastTimeSeen_ < hugRight_.lastTimeSeen_ )
774                 {
775                     if ( hugReplacement_.lastTimeSeen_ > hugLeft_.lastTimeSeen_ )
776                         hugLeft_ = hugReplacement_;
777                 }
778                 else
779                 {
780                     if ( hugReplacement_.lastTimeSeen_ > hugRight_.lastTimeSeen_ )
781                         hugRight_ = hugReplacement_;
782                 }
783                 hugReplacement_.owner_ = 0;
784             }
785 
786             FindHugReplacement( front );
787             FindHugReplacement( forwardLeft );
788             FindHugReplacement( forwardRight );
789             FindHugReplacement( backwardLeft );
790             FindHugReplacement( backwardRight );
791 
792             // determine survival chances in the four directions
793             REAL frontOpen = Distance ( forwardLeft, forwardRight );
794             REAL leftOpen  = Distance ( forwardLeft, backwardLeft );
795             REAL rightOpen = Distance ( forwardRight, backwardRight );
796             REAL rearOpen = Distance ( backwardLeft, backwardRight );
797 
798             Sensor self( owner_, pos, scanDir.Turn(-1, 0) );
799             // fake entries
800             self.before_hit = pos;
801             self.windingNumber_ = owner_->windingNumber_;
802             self.type = gSENSOR_SELF;
803             self.hitDistance_ = 0;
804             self.hitOwner_ = owner_;
805             self.hitTime_ = currentTime;
806             self.lr = -1;
807             REAL rearLeftOpen = Distance( backwardLeft, self );
808             self.lr = 1;
809             REAL rearRightOpen = Distance( backwardRight, self );
810 
811             /*
812             // override: don't camp (too much)
813             if ( forwardRight.type == gSENSOR_SELF &&
814                     forwardLeft.type == gSENSOR_SELF &&
815                     backwardRight.type == gSENSOR_SELF &&
816                     backwardLeft.type == gSENSOR_SELF &&
817                     front.type == gSENSOR_SELF &&
818                     forwardRight.lr == front.lr &&
819                     forwardLeft.lr == front.lr &&
820                     backwardRight.lr == front.lr &&
821                     backwardLeft.lr == front.lr &&
822                     frontOpen + leftOpen + rightOpen < owner_->GetDistance() * .5 )
823             {
824                 turnedRecently_ = true;
825                 if ( front.lr > 0 )
826                 {
827                     if ( leftOpen > minstep * speed )
828                         // force a turn to the left
829                         rightOpen = 0;
830                     else if ( front.hit * range < 2 * minstep )
831                         // force a preliminary turn to the right that will allow us to reverse
832                         frontOpen = 0;
833                 }
834                 else
835                 {
836                     if ( rightOpen > minstep * speed )
837                         // force a turn to the right
838                         leftOpen = 0;
839                     else if ( front.hit * range < 2 * minstep )
840                         // force a preliminary turn to the left that will allow us to reverse
841                         frontOpen = 0;
842                 }
843             }
844             */
845 
846             // override rim hugging
847             if ( forwardRight.type == gSENSOR_SELF &&
848                     forwardLeft.type == gSENSOR_RIM &&
849                     backwardRight.type == gSENSOR_SELF &&
850                     backwardLeft.type == gSENSOR_RIM &&
851                     // backwardLeft.hit < .1 &&
852                     forwardRight.lr == -1 &&
853                     backwardRight.lr == -1 )
854             {
855                 turnedRecently_ = true;
856                 if ( rightOpen > speed * ( owner_->GetTurnDelay() - rubberTime * .8 ) )
857                 {
858                     owner_->Act( &gCycle::se_turnRight, 1 );
859                     owner_->Act( &gCycle::se_turnRight, 1 );
860                 }
861                 else
862                 {
863                     owner_->Act( &gCycle::se_turnLeft, 1 );
864                     owner_->Act( &gCycle::se_turnLeft, 1 );
865                 }
866             }
867 
868             if ( forwardLeft.type == gSENSOR_SELF &&
869                     forwardRight.type == gSENSOR_RIM &&
870                     backwardLeft.type == gSENSOR_SELF &&
871                     backwardRight.type == gSENSOR_RIM &&
872                     // backwardRight.hit < .1 &&
873                     forwardLeft.lr == 1 &&
874                     backwardLeft.lr == 1 )
875             {
876                 turnedRecently_ = true;
877                 if ( leftOpen > speed * ( owner_->GetTurnDelay() - rubberTime * .8 ) )
878                 {
879                     owner_->Act( &gCycle::se_turnLeft, 1 );
880                     owner_->Act( &gCycle::se_turnLeft, 1 );
881                 }
882                 else
883                 {
884                     owner_->Act( &gCycle::se_turnRight, 1 );
885                     owner_->Act( &gCycle::se_turnRight, 1 );
886                 }
887             }
888 
889             // get the best turn direction
890             uActionPlayer * bestAction = ( leftOpen > rightOpen ) ? &gCycle::se_turnLeft : &gCycle::se_turnRight;
891             int             bestDir      = ( leftOpen > rightOpen ) ? 1 : -1;
892             REAL            bestOpen     = ( leftOpen > rightOpen ) ? leftOpen : rightOpen;
893             Sensor &        bestForward  = ( leftOpen > rightOpen ) ? forwardLeft : forwardRight;
894             Sensor &        bestBackward = ( leftOpen > rightOpen ) ? backwardLeft : backwardRight;
895 
896             Sensor direct ( owner_, pos, scanDir.Turn( 0, bestDir) );
897             direct.detect( 1 );
898 
899             // restore last wall
900             owner_->lastWall = lastWall;
901 
902             // only turn if the hole has a shape that allows better entry after we do a zig-zag, or if we're past the good turning point
903             // see how the survival chance is distributed between forward and backward half
904             REAL forwardHalf  = Distance ( direct, bestForward );
905             REAL backwardHalf = Distance ( direct, bestBackward );
906 
907             REAL forwardOverhang  = bestForward.HitWallExtends( bestForward.Direction(), pos );
908             REAL backwardOverhang  = bestBackward.HitWallExtends( bestForward.Direction(), pos );
909 
910             // we have to move forward this much before we can hope to turn
911             minMoveOn = bestBackward.HitWallExtends( dir, pos );
912 
913             // maybe the direct to the side sensor is better?
914             REAL minMoveOnOther = direct.HitWallExtends( dir, pos );
915 
916             // determine how far we can drive on
917             maxMoveOn      = bestForward.HitWallExtends( dir, pos );
918             REAL maxMoveOnOther = front.HitWallExtends( dir, pos );
919             if ( maxMoveOn > maxMoveOnOther )
920                 maxMoveOn = maxMoveOnOther;
921 
922             if ( maxMoveOn > minMoveOnOther && forwardHalf > backwardHalf && direct.hitOwner_ == bestBackward.hitOwner_ )
923             {
924                 backwardOverhang  = direct.HitWallExtends( bestForward.Direction(), pos );
925                 minMoveOn = minMoveOnOther;
926             }
927 
928             // best place to turn
929             moveOn = .5 * ( minMoveOn * ( 1 + rubberRatio ) + maxMoveOn * ( 1 - rubberRatio ) );
930 
931             // hit the brakes before you hit anything and if it's worth it
932             bool brake = sg_brakeCycle > 0 &&
933                          front.hit * lookahead * sg_cycleBrakeDeplete < owner_->GetBrakingReservoir() &&
934                          sg_brakeCycle * front.hit * lookahead < 2 * speed * owner_->GetBrakingReservoir() &&
935                          ( maxMoveOn - minMoveOn ) > 0 &&
936                          owner_->GetBrakingReservoir() * ( maxMoveOn - minMoveOn ) < speed * owner_->GetTurnDelay();
937             if ( frontOpen < bestOpen &&
938                     ( forwardOverhang <= backwardOverhang || ( minMoveOn < 0 && moveOn < minstep * speed ) ) )
939             {
940                 // FindHugReplacement( direct );
941                 // REAL expectedBackwardHalf = ( direct.before_hit - bestBackward.before_hit ).Norm();
942 
943                 // if ( ( ( forwardHalf + backwardHalf > bestOpen * 2 || backwardHalf > frontOpen * 10 || backwardHalf > expectedBackwardHalf * 1.01 ) && frontOpen < bestOpen ) ||
944                 // rubberTime * .5 + minspace * lookahead < minstep )
945                 //                {
946                 turnedRecently_ = true;
947 
948                 minMoveOn = maxMoveOn = moveOn = 0;
949 
950                 /*
951                 if (
952                     ( ( ( bestBackward.type == gSENSOR_ENEMY || bestBackward.type == gSENSOR_TEAMMATE ) && bestBackward.hitDistance_ < bestBackward.hit * lookahead * speed ) ||
953                       direct.hit * lookahead + rubberTime < owner_->GetTurnDelay() ) &&
954                     ( bestBackward.hit * lookahead + rubberTime < owner_->GetTurnDelay() ||
955                       bestForward.hit * lookahead + rubberTime < owner_->GetTurnDelay() )
956                 )
957                 {
958                     // override: stupid turn into certain death, turn it around if that makes it less stupid
959                     uActionPlayer * newBestAction = ( leftOpen > rightOpen ) ? &gCycle::se_turnLeft : &gCycle::se_turnRight;
960                     Sensor newDirect ( owner_, pos, scanDir.Turn( 0, -bestDir) );
961                     newDirect.detect( 1 );
962                     if ( newDirect.hit > direct.hit ||
963                             newDirect.hit * lookahead + rubberTime > owner_->GetTurnDelay() )
964                         owner_->Act( newBestAction, 1 );
965                 }
966                 else
967                 */
968                 {
969                     if ( !CanMakeTurn( bestAction ) )
970                     {
971                         nextChatAI_ = currentTime;
972                         return;
973                     }
974 
975                     owner_->Act( bestAction, 1 );
976                 }
977 
978                 brake = false;
979             }
980             else
981             {
982                 // the best
983                 REAL bestSoFar = frontOpen > bestOpen ? frontOpen : bestOpen;
984                 bestSoFar *= ( 10 * ( 1 - rubberRatio ) + 1 );
985 
986                 if ( rearOpen > bestSoFar && ( rearLeftOpen > bestSoFar || rearRightOpen > bestSoFar ) )
987                 {
988                     brake = false;
989                     turnedRecently_ = true;
990 
991                     bool goLeft = rearLeftOpen > rearRightOpen;
992 
993                     // dead end. reverse into the opposite direction of the front wall
994                     uActionPlayer * bestAction = goLeft ? &gCycle::se_turnLeft : &gCycle::se_turnRight;
995                     uActionPlayer * otherAction = !goLeft ? &gCycle::se_turnLeft : &gCycle::se_turnRight;
996                     Sensor &        bestForward  = goLeft ? forwardLeft : forwardRight;
997                     Sensor &        bestBackward  = goLeft ? backwardLeft : backwardRight;
998                     Sensor &        otherForward  = !goLeft ? forwardLeft : forwardRight;
999                     Sensor &        otherBackward  = !goLeft ? backwardLeft : backwardRight;
1000 
1001                     // space in the two directions available for turns
1002                     REAL bestHit = bestForward.hit > bestBackward.hit ? bestBackward.hit : bestForward.hit;
1003                     REAL otherHit = otherForward.hit > otherBackward.hit ? otherBackward.hit : otherForward.hit;
1004 
1005                     bool wait = false;
1006 
1007                     if ( !CanMakeTurn( bestAction ) )
1008                     {
1009                         nextChatAI_ = currentTime;
1010                         return;
1011                     }
1012 
1013                     // well, after a short turn to the right if space is tight
1014                     if ( bestHit * lookahead < owner_->GetTurnDelay() + rubberTime )
1015                     {
1016                         if ( otherHit < bestForward.hit * 2 && front.hit * lookahead > owner_->GetTurnDelay() * 2 )
1017                         {
1018                             // wait a bit, perhaps there will be a better spot
1019                             wait = true;
1020                         }
1021                         else
1022                         {
1023                             if ( !CanMakeTurn( otherAction ) )
1024                             {
1025                                 nextChatAI_ = currentTime;
1026                                 return;
1027                             }
1028 
1029                             owner_->Act( otherAction, 1 );
1030 
1031                             // there needs to be space ahead to finish the maneuver correctly
1032                             if ( maxMoveOn < speed * owner_->GetTurnDelay() )
1033                             {
1034                                 // there isn't. oh well, turn into the wrong direction completely, see if I care
1035                                 owner_->Act( otherAction, 1 );
1036                                 wait = true;
1037                             }
1038                         }
1039                     }
1040 
1041                     if ( !wait )
1042                     {
1043                         owner_->Act( bestAction, 1 );
1044                         owner_->Act( bestAction, 1 );
1045                     }
1046 
1047                     minMoveOn = maxMoveOn = moveOn = 0;
1048                 }
1049             }
1050 
1051             // execute brake command
1052             owner_->Act( &gCycle::s_brake, brake ? 1 : -1 );
1053 
1054             // swap hugged walls if we're in fact grinding them the other way round
1055             if ( hugLeft_.owner_ == backwardRight.hitOwner_ ||
1056                     hugRight_.owner_ == backwardLeft.hitOwner_ )
1057             {
1058                 WallHug swap = hugRight_;
1059                 hugRight_ = hugLeft_;
1060                 hugLeft_ = swap;
1061             }
1062         }
1063 
1064         // REAL mintime = minspace * lookahead;
1065 
1066         // try again soon
1067         //        REAL newmintime = mintime * .5 - minstep * .2 * tReproducibleRandomizer::GetInstance().Get();
1068 
1069         // clamp
1070         // if ( newmintime < minstep )
1071         // newmintime = minstep;
1072 
1073         // add slack, acceleration and rubber
1074         // if ( owner_->acceleration > 0 )
1075         // mintime -= owner_->acceleration * mintime * mintime / speed;
1076         // mintime -= .1 * minstep - rubberTime * .3;
1077 
1078         // if the next step gets us too close to the wall to do anything useful,
1079         // bring us really close right away.
1080         // if ( mintime - newmintime > minstep )
1081         // {
1082         // mintime = newmintime;
1083         // }
1084 
1085         REAL space = moveOn;
1086         REAL minTime = space/speed;
1087 
1088         if ( turnedRecently_ )
1089             minTime = owner_->GetTurnDelay();
1090 
1091         if ( minTime < minstep )
1092             minTime = minstep;
1093         if ( minTime > maxstep + minstep * 1.5 )
1094         {
1095             minTime = maxstep;
1096         }
1097         // minTime = 0;
1098 
1099         nextChatAI_ = currentTime + minTime;
1100         timeOnChatAI_ += minTime;
1101     }
1102 
1103     REAL nextChatAI_;        //!< the next time the chat AI can be active
1104 private:
1105     REAL timeOnChatAI_;      //!< the total time the player was on chat AI this round
1106     short lastTurn_;         //!< the last turn the chat AI made
1107     REAL nextTurn_;          //!< the next turn if one is planned
1108     bool turnedRecently_;    //!< whether the cycle was turned or almost turned recently
1109     gCycle * owner_;         //!< owner of chatbot
1110 
1111     WallHug hugLeft_;              //!< the wall we like to have on our left side
1112     WallHug hugRight_;             //!< the wall we like to have on our right side
1113     WallHug hugReplacement_;       //!< a possible replacement candidate for one of the hugged walls
1114 };
1115 
1116 //  *****************************************************************
1117 
sg_ArchiveCoord(eCoord & coord,int level)1118 static void sg_ArchiveCoord( eCoord & coord, int level )
1119 {
1120     static char const * section = "_COORD";
1121     tRecorderSync< eCoord >::Archive( section, level, coord );
1122 }
1123 
sg_ArchiveReal(REAL & real,int level)1124 static void sg_ArchiveReal( REAL & real, int level )
1125 {
1126     static char const * section = "_REAL";
1127     tRecorderSync< REAL >::Archive( section, level, real );
1128 }
1129 
1130 //  *****************************************************************
1131 
1132 
1133 
1134 //  *****************************************************************
1135 
1136 
1137 // take pos,dir and time from a cycle
gDestination(const gCycleMovement & c)1138 gDestination::gDestination(const gCycleMovement &c)
1139         :chatting(false)
1140         ,turns(0)
1141         ,hasBeenUsed(false)
1142         ,messageID(1)
1143         ,missable(true)
1144         ,next(NULL)
1145         ,list(NULL)
1146 {
1147     CopyFrom( c );
1148 }
1149 
gDestination(const gCycle & c)1150 gDestination::gDestination(const gCycle &c)
1151         :chatting(false)
1152         ,turns(0)
1153         ,hasBeenUsed(false)
1154         ,messageID(1)
1155         ,missable(true)
1156         ,next(NULL)
1157         ,list(NULL)
1158 {
1159     CopyFrom( c );
1160 }
1161 
1162 // or from a message
gDestination(nMessage & m,unsigned short & cycle_id)1163 gDestination::gDestination(nMessage &m, unsigned short & cycle_id )
1164         :gameTime(0),distance(0),speed(0),
1165         hasBeenUsed(false),
1166         messageID(1),
1167         missable(true),
1168 next(NULL),list(NULL){
1169     m >> position;
1170     m >> direction;
1171     m >> distance;
1172 
1173     unsigned short flags;
1174     m >> flags;
1175     braking  = flags & 0x01;
1176     chatting = flags & 0x02;
1177 
1178     messageID = m.MessageID();
1179 
1180     turns = 0;
1181 
1182     m.Read( cycle_id );
1183 
1184     if ( !m.End() )
1185         m >> gameTime;
1186     else
1187         gameTime = -1000;
1188 
1189     if ( !m.End() )
1190     {
1191         m.Read( turns );
1192     }
1193 }
1194 
CopyFrom(const gCycleMovement & other)1195 void gDestination::CopyFrom(const gCycleMovement &other)
1196 {
1197     position 	= other.Position();
1198     direction 	= other.Direction();
1199     gameTime 	= other.LastTime();
1200     distance 	= other.GetDistance();
1201     speed 		= other.Speed();
1202     braking 	= other.GetBraking();
1203     turns 		= other.GetTurns();
1204 
1205 #ifdef DEBUG
1206     if (!finite(gameTime) || !finite(speed) || !finite(distance))
1207         st_Breakpoint();
1208 #endif
1209     if ( other.Owner() && other.Player() )
1210         chatting = other.Player()->IsChatting();
1211 
1212     // cheat. If rubber ran out, backdate the time.
1213     if ( other.RubberDepleteTime() > 0 )
1214     {
1215         gameTime = other.RubberDepleteTime();
1216     }
1217 }
1218 
CopyFrom(const gCycle & other)1219 void gDestination::CopyFrom(const gCycle &other)
1220 {
1221     CopyFrom( static_cast<const gCycleMovement&>(other) );
1222 
1223     // correct distance
1224     distance 	+= other.correctDistanceSmooth;
1225 }
1226 
1227 // *******************************************************************************************
1228 // *
1229 // *	CompareWith
1230 // *
1231 // *******************************************************************************************
1232 //!
1233 //!		@param	other	the destination to compare with
1234 //!		@return			-1 if this destination came earler, +1 if other was earler, 0 if no difference can be found
1235 //!
1236 // *******************************************************************************************
1237 
CompareWith(const gDestination & other) const1238 int gDestination::CompareWith( const gDestination & other ) const
1239 {
1240     // compare distances, but use the network message ID ( they are always increasing, the distance may stagnate or in extreme cases run backwards ) as main sort criterion
1241 
1242     // compare message IDs with overflow ( if both are at good values )
1243     if ( messageID > 1 && other.messageID > 1 )
1244     {
1245         short messageIDDifference = messageID - other.messageID;
1246         if ( messageIDDifference < 0 )
1247             return -1;
1248         if ( messageIDDifference > 0 )
1249             return 1;
1250     }
1251 
1252     // compare travelled distance
1253     if ( distance < other.distance )
1254         return -1;
1255     else if ( distance > other.distance )
1256         return 1;
1257 
1258     // no relevant difference found
1259     return 0;
1260 }
1261 
1262 class gFloatCompressor
1263 {
1264 public:
gFloatCompressor(REAL min,REAL max)1265     gFloatCompressor( REAL min, REAL max )
1266             : min_( min ), max_( max ){}
1267 
1268     // write compressed float to message
Write(nMessage & m,REAL value) const1269     void Write( nMessage& m, REAL value ) const
1270     {
1271         clamp( value, min_, max_ );
1272         unsigned short compressed = static_cast< unsigned short > ( maxShort_ * ( value - min_ )/( max_ - min_ ) );
1273         m.Write( compressed );
1274     }
1275 
Read(nMessage & m) const1276     REAL Read( nMessage& m ) const
1277     {
1278         unsigned short compressed;
1279         m.Read( compressed );
1280 
1281         return  min_ + compressed * ( max_ - min_ )/maxShort_;
1282     }
1283 private:
1284     REAL min_, max_;  // minimal and maximal expected value
1285 
1286     static const unsigned short maxShort_;
1287 
1288     gFloatCompressor();
1289 };
1290 
1291 const unsigned short gFloatCompressor::maxShort_ = 0xFFFF;
1292 
1293 static gFloatCompressor compressZeroOne( 0, 1 );
1294 
1295 // write all the data into a nMessage
WriteCreate(nMessage & m,unsigned short cycle_id)1296 void gDestination::WriteCreate(nMessage &m, unsigned short cycle_id ){
1297     m << position;
1298     m << direction;
1299     m << distance;
1300 
1301     unsigned short flags = 0;
1302     if ( braking )
1303         flags |= 0x01;
1304     if ( chatting )
1305         flags |= 0x02;
1306     m << flags;
1307 
1308     // store message ID for later reference
1309     messageID = m.MessageID();
1310 
1311     m.Write( cycle_id );
1312     m << gameTime;
1313     m.Write( turns );
1314 }
1315 
RightBefore(gDestination * list,REAL dist)1316 gDestination *gDestination::RightBefore(gDestination *list, REAL dist){
1317     if (!list || list->distance > dist)
1318         return NULL;
1319 
1320     gDestination *ret=list;
1321     while (ret && ret->next && ret->next->distance < dist)
1322         ret=ret->next;
1323 
1324     return ret;
1325 }
1326 
RightAfter(gDestination * list,REAL dist)1327 gDestination *gDestination::RightAfter(gDestination *list, REAL dist){
1328     if (!list)
1329         return NULL;
1330 
1331     gDestination *ret=list;
1332     while (ret && ret->distance < dist)
1333         ret=ret->next;
1334 
1335     return ret;
1336 }
1337 
1338 // insert yourself into a list ordered by gameTime
InsertIntoList(gDestination ** l)1339 void gDestination::InsertIntoList(gDestination **l){
1340     if (!l)
1341         return;
1342 
1343     RemoveFromList();
1344 
1345     // let message find its place
1346     while (l && *l && CompareWith( **l ) > 0 )
1347         l = &((*l)->next);
1348 
1349     list=l;
1350 
1351     tASSERT(l);
1352 
1353     next=*l;
1354     *l=this;
1355 }
1356 
1357 
1358 
RemoveFromList()1359 void gDestination::RemoveFromList(){
1360     /*
1361        if (!list)
1362            return;
1363 
1364        while (list && *list && *list != this)
1365            list = &((*list)->next);
1366 
1367        tASSERT(list);
1368        tASSERT(*list);
1369 
1370        (*list) = next;
1371        next=NULL;
1372        list=NULL;
1373 
1374       */
1375 
1376     // z-man: HUH? I don't understand the code above any more and it seems to be leaky.
1377     // This simple alternative sounds better:
1378 
1379     if(list)
1380         *list = next;
1381     if(next)
1382         next->list = list;
1383 
1384     next = 0;
1385     list = 0;
1386 }
1387 
1388 // *******************************************************************************************
1389 // *
1390 // *	GetGameTime
1391 // *
1392 // *******************************************************************************************
1393 //!
1394 //!		@return		game time of the command
1395 //!
1396 // *******************************************************************************************
1397 
GetGameTime(void) const1398 REAL gDestination::GetGameTime( void ) const
1399 {
1400     return this->gameTime;
1401 }
1402 
1403 // *******************************************************************************************
1404 // *
1405 // *	GetGameTime
1406 // *
1407 // *******************************************************************************************
1408 //!
1409 //!		@param	gameTime	game time of the command to fill
1410 //!		@return		A reference to this to allow chaining
1411 //!
1412 // *******************************************************************************************
1413 
GetGameTime(REAL & gameTime) const1414 gDestination const & gDestination::GetGameTime( REAL & gameTime ) const
1415 {
1416     gameTime = this->gameTime;
1417     return *this;
1418 }
1419 
1420 // *******************************************************************************************
1421 // *
1422 // *	SetGameTime
1423 // *
1424 // *******************************************************************************************
1425 //!
1426 //!		@param	gameTime	game time of the command to set
1427 //!		@return		A reference to this to allow chaining
1428 //!
1429 // *******************************************************************************************
1430 
SetGameTime(REAL gameTime)1431 gDestination & gDestination::SetGameTime( REAL gameTime )
1432 {
1433     this->gameTime = gameTime;
1434     return *this;
1435 }
1436 
1437 // ********************************************************
1438 
new_destination_handler(nMessage & m)1439 static void new_destination_handler(nMessage &m)
1440 {
1441     // read the destination
1442     unsigned short cycle_id;
1443     gDestination *dest=new gDestination(m, cycle_id );
1444 
1445     // and the ID of the cycle the destination is added to
1446     nNetObject *o=nNetObject::ObjectDangerous(cycle_id);
1447     if (o && &o->CreatorDescriptor() == &cycle_init){
1448         if ((sn_GetNetState() == nSERVER) && (m.SenderID() != o->Owner()))
1449         {
1450             Cheater(m.SenderID());
1451         }
1452         else
1453             if(o->Owner() != ::sn_myNetID)
1454             {
1455                 gCycle* c = dynamic_cast<gCycle *>(o);
1456                 if (c)
1457                 {
1458                     if ( c->Player() && !dest->Chatting() )
1459                         c->Player()->Activity();
1460 
1461                     // fill default gametime
1462                     if ( dest->GetGameTime() < -100 )
1463                         dest->SetGameTime( se_GameTime()+c->Lag()*3 );
1464 
1465                     c->AddDestination(dest);
1466                     dest = 0;
1467                 }
1468             }
1469     }
1470 
1471     // delete the destination if it has not been used
1472     // TODO: exception safety!
1473     delete dest;
1474 }
1475 
1476 static nDescriptor destination_descriptor(321,&new_destination_handler,"destinaton");
1477 
BroadCastNewDestination(gCycleMovement * c,gDestination * dest)1478 static void BroadCastNewDestination(gCycleMovement *c, gDestination *dest){
1479     nMessage *m=new nMessage(destination_descriptor);
1480     dest->WriteCreate(*m, c->ID() );
1481     m->BroadCast();
1482 }
1483 
IsMe(eGameObject const * other) const1484 bool gCycle::IsMe( eGameObject const * other ) const
1485 {
1486     return other == this || other == extrapolator_;
1487 }
1488 
1489 #ifdef DELAYEDTURN_DEBUG
1490 double sg_turnReceivedTime = 0;
1491 #endif
1492 
OnNotifyNewDestination(gDestination * dest)1493 void gCycle::OnNotifyNewDestination( gDestination* dest )
1494 {
1495 #ifdef DELAYEDTURN_DEBUG
1496     sg_turnReceivedTime = tSysTimeFloat();
1497 #endif
1498 
1499 #ifdef GNUPLOT_DEBUG
1500     if ( sg_gnuplotDebug && Player() )
1501     {
1502         std::ofstream f( Player()->GetUserName() + "_sync", std::ios::app );
1503         f << dest->position.x << " " << dest->position.y << "\n";
1504     }
1505 #endif
1506 
1507     gCycleMovement::OnNotifyNewDestination( dest );
1508 
1509     //  if (sn_GetNetState()==nSERVER || ::sn_myNetID == owner)
1510     if (sn_GetNetState()==nCLIENT && ::sn_myNetID == Owner())
1511         BroadCastNewDestination(this,dest);
1512 
1513     if ( extrapolator_ )
1514     {
1515         // add destination and try to squeeze it into the schedule.
1516         extrapolator_->NotifyNewDestination( dest );
1517     }
1518 
1519     // start a new simulation in any case. The server may simulate the movement a bit differently at the turn.
1520     // resimulate_ = ( sn_GetNetState() == nCLIENT );
1521 
1522     // detect lag slides
1523     if( sn_GetNetState() == nSERVER )
1524     {
1525         // see how far we should be simulated in an ideal world
1526         REAL simTime=se_GameTime() - Lag();
1527 
1528         REAL lag = simTime - dest->gameTime;  // the real lag
1529         REAL lagOffset = simTime - lastTime;  // difference between real lag and practical lag (what we need to compensate)
1530         if ( sn_GetNetState() == nSERVER )
1531         {
1532             eLag::Report( Owner(), lag );
1533             if ( currentWall && currentWall->Wall() && rubberSpeedFactor >= 1-EPS )
1534             {
1535                 lag -= lagOffset; // switch to practical lag
1536 
1537                 // no compensation? Just quit.
1538                 if ( lag < 0 )
1539                     return;
1540 
1541                 // see how much we can go back
1542                 REAL minDist   = currentWall->Wall()->Pos(0);
1543                 REAL maxGoBack = ( distance - minDist ) * .8;
1544 
1545                 // see how much we should go back
1546                 REAL stepBack = distance - dest->distance;
1547 
1548                 if ( rubberSpeedFactor < 1-EPS )
1549                 {
1550                     // make the correction distance based so we don't loosen a grind
1551                     maxGoBack = stepBack;
1552                 }
1553 
1554                 // clamp so we don't go back too far
1555                 if ( stepBack > maxGoBack )
1556                 {
1557                     stepBack = maxGoBack;
1558                     lag = rubberSpeedFactor*stepBack/verletSpeed_;
1559                 }
1560 
1561                 // ask lag compensation how much we are allowed to go back; switch to real lag
1562                 // for the credit
1563                 lag = eLag::TakeCredit( Owner(), lag + lagOffset ) - lagOffset;
1564 
1565                 // don't go back further than last sync to the owner.
1566                 // old clients get doubly confused and produce an extra
1567                 // evil lag slide.
1568                 static nVersionFeature noConfusionFromMoveBack( 16 );
1569                 if( !noConfusionFromMoveBack.Supported( Owner() ) &&
1570                     lastTime - lag < lastSyncOwnerGameTime_ )
1571                 {
1572                     lag = lastTime - lastSyncOwnerGameTime_;
1573                 }
1574 
1575                 // no compensation? Just quit.
1576                 if ( lag < 0 )
1577                     return;
1578 
1579                 // go back in time
1580                 if ( rubberSpeedFactor >= 1-EPS )
1581                 {
1582                     // rubber is inactive, basic timestep is enough
1583                     TimestepCore( lastTime - lag );
1584                 }
1585                 else if ( 0 )
1586                 {
1587                     // rubber is active. Take care!
1588 
1589                     // just extrapolate the movement backwards
1590                     REAL step = lag * verletSpeed_;
1591                     lastTime -= lag;
1592 
1593                     // rubber is a bit tricker, we need the effectiveness
1594                     REAL rubberGranted, rubberEffectiveness;
1595                     sg_RubberValues( player, verletSpeed_, rubberGranted, rubberEffectiveness );
1596                     if ( rubberEffectiveness > 0 )
1597                         rubber -= step/rubberEffectiveness;
1598 
1599                     if ( rubber < 0 )
1600                         rubber = 0;
1601 
1602                     // position and distance are easy
1603                     step *= rubberSpeedFactor;
1604                     distance -= step;
1605                     pos = pos - dirDrive * step;
1606 
1607                     // undo acceleration
1608                     verletSpeed_ -= acceleration * lag;
1609                 }
1610 
1611                 // see if we went back too far (should almost never happen)
1612                 if ( distance < minDist )
1613                 {
1614                     // st_Breakpoint();
1615                     TimestepCore( lastTime + ( minDist - distance )/verletSpeed_ );
1616                 }
1617             }
1618         }
1619     }
1620 }
1621 
1622 
1623 // *******************************************************************************************
1624 // *
1625 // *	OnDropTempWall
1626 // *
1627 // *******************************************************************************************
1628 //!
1629 //!		@param	wall	   the wall the other cycle is grinding
1630 //!		@param	pos	       the position of the grind
1631 //!     @param  dir        the direction the raycast triggering the gridding comes from
1632 //!
1633 // *******************************************************************************************
1634 
OnDropTempWall(gPlayerWall * wall,eCoord const & position,eCoord const & dir)1635 void gCycle::OnDropTempWall( gPlayerWall * wall, eCoord const & position, eCoord const & dir )
1636 {
1637     tASSERT( wall );
1638 
1639     unsigned short idrec = ID();
1640     tRecorderSync< unsigned short >::Archive( "_ON_DROP_WALL", 8, idrec );
1641 
1642     // determine if the grinded wall is current enough
1643     bool wallRight = ( currentWall && ( wall->NetWall() == currentWall || wall->NetWall() == currentWall ) );
1644 
1645     // don't drop if we already dropped a short time ago
1646     if ( wallRight && currentWall->Edge()->Vec().NormSquared() < verletSpeed_ * verletSpeed_ * sg_minDropInterval * sg_minDropInterval )
1647         wallRight = false;
1648 
1649     tRecorderSync< bool >::Archive( "_ON_DROP_WALL_RIGHT", 8, wallRight );
1650 
1651     // drop the current wall if eiter this or the last wall is grinded
1652     // gNetPlayerWall * nw = wall->NetWall();
1653     if ( wallRight )
1654     {
1655         // calculate relative position of grinding in wall; if alpha is positive, it's already too late
1656         REAL alpha = currentWall->Edge()->Edge(0)->Ratio( position );
1657         tRecorderSync< REAL >::Archive( "_ON_DROP_WALL_ALPHA", 8, alpha );
1658         if ( alpha > -.5 )
1659         {
1660             unsigned short idrec = ID();
1661             tRecorderSync< unsigned short >::Archive( "_ON_DROP_WALL_DROP", 8, idrec );
1662 
1663             // just request the drop, Timestep() will execute it later
1664             dropWallRequested_ = true;
1665 
1666             // bend last driving direction to -dir. That way, should the grinder overtake this cycle,
1667             // it will end up on the right side of his wall.
1668             lastDirDrive = -dir;
1669         }
1670     }
1671 }
1672 
1673 // ************************************************************
1674 
1675 
1676 
1677 
1678 // ************************************************************
1679 
1680 
1681 // the time the cycle walls stay up ( negative values: they stay up forever )
SetWallsStayUpDelay(REAL delay)1682 void 	gCycle::SetWallsStayUpDelay	( REAL delay )
1683 {
1684     c_pwsud->Set( delay );
1685 }
1686 
1687 // how much rubber usage shortens the walls
1688 static REAL sg_cycleRubberWallShrink = 0;
1689 static nSettingItemWatched<REAL>
1690 sg_cycleRubberWallShrinkConf("CYCLE_RUBBER_WALL_SHRINK",
1691                              sg_cycleRubberWallShrink,
1692                              nConfItemVersionWatcher::Group_Bumpy,
1693                              12);
1694 
1695 // make walls grow with distance traveled
1696 static REAL sg_cycleDistWallShrink = 0;
1697 static nSettingItemWatched<REAL>
1698 sg_cycleDistWallShrinkConf("CYCLE_DIST_WALL_SHRINK",
1699                            sg_cycleDistWallShrink,
1700                            nConfItemVersionWatcher::Group_Bumpy,
1701                            12);
1702 
1703 static REAL sg_cycleDistWallShrinkOffset = 0;
1704 static nSettingItemWatched<REAL>
1705 sg_cycleDistWallShrinkOffsetConf("CYCLE_DIST_WALL_SHRINK_OFFSET",
1706                                  sg_cycleDistWallShrinkOffset,
1707                                  nConfItemVersionWatcher::Group_Bumpy,
1708                                  12);
1709 
1710 // calculates the effect of driving distance to wall length
sg_CycleWallLengthFromDist(REAL distance)1711 static REAL sg_CycleWallLengthFromDist( REAL distance )
1712 {
1713     REAL len = gCycle::WallsLength();
1714     if ( len <= 0 )
1715     {
1716         return len;
1717     }
1718 
1719     // make base length longer or shorter, depending on the sign of sg_cycleDistWallShrink
1720     REAL d = sg_cycleDistWallShrinkOffset - distance;
1721     if ( d > 0 )
1722         len -= sg_cycleDistWallShrink * d;
1723 
1724     return len;
1725 }
1726 
1727 // the current length of the walls of this cycle
ThisWallsLength() const1728 REAL gCycle::ThisWallsLength() const
1729 {
1730     // get distance influence
1731     REAL len = sg_CycleWallLengthFromDist( distance );
1732     if ( len <= 0 )
1733     {
1734         return len;
1735     }
1736 
1737     // apply rubber shortening
1738     return len - GetRubber() * sg_cycleRubberWallShrink;
1739 }
1740 
1741 
1742 // the speed the end of the trail currently receeds with
WallEndSpeed() const1743 REAL gCycle::WallEndSpeed() const
1744 {
1745     REAL rubberMax, rubberEffectiveness;
1746     sg_RubberValues( player, Speed(), rubberMax, rubberEffectiveness );
1747 
1748     // basic speed from cycle movement
1749     REAL speed = rubberSpeedFactor * Speed();
1750 
1751     // take distance shrinking into account
1752     REAL d = sg_cycleDistWallShrinkOffset - distance;
1753     if ( d > 0 )
1754         speed *= ( 1 - sg_cycleDistWallShrink );
1755 
1756     // speed from rubber usage and shringing
1757     if ( rubberEffectiveness > 0 )
1758         speed += Speed() * ( 1 - rubberSpeedFactor ) * sg_cycleRubberWallShrink / rubberEffectiveness;
1759 
1760     return speed;
1761 }
1762 
1763 // the maximum total length of the walls
MaxWallsLength() const1764 REAL gCycle::MaxWallsLength() const
1765 {
1766     REAL len = sg_CycleWallLengthFromDist( distance );
1767 
1768     // exception: if the wall grows faster than it receeds, take the maximum, because the wall will
1769     // grow backwards
1770     if ( sg_cycleDistWallShrink > 1 )
1771     {
1772         len = wallsLength;
1773     }
1774 
1775     // if the wall grows from rubber use, add the maximal growth
1776     if ( sg_cycleRubberWallShrink >= 0 || sg_rubberCycle < 0 )
1777         return len;
1778     else
1779         return len - sg_cycleRubberWallShrink * sg_rubberCycle;
1780 }
1781 
1782 // the maximum total length of the walls
SetWallsLength(REAL length)1783 void 	gCycle::SetWallsLength			( REAL length)
1784 {
1785     c_pwl->Set( length );
1786 }
1787 
1788 // the radius of the holes blewn in by an explosion
SetExplosionRadius(REAL radius)1789 void 	gCycle::SetExplosionRadius		( REAL radius)
1790 {
1791     c_per->Set( radius );
1792 }
1793 
1794 //  *****************************************************************
1795 
1796 // copies relevant info from other cylce
CopyFrom(const gCycleMovement & other)1797 void gCycleExtrapolator::CopyFrom( const gCycleMovement& other )
1798 {
1799     // delegate
1800     gCycleMovement::CopyFrom( other );
1801 
1802     // set parent
1803     parent_ = &other;
1804 
1805     // copy distance
1806     trueDistance_ = GetDistance();
1807 }
1808 
1809 // copies relevant info from sync data and everything else from other cycle
CopyFrom(const SyncData & sync,const gCycle & other)1810 void gCycleExtrapolator::CopyFrom( const SyncData& sync, const gCycle& other )
1811 {
1812     // delegate
1813     gCycleMovement::CopyFrom( sync, other );
1814 
1815     //eFace* face1 = currentFace;
1816     MoveSafely( other.lastGoodPosition_, sync.time, sync.time );
1817     //eFace* face2 = currentFace;
1818     MoveSafely( sync.pos, sync.time, sync.time );
1819 
1820 #ifdef DEBUG_X
1821     if ( face1 != face2 || face1 != currentFace )
1822     {
1823         currentFace = face1;
1824         MoveSafely( sync.lastTurn *.01 + sync.pos * .99, sync.time, sync.time );
1825         MoveSafely( sync.pos, sync.time, sync.time );
1826     }
1827 #endif
1828 
1829     // set parent
1830     parent_ = &other;
1831 
1832     // set last turn
1833     lastTurnPos_ = sync.lastTurn;
1834 
1835     // copy distance
1836     trueDistance_ = GetDistance();
1837 
1838     // make a small timestep backwards if we passed the next
1839     // destination. While this makes us react a tad later to lag slides,
1840     // it avoids lag slide false positives, which later cause real
1841     // lag slides.
1842     if( eLag::Feature().Supported(0) )
1843     {
1844         gDestination *dest = GetCurrentDestination();
1845         if ( dest )
1846         {
1847             REAL distToDest = eCoord::F( dest->position - pos, dirDrive );
1848             if( distToDest < 0 )
1849             {
1850                 // instead of doing a full simulation, just trust the data from the
1851                 // destination.
1852                 pos = dest->position;
1853                 lastTime = dest->gameTime;
1854                 distance = dest->distance;
1855                 verletSpeed_ = dest->speed;
1856             }
1857         }
1858     }
1859 }
1860 
gCycleExtrapolator(eGrid * grid,const eCoord & pos,const eCoord & dir,ePlayerNetID * p,bool autodelete)1861 gCycleExtrapolator::gCycleExtrapolator(eGrid *grid, const eCoord &pos,const eCoord &dir,ePlayerNetID *p,bool autodelete)
1862         :gCycleMovement( grid, pos, dir, p, autodelete )
1863         ,trueDistance_( 0 )
1864         ,parent_( 0 )
1865 {
1866     // an extrapolator should not be visible as a gameobject from the outside
1867     eFace * currentFaceBack = currentFace;
1868     RemoveFromList();
1869     currentFace = currentFaceBack;
1870     if ( !currentFace )
1871         currentFace = grid->FindSurroundingFace( pos, currentFace );
1872 }
1873 
1874 // gCycleExtrapolator::gCycleExtrapolator(nMessage &m);
~gCycleExtrapolator()1875 gCycleExtrapolator::~gCycleExtrapolator()
1876 {
1877 }
1878 
1879 /*
1880 // returns the current destination
1881 gDestination* gCycleExtrapolator::GetCurrentDestination() const
1882 {
1883     return currentDestination;
1884 
1885     // the code below does not appear to be such a good idea after all...
1886     if ( currentDestination )
1887     {
1888         return currentDestination;
1889     }
1890     else
1891     {
1892         tASSERT( parent_ );
1893 
1894         // return a destination with the current parent position
1895         static gDestination parentPos( *parent_ );
1896         parentPos.missable = false;
1897         parentPos.CopyFrom( *parent_ );
1898         return &parentPos;
1899     }
1900 }
1901 */
1902 
EdgeIsDangerous(const eWall * ww,REAL time,REAL alpha) const1903 bool gCycleExtrapolator::EdgeIsDangerous(const eWall *ww, REAL time, REAL alpha ) const
1904 {
1905     const gPlayerWall *w = dynamic_cast<const gPlayerWall*>(ww);
1906     if (w)
1907     {
1908         gNetPlayerWall* nw = w->NetWall();
1909 
1910         // get time the wall was built
1911         REAL builtTime = w->Time(alpha);
1912 
1913         // is the wall built in the future ( happens during extrapolation )
1914         if ( builtTime > time )
1915             return false;
1916 
1917         // ignore temporary walls in some cases, they may not be real
1918         if ( nw && nw->Preliminary() && w->Cycle() == parent_ && fabs( dirDrive * w->Vec() ) < EPS )
1919             return false;
1920 
1921         // ignore recent walls of parent cycle
1922         gCycle *otherPlayer=w->Cycle();
1923         if (otherPlayer==parent_ &&
1924                 ( time < builtTime + 2 * GetTurnDelay() || GetDistance() < w->Pos( alpha ) + .01 * sg_delayCycle*Speed()/SpeedMultiplier()  )
1925            )
1926             return false;
1927     }
1928 
1929     // delegate
1930     return bool(parent_) && parent_->EdgeIsDangerous( ww, time, alpha ) && gCycleMovement::EdgeIsDangerous( ww, time, alpha );
1931 }
1932 
PassEdge(const eWall * ww,REAL time,REAL a,int)1933 void gCycleExtrapolator::PassEdge(const eWall *ww,REAL time,REAL a,int){
1934     {
1935         if (!EdgeIsDangerous(ww,time,a) || !Alive() )
1936         {
1937             return;
1938         }
1939         else
1940         {
1941             eCoord collPos = ww->Point( a );
1942             throw gCycleDeath( collPos );
1943         }
1944     }
1945 }
1946 
TimestepCore(REAL currentTime,bool calculateAcceleration)1947 bool gCycleExtrapolator::TimestepCore(REAL currentTime, bool calculateAcceleration)
1948 {
1949     // determine a suitable next destination
1950     gDestination destDefault( *parent_ ), *dest=GetCurrentDestination();
1951     if ( !dest )
1952     {
1953         dest = &destDefault;
1954     }
1955 
1956     // correct distance
1957     // distance = dest->distance - DistanceToDestination( *dest );
1958     // REAL distanceBefore = GetDistance();
1959     tASSERT(finite(distance));
1960 
1961     // delegate
1962     bool ret = false;
1963     try{
1964         ret = gCycleMovement::TimestepCore( currentTime, calculateAcceleration );
1965     }
1966     catch( gCycleDeath & )
1967     {
1968         return false;
1969     }
1970 
1971     // update true distance
1972     // trueDistance_ += GetDistance() - distanceBefore;
1973     trueDistance_ = distance;
1974 
1975     return ret;
1976 }
1977 
1978 /*
1979 bool gCycleExtrapolator::DoTurn( int dir )
1980 {
1981     // delegate
1982     return gCycleMovement::DoTurn( dir );
1983 }
1984 */
1985 
CreatorDescriptor() const1986 nDescriptor &gCycleExtrapolator::CreatorDescriptor() const{
1987     // should never be called
1988     tASSERT( 0 );
1989     return cycle_init;
1990 }
1991 
1992 //  *****************************************************************
1993 
Edge()1994 const eTempEdge* gCycle::Edge(){
1995     if (currentWall)
1996         return currentWall->Edge();
1997     else
1998         return NULL;
1999 }
2000 
CurrentWall()2001 const gPlayerWall* gCycle::CurrentWall(){
2002     if (currentWall)
2003         return currentWall->Wall();
2004     else
2005         return NULL;
2006 }
2007 
2008 /*
2009 const gPlayerWall* gCycle::LastWall(){
2010     if (lastWall)
2011         return lastWall->Wall();
2012     else
2013         return NULL;
2014 }
2015 */
2016 
2017 /*
2018 static tString lala_bodyTexture("Anonymous/original/textures/cycle_body.png");
2019 static nSettingItem<tString> gg_tex("TEXTURE_CYCLE_BODY", lala_bodyTexture);
2020 //static tConfItemLine g_tex("CYCLE_BODY", sg_bodyTexture);
2021 //static tSettingItem<tString> gg_tex("TEXTURE_CYCLE_BODY", sg_bodyTexture);
2022 static tString lala_wheelTexture("Anonymous/original/textures/cycle_wheel.png");
2023 static nSettingItem<tString> gg_wheelTexture("TEXTURE_CYCLE_WHEEL", lala_wheelTexture);
2024 
2025 static tString lala_bikeTexture("Anonymous/original/moviepack/bike.png");
2026 static nSettingItem<tString> lalala_bikeTexture("TEXTURE_MP_BIKE", lala_bikeTexture);
2027 */
2028 
2029 // HACK! Flexible model loader that eats anything a moviepack currently can throw at it.
2030 #ifndef DEDICATED
2031 struct gCycleVisuals
2032 {
2033     rModel *customModel, *bodyModel, *frontModel, *rearModel; // cycle models
2034     gTextureCycle *customTexture, *bodyTexture, *wheelTexture; // cycle textures
2035     gRealColor color; // cycle color
2036     bool mpType;      // true if moviepack type model/texture can be used
2037     int  mpPreference; // the user preference for the texture/model search path
2038 
gCycleVisualsgCycleVisuals2039     gCycleVisuals( gRealColor const & a_color )
2040     {
2041         customModel = bodyModel = frontModel = rearModel = 0;
2042         customTexture = bodyTexture = wheelTexture = 0;
2043 
2044         color = a_color;
2045 
2046         mpType = false;
2047         mpPreference = 0;
2048     }
2049 
~gCycleVisualsgCycleVisuals2050     ~gCycleVisuals()
2051     {
2052         delete customTexture;
2053         delete bodyTexture;
2054         delete wheelTexture;
2055     }
2056 
2057     enum Slot{ SLOT_CUSTOM=0, SLOT_BODY=1, SLOT_WHEEL=2, SLOT_MAX=3 };
2058 
2059     // loads a specific texture from a specific folder
LoadTextureSafe2gCycleVisuals2060     static rSurface * LoadTextureSafe2( Slot slot, int mp )
2061     {
2062         static std::auto_ptr<rSurface> cache[SLOT_MAX][2];
2063         std::auto_ptr<rSurface> & surface = cache[slot][mp];
2064         if ( surface.get() == NULL )
2065         {
2066             static char const * names[SLOT_MAX]={"bike.png","cycle_body.png", "cycle_wheel.png"};
2067             char const * name = names[slot];
2068 
2069             char const * folder = mp ? "moviepack" : "textures";
2070             tString file = tString(folder) + "/" + name;
2071 
2072             surface = std::auto_ptr<rSurface> ( tNEW( rSurface( file ) ) );
2073         }
2074 
2075         if ( surface->GetSurface() )
2076             return surface.get();
2077         else
2078             return NULL;
2079     }
2080 
2081     // load one texture from the prefered folder (or the other one as fallback)
LoadTextureSafegCycleVisuals2082     gTextureCycle * LoadTextureSafe( Slot slot, bool wheel )
2083     {
2084         rSurface * surface = LoadTextureSafe2( slot, mpPreference );
2085         if ( !surface )
2086             surface = LoadTextureSafe2( slot, 1-mpPreference );
2087 
2088         if ( surface )
2089             return tNEW( gTextureCycle )( *surface, color, 0, 0, wheel );
2090 
2091         return NULL;
2092     }
2093 
2094     // load textures from the specified folder (or the other one) and the format the model has just been read for
LoadTexturesgCycleVisuals2095     bool LoadTextures()
2096     {
2097         if ( mpType )
2098         {
2099             if ( !customTexture )
2100                 customTexture = LoadTextureSafe( SLOT_CUSTOM, false );
2101 
2102             return customTexture;
2103         }
2104         else
2105         {
2106             if ( !bodyTexture )
2107                 bodyTexture = LoadTextureSafe( SLOT_BODY, false );
2108             if ( !wheelTexture )
2109                 wheelTexture = LoadTextureSafe( SLOT_WHEEL, true );
2110 
2111             return bodyTexture && wheelTexture;
2112         }
2113     }
2114 
2115     // loads a model, checking before if the file exists
LoadModelSafegCycleVisuals2116     static rModel * LoadModelSafe( char const * filename )
2117     {
2118         return rModel::GetModel(filename);
2119     }
2120 
2121     // load a model of specified type from a specified directory
LoadModelgCycleVisuals2122     bool LoadModel( bool a_mpType, bool mpFolder )
2123     {
2124         mpType = a_mpType;
2125         char const * folder = mpFolder ? "moviepack" : "models";
2126 
2127         if ( mpType )
2128         {
2129             tString base = tString(folder);
2130             base << "/cycle";
2131             if (!customModel) customModel = LoadModelSafe( base + ".ASE" );
2132             if (!customModel) customModel = LoadModelSafe( base + ".ase" );
2133 
2134             return customModel && LoadTextures();
2135         }
2136         else
2137         {
2138             tString base = tString(folder) + "/cycle_";
2139 
2140             if (!bodyModel) bodyModel = LoadModelSafe( base + "body.mod" );
2141             if (!frontModel) frontModel = LoadModelSafe( base + "front.mod" );
2142             if (!rearModel) rearModel = LoadModelSafe( base + "rear.mod" );
2143 
2144             return bodyModel && frontModel && rearModel && LoadTextures();
2145         }
2146     }
2147 
2148     // tries to load everything from the given data folder
LoadModel2gCycleVisuals2149     bool LoadModel2( bool mp )
2150     {
2151         // first, try the right type, then the 'unnatural' choice
2152         return LoadModel( mp, mp ) || LoadModel( !mp, mp );
2153     }
2154 
2155     // top level load function: tries to load all variations, starting with passed moviepack folder flag
LoadModelgCycleVisuals2156     bool LoadModel( bool mp )
2157     {
2158         mpPreference = mp ? 1 : 0;
2159 
2160         // delegate to try loading both formats from both directories
2161         return LoadModel2( mp ) || LoadModel2( !mp );
2162     }
2163 };
2164 #endif
2165 
2166 #ifndef DEDICATED
2167 // renders a cycle even after it died
2168 class gCycleWallRenderer: public eReferencableGameObject
2169 {
2170 public:
gCycleWallRenderer(gCycle * cycle)2171     gCycleWallRenderer( gCycle * cycle )
2172     : eReferencableGameObject( cycle->Grid(), cycle->Position(), cycle->Direction(), cycle->CurrentFace(), true )
2173     , cycle_( cycle )
2174     {
2175         AddToList();
2176     }
2177 
2178 #if 0 // not required
2179     virtual ~gCycleWallRenderer()
2180     {
2181     }
2182 
2183     virtual void OnRemoveFromGame()
2184     {
2185         eReferencableGameObject::OnRemoveFromGame();
2186     }
2187 #endif
2188 private:
Render(eCamera const * camera)2189     virtual void Render( eCamera const * camera )
2190     {
2191         cycle_->displayList_.RenderAll( camera, cycle_ );
2192     }
2193 
Timestep(REAL currentTime)2194     virtual bool Timestep( REAL currentTime )
2195     {
2196         if ( !cycle_ )
2197         {
2198             return true;
2199         }
2200 
2201         Move( cycle_->Position(), lastTime, currentTime );
2202 
2203         return !cycle_->Alive() && !cycle_->displayList_.Walls();
2204     }
2205 
2206     tJUST_CONTROLLED_PTR< gCycle > cycle_;
2207 };
2208 #endif
2209 
MyInitAfterCreation()2210 void gCycle::MyInitAfterCreation(){
2211 // create wall renderer
2212 #ifndef DEDICATED
2213     new gCycleWallRenderer( this );
2214 #endif
2215 
2216     dropWallRequested_ = false;
2217     lastGoodPosition_ = pos;
2218 
2219 #ifdef DEBUG
2220     // con << "creating cycle.\n";
2221 #endif
2222     engine  = tNEW(eSoundPlayer)(cycle_run,true);
2223     turning = tNEW(eSoundPlayer)(turn_wav);
2224     spark   = tNEW(eSoundPlayer)(scrap);
2225 
2226     //correctDistSmooth=correctTimeSmooth=correctSpeedSmooth=0;
2227     correctDistanceSmooth = 0;
2228 
2229     resimulate_ = false;
2230 
2231     mp=sg_MoviePack();
2232 
2233     lastTimeAnim = 0;
2234     timeCameIntoView = 0;
2235 
2236     customModel = NULL;
2237     body = NULL;
2238     front = NULL;
2239     rear = NULL;
2240     wheelTex = NULL;
2241     bodyTex = NULL;
2242     customTexture = NULL;
2243 
2244     correctPosSmooth=eCoord(0,0);
2245 
2246     rotationFrontWheel=rotationRearWheel=eCoord(1,0);
2247 
2248     skew=skewDot=0;
2249 
2250     {
2251         dir=dirDrive;
2252         REAL dirLen=dir.NormSquared();
2253         if ( dirLen > 0 )
2254         {
2255             dir = dir * (1/sqrt( dirLen ));
2256         }
2257     }
2258 
2259     if (sn_GetNetState()!=nCLIENT){
2260         if(!player)
2261         { // distribute AI colors
2262             // con << current_ai << ':' << take_ai << ':' << maxmindist <<  "\n\n\n";
2263             color_.r=1;
2264             color_.g=1;
2265             color_.b=1;
2266 
2267             trailColor_.r=1;
2268             trailColor_.g=1;
2269             trailColor_.b=1;
2270         }
2271         else
2272         {
2273             player->Color(color_.r,color_.g,color_.b);
2274             player->TrailColor(trailColor_.r,trailColor_.g,trailColor_.b);
2275         }
2276 
2277         se_MakeColorValid( color_.r, color_.g, color_.b, 1.0f );
2278         se_MakeColorValid( trailColor_.r, trailColor_.g, trailColor_.b, .5f );
2279     }
2280 
2281     // load model and texture
2282 #ifndef DEDICATED
2283     gCycleVisuals visuals( color_ );
2284     if ( !visuals.LoadModel( mp ) )
2285     {
2286         tERR_ERROR( "Neither classic style nor moviepack style model and textures found. "
2287                     "The folders \"textures\" and \"moviepack\" need to contain either "
2288                     "cycle.ase and bike.png or body.mod, front.mod, rear.mod, cycle_body.png and cycle_wheel.png." );
2289     }
2290 
2291     mp = visuals.mpType;
2292 
2293     // transfer models and textures
2294     if ( mp )
2295     {
2296         // use moviepack style body and texture
2297         customModel = visuals.customModel;
2298         visuals.customModel = 0;
2299         customTexture = visuals.customTexture;
2300         visuals.customTexture = 0;
2301     }
2302     else
2303     {
2304         // use classic style body and texture
2305         body = visuals.bodyModel;
2306         visuals.bodyModel = 0;
2307         front = visuals.frontModel;
2308         visuals.frontModel = 0;
2309         rear = visuals.rearModel;
2310         visuals.rearModel = 0;
2311         bodyTex = visuals.bodyTexture;
2312         visuals.bodyTexture = 0;
2313         wheelTex = visuals.wheelTexture;
2314         visuals.wheelTexture = 0;
2315 
2316         tASSERT ( body && front && rear && bodyTex && wheelTex );
2317 
2318         mp = false;
2319     }
2320 #endif // DEDICATED
2321 
2322     /*
2323       the old, less flexible, loading code
2324 
2325     if (mp)
2326     {
2327         customModel=new rModel("moviepack/cycle.ASE","moviepack/cycle.ase");
2328     }
2329     else{
2330         body=new rModel("models/cycle_body.mod");
2331         front=new rModel("models/cycle_front.mod");
2332         rear=new rModel("models/cycle_rear.mod");
2333     }
2334 
2335     if (mp)
2336     {
2337         // static rSurface bike(lala_bikeTexture);
2338         static rSurface bike("moviepack/bike.png");
2339         customTexture=new gTextureCycle(bike,color_,0,0);
2340     }
2341     else{
2342         static rSurface body("textures/cycle_body.png");
2343         static rSurface wheel("textures/cycle_wheel.png");
2344         //static rSurface body((const char*) lala_bodyTexture);
2345         //static rSurface wheel((const char*) lala_wheelTexture);
2346         wheelTex=new gTextureCycle(wheel,color_,0,0,true);
2347         bodyTex=new gTextureCycle(body,color_,0,0);
2348     }
2349     */
2350 
2351 #ifdef DEBUG
2352     //con << "Created cycle.\n";
2353 #endif
2354     nextSyncOwner=nextSync=tSysTimeFloat()-1;
2355     lastSyncOwnerGameTime_ = 0;
2356 
2357     if (sn_GetNetState()!=nCLIENT)
2358         RequestSync();
2359 
2360     grid->AddGameObjectInteresting(this);
2361 
2362     spawnTime_=lastTimeAnim=lastTime;
2363     // set spawn time to infinite past if this is the first spawn
2364     if ( !sg_cycleFirstSpawnProtection && spawnTime_ <= 1.0 )
2365     {
2366         spawnTime_ = -1E+20;
2367     }
2368 
2369     if ( engine )
2370         engine->Reset(10000);
2371 
2372     if ( turning )
2373         turning->End();
2374 
2375     // add to game grid
2376     this->AddToList();
2377 
2378     predictPosition_ = pos;
2379 
2380 #ifdef GNUPLOT_DEBUG
2381     if ( sg_gnuplotDebug && Player() )
2382     {
2383         std::ofstream f( Player()->GetUserName() + "_step" );
2384         f << pos.x << " " << pos.y << "\n";
2385         std::ofstream g( Player()->GetUserName() + "_sync" );
2386         g << pos.x << " " << pos.y << "\n";
2387         std::ofstream h( Player()->GetUserName() + "_turn" );
2388         h << pos.x << " " << pos.y << "\n";
2389     }
2390 #endif
2391 }
2392 
InitAfterCreation()2393 void gCycle::InitAfterCreation(){
2394 #ifdef DEBUG
2395     if (!finite(Speed()))
2396         st_Breakpoint();
2397 #endif
2398     gCycleMovement::InitAfterCreation();
2399 #ifdef DEBUG
2400     if (!finite(Speed()))
2401         st_Breakpoint();
2402 #endif
2403     MyInitAfterCreation();
2404 }
2405 
gCycle(eGrid * grid,const eCoord & pos,const eCoord & d,ePlayerNetID * p)2406 gCycle::gCycle(eGrid *grid, const eCoord &pos,const eCoord &d,ePlayerNetID *p)
2407         :gCycleMovement(grid, pos,d,p,false),
2408         engine(NULL),
2409         turning(NULL),
2410         skew(0),skewDot(0),
2411         rotationFrontWheel(1,0),rotationRearWheel(1,0),heightFrontWheel(0),heightRearWheel(0),
2412         currentWall(NULL),
2413         lastWall(NULL)
2414 {
2415     windingNumberWrapped_ = windingNumber_ = Grid()->DirectionWinding(dirDrive);
2416     dirDrive = Grid()->GetDirection(windingNumberWrapped_);
2417     dir = dirDrive;
2418 
2419     deathTime=0;
2420 
2421     lastNetWall=lastWall=currentWall=NULL;
2422 
2423     MyInitAfterCreation();
2424 
2425     sg_ArchiveCoord( this->dirDrive, 1 );
2426     sg_ArchiveCoord( this->dir, 1 );
2427     sg_ArchiveCoord( this->pos, 1 );
2428     sg_ArchiveReal( this->verletSpeed_, 1 );
2429 }
2430 
~gCycle()2431 gCycle::~gCycle(){
2432 #ifdef DEBUG
2433     //  con << "deleting cylce...\n";
2434 #endif
2435     // clear the destination list
2436 
2437     tDESTROY(engine);
2438     tDESTROY(turning);
2439     tDESTROY(spark);
2440 
2441     this->RemoveFromGame();
2442 
2443     if (mp){
2444         delete customTexture;
2445     }
2446     else{
2447         delete wheelTex;
2448         delete bodyTex;
2449     }
2450 #ifdef DEBUG
2451     //con << "Deleted cycle.\n";
2452 #endif
2453     /*
2454       delete currentPos;
2455       delete last_turn;
2456     */
2457 }
2458 
OnRemoveFromGame()2459 void gCycle::OnRemoveFromGame()
2460 {
2461     // keep this cycle alive during this function
2462     tJUST_CONTROLLED_PTR< gCycle > keep;
2463 
2464     if ( this->GetRefcount() > 0 )
2465     {
2466         keep = this;
2467 
2468         this->Turn(0);
2469 
2470         if ( sn_GetNetState() == nSERVER )
2471             RequestSync();
2472     }
2473 
2474     // make sure we're dead, so our walls know they need to time out.
2475     if ( Alive() )
2476     {
2477         Die( lastTime );
2478     }
2479 
2480     if (currentWall)
2481         currentWall->CopyIntoGrid(0);
2482     currentWall=NULL;
2483     lastWall=NULL;
2484 
2485     gCycleMovement::OnRemoveFromGame();
2486 }
2487 
2488 // called when the round ends
OnRoundEnd()2489 void gCycle::OnRoundEnd()
2490 {
2491     // give survival bonus
2492     if ( Alive() && player )
2493     {
2494         Player()->AddScore( score_survive, tOutput("$player_win_survive"), tOutput() );
2495     }
2496 }
2497 
2498 
rotate(eCoord & r,REAL angle)2499 static inline void rotate(eCoord &r,REAL angle){
2500     REAL x=r.x;
2501     r.x+=r.y*angle;
2502     r.y-=x*angle;
2503     r=r*(1/sqrt(r.NormSquared()));
2504 }
2505 
2506 #ifdef MACOSX
2507 // Sparks have a large performance problem on Macs. See http://guru3.sytes.net/viewtopic.php?t=2167
2508 bool crash_sparks=false;
2509 #else
2510 bool crash_sparks=true;
2511 #endif
2512 
2513 //static bool forceTime=false;
2514 
2515 // from nNetwork.C
2516 extern REAL planned_rate_control[MAXCLIENTS+2];
2517 
2518 //! intermediate data for position prediction
2519 struct gPredictPositionData
2520 {
2521     REAL maxSpaceReport; //!< maximum space to report in front
2522 #ifdef DEDICATED
2523     REAL rubberStart;    //!< distance from a wall rubber starts to get activated
2524 #endif
2525 };
2526 
2527 // *******************************************************************************************
2528 // *
2529 // *	PreparePredictPosition()
2530 // *
2531 // *******************************************************************************************
2532 //!
2533 //!     @param  data data to be passed to CalculatePredictPosition() later
2534 //!
2535 // *******************************************************************************************
PreparePredictPosition(gPredictPositionData & data)2536 void gCycle::PreparePredictPosition( gPredictPositionData & data )
2537 {
2538     // don't cast a ray by default
2539     data.maxSpaceReport = 0;
2540 #ifdef DEDICATED
2541     if ( sg_predictWalls )
2542     {
2543         // predict to the maximum time anyone else may be simulated up to
2544         REAL maxTime = lastTime + MaxSimulateAhead() + GetMaxLazyLag();
2545         REAL predictDT = maxTime - lastTime;
2546         REAL lookAhead = predictDT * verletSpeed_;
2547 
2548         // see how far we can go before rubber kicks in
2549         data.rubberStart = verletSpeed_ / RubberSpeed();
2550 
2551         // store max lookahead plus rubber safety
2552         data.maxSpaceReport = lookAhead + data.rubberStart;
2553     }
2554 #else
2555     data.maxSpaceReport = verletSpeed_ * se_PredictTime() * rubberSpeedFactor;
2556 #endif
2557 
2558     // request a raycast of the right length
2559     maxSpaceMaxCast_ = data.maxSpaceReport;
2560 }
2561 
2562 // *******************************************************************************************
2563 // *
2564 // *	CalculatePredictPosition()
2565 // *
2566 // *******************************************************************************************
2567 //!
2568 //!     @param  data data from PreparePredictPosition()
2569 //!		@return the time up to which the cycle's position was predicted
2570 //!
2571 // *******************************************************************************************
2572 
CalculatePredictPosition(gPredictPositionData & data)2573 REAL gCycle::CalculatePredictPosition( gPredictPositionData & data )
2574 {
2575     // predict position
2576     REAL predictTime = lastTime;
2577     {
2578 #ifdef DEDICATED
2579         predictPosition_ = pos;
2580 
2581         if ( sg_predictWalls )
2582         {
2583             REAL spaceAhead = GetMaxSpaceAhead( data.maxSpaceReport );
2584             spaceAhead -= data.rubberStart;
2585 
2586             if ( spaceAhead > 0 )
2587             {
2588                 // store consistent prediction position and  time
2589                 predictPosition_ = pos + dirDrive * spaceAhead;
2590                 predictTime = lastTime + spaceAhead/verletSpeed_;
2591             }
2592         }
2593 #else
2594         // predict half a frame time
2595         predictTime += se_PredictTime();
2596         predictPosition_ = pos+correctPosSmooth + dirDrive * GetMaxSpaceAhead( data.maxSpaceReport );
2597 #endif
2598     }
2599 
2600     return predictTime;
2601 }
2602 
Timestep(REAL currentTime)2603 bool gCycle::Timestep(REAL currentTime){
2604     // clear out dangerous info when we're done
2605     gMaxSpaceAheadHitInfoClearer hitInfoClearer( maxSpaceHit_ );
2606 
2607     // archive rubber speed for later comparison
2608     REAL rubberSpeedFactorBack = rubberSpeedFactor;
2609 
2610     // if ( Owner() == sn_myNetID )
2611     //    con << pos << ',' << distance << ',' << eCoord::F( dirDrive, pos ) - distance << '\n';
2612 
2613     // request the right space ahead for wall extrapolation
2614     gPredictPositionData predictPositionData;
2615     PreparePredictPosition( predictPositionData );
2616 
2617     // drop current wall if it was requested
2618     if ( dropWallRequested_ )
2619     {
2620         // but don't do so too often globally (performance)
2621         static double nextDrop = 0;
2622         double time = tSysTimeFloat();
2623         if ( time >= nextDrop )
2624         {
2625             unsigned short idrec = ID();
2626             tRecorderSync< unsigned short >::Archive( "_PARTIAL_COPY_GRID", 8, idrec );
2627 
2628             nextDrop = time + sg_minDropInterval;
2629             if ( currentWall )
2630             {
2631                 currentWall->Update(lastTime,pos);
2632                 currentWall->PartialCopyIntoGrid( grid );
2633             }
2634             dropWallRequested_ = false;
2635         }
2636     }
2637 
2638 #ifdef GNUPLOT_DEBUG
2639     if ( sg_gnuplotDebug && Player() )
2640     {
2641         std::ofstream f( Player()->GetUserName() + "_step", std::ios::app );
2642         f << pos.x << " " << pos.y << "\n";
2643     }
2644 #endif
2645     // timewarp test debug code
2646     //if ( Player() && Player()->IsHuman() )
2647     //    currentTime -= .1;
2648 
2649     // don't timestep when you're dead
2650     if ( !Alive() )
2651     {
2652         // die completely
2653         Die( lastTime );
2654 
2655         // and let yourself be removed from the lists so we don't have to go
2656         // through this again.
2657         return true;
2658     }
2659 
2660     // Debug archive position and speed
2661     sg_ArchiveCoord( pos, 7 );
2662     sg_ArchiveReal( verletSpeed_, 7 );
2663 
2664     if ( !destinationList && sn_GetNetState() == nCLIENT )
2665     {
2666         // insert emergency first destination without broadcasting it
2667         gDestination* dest = tNEW(gDestination)(*this);
2668         dest->messageID = 0;
2669         dest->distance = -.001;
2670         dest->InsertIntoList(&destinationList);
2671     }
2672 
2673     // start new extrapolation
2674     if ( !extrapolator_ && resimulate_ )
2675         ResetExtrapolator();
2676 
2677     // extrapolate state from server and copy state when finished
2678     if ( extrapolator_ )
2679     {
2680         REAL dt = ( currentTime - lastTime ) * sg_syncFF / sg_syncFFSteps;
2681 #ifdef DEBUG
2682         // dt *= 10.101;
2683         //if ( !resimulate_ )
2684         //	dt *= .1;
2685 #endif
2686         for ( int i = sg_syncFFSteps - 1; i>= 0; --i )
2687         {
2688             if ( Extrapolate( dt ) )
2689             {
2690                 SyncFromExtrapolator();
2691                 break;
2692             }
2693         }
2694     }
2695 
2696     bool ret = false;
2697 
2698     // nothing special if simulating backwards
2699     if (currentTime < lastTime)
2700     {
2701         ret = gCycleMovement::Timestep(currentTime);
2702     }
2703     // no targets are given
2704     else if ( !currentDestination && pendingTurns.empty() )
2705     {
2706         // chatting? activate chatbot
2707         if ( bool(player) &&
2708                 player->IsHuman() &&
2709                 ( sg_chatBotAlwaysActive || player->IsChatting() ) &&
2710                 player->Owner() == sn_myNetID )
2711         {
2712             gCycleChatBot & bot = gCycleChatBot::Get( this );
2713             bot.Activate( currentTime );
2714         }
2715         else if ( chatBot_.get() )
2716         {
2717             chatBot_->nextChatAI_ = 0;
2718         }
2719 
2720         bool simulate=Alive();
2721 
2722         if ( !pendingTurns.empty() || currentDestination )
2723             simulate=true;
2724 
2725         if (simulate)
2726         {
2727             try
2728             {
2729                 ret = gCycleMovement::Timestep(currentTime);
2730             }
2731             catch ( gCycleDeath const & death )
2732             {
2733                 KillAt( death.pos_ );
2734                 return false;
2735             }
2736         }
2737         else
2738             ret = !Alive();
2739     }
2740     else
2741     {
2742         // just basic movement to do: let base class handle that.
2743         try
2744         {
2745             gCycleMovement::Timestep(currentTime);
2746         }
2747         catch ( gCycleDeath const & death )
2748         {
2749             KillAt( death.pos_ );
2750             return false;
2751         }
2752     }
2753 
2754     // do the rest of the timestep
2755     try
2756     {
2757         if ( currentTime > lastTime )
2758             ret = gCycleMovement::Timestep(currentTime);
2759     }
2760     catch ( gCycleDeath const & death )
2761     {
2762         KillAt( death.pos_ );
2763         return false;
2764     }
2765 
2766     REAL predictTime = CalculatePredictPosition( predictPositionData );
2767 
2768     if ( Alive() && currentWall )
2769     {
2770         // z-man: the next two lines are a very bad idea. This lets walls stick out on the other side while you're using up your rubber.
2771         //if ( sn_GetNetState() != nSERVER )
2772         //    wallEndPos = pos + dirDrive * ( verletSpeed_ * se_PredictTime() );
2773 
2774         // but using the predicted position which halts at walls may work
2775         currentWall->Update(predictTime, PredictPosition() );
2776     }
2777 
2778     // checkpoint wall when rubber starts to get used
2779     if ( currentWall )
2780     {
2781         if ( rubberSpeedFactor >= .99 && rubberSpeedFactorBack < .99 )
2782         {
2783             currentWall->Checkpoint();
2784         }
2785         else if ( rubberSpeedFactor < .99 && rubberSpeedFactorBack >= .99 )
2786         {
2787             currentWall->Checkpoint();
2788         }
2789         else if ( rubberSpeedFactor < .1 && rubberSpeedFactorBack >= .1 )
2790         {
2791             currentWall->Checkpoint();
2792         }
2793         else if ( rubberSpeedFactor < .01 && rubberSpeedFactorBack >= .01 )
2794         {
2795             currentWall->Checkpoint();
2796         }
2797     }
2798 
2799     if ( sn_GetNetState()==nSERVER )
2800     {
2801         // do an emergency sync when rubber starts to get used, it may come unexpected to clients
2802         if ( rubberSpeedFactor < .99 && rubberSpeedFactorBack >= .99 )
2803         {
2804             RequestSyncOwner();
2805         }
2806     }
2807 
2808     // check whether simulation has fallen too far behind the requested time
2809 #ifdef DEDICATED
2810     if ( Alive() && currentTime > lastTime + Lag() + 1 )
2811     {
2812         sn_ConsoleOut( "0xff7777Admin : 0xffff77BUG had to kill a cycle because it lagged behind in the simulation. Probably the invulnerability bug. Investigate!\n" );
2813         st_Breakpoint();
2814         KillAt( pos );
2815         ret = false;
2816     }
2817 #endif
2818 
2819     return ret;
2820 }
2821 
2822 // lets a value decay smoothly
DecaySmooth(REAL & smooth,REAL relSpeed,REAL minSpeed,REAL clamp)2823 static void DecaySmooth( REAL& smooth, REAL relSpeed, REAL minSpeed, REAL clamp )
2824 {
2825 #ifdef DEBUG
2826     if ( fabs(smooth) > .01 )
2827     {
2828         int x;
2829         x = 1;
2830     }
2831 #endif
2832 
2833     // increase correction speed if the value is out of bounds
2834     if ( clamp > 0 )
2835         relSpeed *= ( 1 + smooth * smooth / ( clamp * clamp ) );
2836 
2837     // nothing to do
2838     if ( smooth == 0 )
2839     {
2840         return;
2841     }
2842 
2843     // calculate speed from relative speed
2844     REAL speed = smooth * relSpeed;
2845 
2846     // apply minimal correction
2847     if ( fabs( speed ) < minSpeed )
2848         speed = copysign ( minSpeed , smooth );
2849 
2850     // don't overshoot
2851     if ( fabs( speed ) > fabs( smooth ) )
2852         smooth = 0;
2853     else
2854         smooth -= speed;
2855 }
2856 
2857 // clamps a cycle displacement to non-confusing values
ClampDisplacement(gCycle * cycle,eCoord & displacement,const eCoord & lookout,const eCoord & pos)2858 static REAL ClampDisplacement( gCycle* cycle, eCoord& displacement, const eCoord& lookout, const eCoord& pos )
2859 {
2860     gSensor sensor( cycle, pos, lookout );
2861     sensor.detect(1);
2862     if ( sensor.ehit && sensor.hit >= 0 && sensor.hit < 1 )
2863     {
2864 #ifdef DEBUG_X
2865         // repeat sensor
2866         gSensor sensor( cycle, pos, lookout );
2867         sensor.detect(1);
2868 #endif
2869         displacement = displacement * sensor.hit;
2870     }
2871     return sensor.hit;
2872 }
2873 
2874 // from gCycleMovement.cpp
2875 REAL sg_GetSparksDistance();
2876 
2877 
TimestepCore(REAL currentTime,bool calculateAcceleration)2878 bool gCycle::TimestepCore(REAL currentTime, bool calculateAcceleration ){
2879     if (!finite(skew))
2880         skew=0;
2881     if (!finite(skewDot))
2882         skewDot=0;
2883 
2884     // eCoord oldpos=pos;
2885 
2886     REAL ts=(currentTime-lastTime);
2887 
2888     clamp(ts, -10, 10);
2889     //clamp(correctTimeSmooth, -100, 100);
2890     clamp(correctDistanceSmooth, -100, 100);
2891     //clamp(correctSpeedSmooth, -100, 100);
2892 
2893     // scale factor for smoothing. It is always used as
2894     // value += smooth * smooth_correction;
2895     // smooth_correction -= smooth * smooth_correction;
2896 
2897     REAL smooth = 0;
2898 
2899     if ( ts > 0 )
2900     {
2901         // go a bit of the way
2902         smooth = 1 - 1/( 1 + ts / sg_cycleSyncSmoothTime );
2903     }
2904 
2905     //if ( smooth > 0)
2906     //{
2907     //    REAL scd = correctDistanceSmooth * smooth;
2908     //    distance += scd;
2909     //    correctDistanceSmooth -= scd;
2910     // }
2911 
2912     // handle distance correction
2913     TransferPositionCorrectionToDistanceCorrection();
2914 
2915     // apply smooth position correction
2916     // smooth = .5f;
2917     //correctPosSmooth = eCoord(0,0);
2918 
2919     // correctPosSmooth = correctPosSmooth * ( 1 - smooth );
2920 
2921     //if ( 0 )
2922 
2923     REAL animts=currentTime-lastTimeAnim;
2924     if (animts<0 || !finite(animts))
2925         animts=0;
2926     else
2927         lastTimeAnim=currentTime;
2928 
2929     // handle decaying of smooth position correction
2930     {
2931         // let components of smooth position correction decay
2932         REAL minSpeed = sg_cycleSyncSmoothMinSpeed * Speed() * animts;
2933         REAL clamp    = sg_cycleSyncSmoothThreshold * Speed();
2934 
2935         DecaySmooth( correctPosSmooth.x, smooth, minSpeed, clamp );
2936         DecaySmooth( correctPosSmooth.y, smooth, minSpeed, clamp );
2937 
2938         // do sanity checks
2939         if ( correctPosSmooth.NormSquared() > EPS )
2940         {
2941             // cast ray to make sure corrected position lies on the right side of walls
2942             ClampDisplacement( this, correctPosSmooth, correctPosSmooth * 2, pos );
2943 
2944             // same for negative direction, players should see it when they are close to a wall
2945             ClampDisplacement( this, correctPosSmooth, -correctPosSmooth, pos );
2946 
2947             // cast another some rays into the future
2948             eCoord lookahead = dirDrive * ( 2 * correctPosSmooth.Norm() );
2949             ClampDisplacement( this, correctPosSmooth, lookahead, pos );
2950         }
2951     }
2952 
2953     if (animts>.2)
2954         animts=.2;
2955 
2956     rotate(rotationFrontWheel,2*verletSpeed_*animts/.43);
2957     rotate(rotationRearWheel,2*verletSpeed_*animts/.73);
2958 
2959     REAL sparksDistance = sg_GetSparksDistance();
2960     REAL extension = .25;
2961     if ( extension < sparksDistance )
2962         extension = sparksDistance;
2963 
2964     //    REAL step=speed*ts; // +.5*acceleration*ts*ts;
2965 
2966     // animate cycle direction
2967     {
2968         // move it a bit closer to dirDrive
2969         dir=dir+dirDrive*animts*verletSpeed_*3;
2970 
2971         // if it's too far away from dirDrive, clamp it
2972         eCoord dirDistance = dir - dirDrive;
2973         REAL dist = dirDistance.NormSquared();
2974         const REAL maxDist = .8;
2975         if (dirDistance.NormSquared() > maxDist)
2976             dir = dirDrive + dirDistance* (1/sqrt(dist/maxDist));
2977 
2978         // normalize
2979         dir=dir*(1/sqrt(dir.NormSquared()));
2980     }
2981 
2982     {
2983         eCoord oldPos = pos;
2984 
2985         if ( Alive() ){
2986             // delegate core work to base class
2987             try
2988             {
2989                 // start building wall
2990                 REAL startBuildWallAt = spawnTime_ + sg_cycleWallTime;
2991                 if ( !currentWall && currentTime > startBuildWallAt  )
2992                 {
2993                     // simulate right to the spot where the wall should begin
2994                     if ( currentTime < startBuildWallAt )
2995                         if ( gCycleMovement::TimestepCore( startBuildWallAt, calculateAcceleration ) )
2996                             return true;
2997                     calculateAcceleration = true;
2998 
2999                     // build the wall, modifying the spawn time to make sure it happens
3000                     REAL lastSpawn = spawnTime_;
3001                     spawnTime_ += -1E+20;
3002                     DropWall();
3003                     spawnTime_ = lastSpawn;
3004                     lastTurnPos_ = pos; // hack last turn position to generate good wall
3005                 }
3006 
3007                 // simulate rest of frame
3008                 if ( gCycleMovement::TimestepCore( currentTime, calculateAcceleration ) )
3009                     return true;
3010             }
3011             catch ( gCycleDeath const & death )
3012             {
3013                 KillAt( death.pos_ );
3014 
3015                 // death exceptions are precise; we can safely take over the position from it
3016                 oldPos = death.pos_;
3017             }
3018         }
3019 
3020         // die where you started
3021         if ( !Alive() )
3022         {
3023             MoveSafely(oldPos,currentTime,currentTime);
3024             Die( currentTime );
3025         }
3026     }
3027 
3028     // Debug archive position and speed
3029     sg_ArchiveCoord( pos, 7 );
3030     sg_ArchiveReal( verletSpeed_, 7 );
3031 
3032     if (Alive()){
3033 #ifndef DEDICATED
3034         // animate skew
3035         gSensor fl(this,pos,dirDrive.Turn(1,1));
3036         gSensor fr(this,pos,dirDrive.Turn(1,-1));
3037 
3038         fl.detect(extension*4);
3039         fr.detect(extension*4);
3040 
3041         if (fl.hit > extension)
3042             fl.hit = extension;
3043 
3044         if (fr.hit > extension)
3045             fr.hit = extension;
3046 
3047         REAL lr=(fl.hit-fr.hit)/extension;
3048 
3049         // ODE for skew
3050         const REAL skewrelax=24;
3051         skewDot-=128*(skew+lr/2)*animts;
3052         skewDot/=1+skewrelax*animts;
3053         if (skew*skewDot>4) skewDot=4/skew;
3054         skew+=skewDot*animts;
3055 
3056         REAL fac = 0.5f;
3057         if ( skew > fr.hit * fac )
3058         {
3059             skew = fr.hit * fac;
3060         }
3061         if ( skew < -fl.hit * fac )
3062         {
3063             skew = -fl.hit * fac;
3064         }
3065 
3066         // generate sparks
3067         eCoord sparkpos,sparkdir;
3068 
3069         if (fl.ehit && fl.hit<=sparksDistance){
3070             sparkpos=pos+dirDrive.Turn(1,1)*fl.hit;
3071             sparkdir=dirDrive.Turn(0,-1);
3072         }
3073         if (fr.ehit && fr.hit<=sparksDistance){
3074             sparkpos=pos+dirDrive.Turn(1,-1)*fr.hit;
3075             sparkdir=dirDrive.Turn(0,1);
3076         }
3077 
3078         if (fabs(skew)<fabs(lr*.8) ){
3079             skewDot-=lr*1000*animts;
3080             if (crash_sparks && animts>0)
3081             {
3082                 gPlayerWall *tmpplayerWall=0;
3083 
3084                 if(fl.ehit) tmpplayerWall=dynamic_cast<gPlayerWall*>(fl.ehit->GetWall());
3085                 if(fr.ehit) tmpplayerWall=dynamic_cast<gPlayerWall*>(fr.ehit->GetWall());
3086 
3087                 if(tmpplayerWall) {
3088                     gCycle *tmpcycle = tmpplayerWall->Cycle();
3089 
3090                     if( tmpcycle )
3091                         new gSpark(grid, sparkpos-dirDrive*.1,sparkdir,currentTime,color_.r,color_.g,color_.b,tmpcycle->color_.r,tmpcycle->color_.g,tmpcycle->color_.b);
3092                 }
3093                 else
3094                     new gSpark(grid, sparkpos-dirDrive*.1,sparkdir,currentTime,color_.r,color_.g,color_.b,1,1,1);
3095 
3096                 if ( spark )
3097                     spark->Reset();
3098             }
3099         }
3100 
3101         if (fabs(skew)<fabs(lr*.9) ){
3102             skewDot-=lr*100*animts;
3103             if (crash_sparks && animts>0)
3104             {
3105                 gPlayerWall *tmpplayerWall=0;
3106 
3107                 if(fl.ehit) tmpplayerWall=dynamic_cast<gPlayerWall*>(fl.ehit->GetWall());
3108                 if(fr.ehit) tmpplayerWall=dynamic_cast<gPlayerWall*>(fr.ehit->GetWall());
3109 
3110                 if(tmpplayerWall) {
3111                     gCycle *tmpcycle = tmpplayerWall->Cycle();
3112 
3113                     if( tmpcycle )
3114                         new gSpark(grid, sparkpos-dirDrive*.1,sparkdir,currentTime,color_.r,color_.g,color_.b,tmpcycle->color_.r,tmpcycle->color_.g,tmpcycle->color_.b);
3115                 }
3116                 else
3117                     new gSpark(grid, sparkpos-dirDrive*.1,sparkdir,currentTime,color_.r,color_.g,color_.b,1,1,1);
3118             }
3119         }
3120 #endif
3121         /*
3122           if (fl.hit+fr.hit<extension*.4)
3123           Kill();
3124         */
3125     }
3126 
3127     // clamp skew
3128     if (skew>.5){
3129         skew=.5;
3130         skewDot=0;
3131     }
3132 
3133     if (skew<-.5){
3134         skew=-.5;
3135         skewDot=0;
3136     }
3137 
3138     if ( sn_GetNetState()==nSERVER )
3139     {
3140         if (nextSync < tSysTimeFloat() )
3141         {
3142             // delay syncs for old clients when there is a wall ahead; they would tunnel locally
3143             REAL lookahead = Speed() * sg_syncIntervalEnemy*.5;
3144             if ( !sg_avoidBadOldClientSync || sg_NoLocalTunnelOnSync.Supported( Owner() ) || GetMaxSpaceAhead( lookahead ) >= lookahead )
3145             {
3146                 RequestSync(false);
3147 
3148                 // checkpoint wall for better collision accuracy, only required on the server
3149                 if ( currentWall )
3150                     currentWall->Checkpoint();
3151             }
3152 
3153             nextSync=tSysTimeFloat()+sg_syncIntervalEnemy;
3154             nextSyncOwner=tSysTimeFloat()+sg_GetSyncIntervalSelf( this );
3155         }
3156         else if ( nextSyncOwner < tSysTimeFloat() &&
3157                   Owner() != 0 &&
3158                   sn_Connections[Owner()].bandwidthControl_.Control( nBandwidthControl::Usage_Planning ) > 200 )
3159         {
3160             // sync only to the owner (provided there is enough bandwidth available)
3161             RequestSyncOwner();
3162         }
3163     }
3164 
3165     return (!Alive());
3166 }
3167 
3168 
InteractWith(eGameObject * target,REAL,int)3169 void gCycle::InteractWith(eGameObject *target,REAL,int){
3170     /*
3171       if (alive && target->type()==ArmageTron_CYCLE){
3172          gCycle *c=(gCycle *)target;
3173          if (c->alive){
3174       const eEdge *current_eEdge=Edge();
3175       const eEdge *other_eEdge=c->Edge();
3176       if(current_eEdge && other_eEdge){
3177       ePoint *meet=current_eEdge->IntersectWith(other_eEdge);
3178       if (meet){ // somebody is going to die!
3179       REAL ratio=current_eEdge->Ratio(*meet);
3180       REAL time=CurrentWall()->Time(ratio);
3181       PassEdge(other_eEdge,time,other_eEdge->Ratio(*meet),0);
3182       }
3183       }
3184          }
3185       }
3186     */
3187 }
3188 
3189 // *******************************************************************************************
3190 // *
3191 // *	Die
3192 // *
3193 // *******************************************************************************************
3194 //!
3195 //!		@param	time	the time of death
3196 //!
3197 // *******************************************************************************************
3198 
Die(REAL time)3199 void gCycle::Die( REAL time )
3200 {
3201     if ( sn_GetNetState() == nSERVER )
3202     {
3203         // request one last sync
3204         RequestSync( true );
3205     }
3206 
3207     gCycleMovement::Die( time );
3208 
3209     // reset smoothing
3210     correctPosSmooth = eCoord();
3211     TransferPositionCorrectionToDistanceCorrection();
3212     predictPosition_ = pos;
3213 
3214     // delete all temporary walls of this cycle
3215     for ( int i = sg_netPlayerWalls.Len()-1; i >= 0; --i )
3216     {
3217         gNetPlayerWall * wall = sg_netPlayerWalls(i);
3218         if ( wall->Cycle() == this && wall->Preliminary() )
3219         {
3220             wall->real_CopyIntoGrid(grid);
3221         }
3222     }
3223 }
3224 
3225 static eLadderLogWriter sg_deathFragWriter("DEATH_FRAG", true);
3226 static eLadderLogWriter sg_deathSuicideWriter("DEATH_SUICIDE", true);
3227 static eLadderLogWriter sg_deathTeamkillWriter("DEATH_TEAMKILL", true);
3228 
KillAt(const eCoord & deathPos)3229 void gCycle::KillAt( const eCoord& deathPos){
3230     // don't kill invulnerable cycles
3231     if ( !Vulnerable() )
3232     {
3233         MoveSafely( deathPos, lastTime, lastTime );
3234         return;
3235     }
3236 
3237     // find the killer from the enemy influence storage
3238     ePlayerNetID const * constHunter = enemyInfluence.GetEnemy();
3239     ePlayerNetID * hunter = Player();
3240 
3241     // cast away const the safe way
3242     if ( constHunter && constHunter->Object() )
3243         hunter = constHunter->Object()->Player();
3244 
3245     // only take it if it is not too old
3246     if ( LastTime() - enemyInfluence.GetTime() > sg_suicideTimeout )
3247         hunter = NULL;
3248 
3249     // suicide?
3250     if ( !hunter )
3251         hunter = Player();
3252 
3253 
3254     if (!Alive() || sn_GetNetState()==nCLIENT)
3255         return;
3256 
3257 #ifdef KRAWALL_SERVER_LEAGUE
3258     if (    hunter           && Player()          &&
3259             !dynamic_cast<gAIPlayer*>(hunter)     &&
3260             !dynamic_cast<gAIPlayer*>(Player())             &&
3261             hunter->IsAuth() && Player()->IsAuth())
3262         nKrawall::ServerFrag(hunter->GetUserName(), Player()->GetUserName());
3263 #endif
3264 
3265     if (hunter==Player())
3266     {
3267         if (hunter)
3268         {
3269             sg_deathSuicideWriter << hunter->GetUserName();
3270             sg_deathSuicideWriter.write();
3271 
3272             if ( score_suicide )
3273                 hunter->AddScore(score_suicide, tOutput(), "$player_lose_suicide" );
3274             else
3275             {
3276                 tColoredString hunterName;
3277                 hunterName << *hunter << tColoredString::ColorString(1,1,1);
3278                 sn_ConsoleOut( tOutput( "$player_free_suicide", hunterName ) );
3279             }
3280         }
3281     }
3282     else{
3283         if (hunter)
3284         {
3285             tOutput lose;
3286             tOutput win;
3287             if (Player())
3288             {
3289                 tColoredString preyName;
3290                 preyName << *Player();
3291                 preyName << tColoredString::ColorString(1,1,1);
3292                 if (Player()->CurrentTeam() != hunter->CurrentTeam()) {
3293                     sg_deathFragWriter << Player()->GetUserName() << hunter->GetUserName();
3294                     sg_deathFragWriter.write();
3295 
3296                     win.SetTemplateParameter(3, preyName);
3297                     win << "$player_win_frag";
3298                     if ( score_kill != 0 )
3299                         hunter->AddScore(score_kill, win, lose );
3300                     else
3301                     {
3302                         tColoredString hunterName;
3303                         hunterName << *hunter << tColoredString::ColorString(1,1,1);
3304                         sn_ConsoleOut( tOutput( "$player_free_frag", hunterName, preyName ) );
3305                     }
3306                 }
3307                 else {
3308                     sg_deathTeamkillWriter << Player()->GetUserName() << hunter->GetUserName();
3309                     sg_deathTeamkillWriter.write();
3310 
3311                     tColoredString hunterName;
3312                     hunterName << *hunter << tColoredString::ColorString(1,1,1);
3313                     sn_ConsoleOut( tOutput( "$player_teamkill", hunterName, preyName ) );
3314                 }
3315             }
3316             else
3317             {
3318                 win << "$player_win_frag_ai";
3319                 hunter->AddScore(score_kill, win, lose);
3320             }
3321         }
3322         //	if (prey->player && (prey->player->CurrentTeam() != hunter->player->CurrentTeam()))
3323         if (Player())
3324             Player()->AddScore(score_die,tOutput(),"$player_lose_frag");
3325     }
3326 
3327     // set position to death position so the explosion is at the right place (I dimly remember this caused problems in an earlier attempt)
3328     this->pos = deathPos;
3329 
3330     Kill();
3331 }
3332 
3333 class gJustChecking
3334 {
3335 public:
3336     static bool justChecking;
3337 
gJustChecking()3338     gJustChecking(){ justChecking = false; }
~gJustChecking()3339     ~gJustChecking(){ justChecking = true; }
3340 };
3341 
3342 bool gJustChecking::justChecking = true;
3343 
EdgeIsDangerous(const eWall * ww,REAL time,REAL a) const3344 bool gCycle::EdgeIsDangerous(const eWall* ww, REAL time, REAL a) const{
3345     gPlayerWall const * w = dynamic_cast< gPlayerWall const * >( ww );
3346     if ( w )
3347     {
3348         if ( !Vulnerable() )
3349             return false;
3350 
3351         gNetPlayerWall *nw = w->NetWall();
3352 
3353         // check whether the wall is one of the walls no real collision is
3354         // possible with. The lastNetWall is only checked for enemy cycles,
3355         // because it can be an arbitrary wall for your own cycle. But for
3356         // enemy cycles, it may be what lastWall should be, and the test
3357         // prevents enemy cycles from getting stuck right after a turn.
3358         if( nw == currentWall || nw == lastWall || ( nw == lastNetWall && Owner() != sn_myNetID ) )
3359             return false;
3360 
3361         // see if the wall is from another player and from the future.
3362         // if it is, it's not dangerous, this cycle was here first.
3363         // on passing the wall later, the other cycle will be pushed back
3364         // to the collision point or killed if that is no longer possible.
3365         // z-man notes: I've got the vaque feeling something like this was
3366         // here before, but got thrown out again for making problems.
3367         if ( gJustChecking::justChecking && w->CycleMovement() != static_cast< const gCycleMovement * >( this ) && w->Time(a) > time )
3368         {
3369             // One problem with this is that you kill teammates if they can't
3370             // be pushed back, whereas we'd just use our rubber to survive.
3371             // So we should first check whether the other player should be
3372             // protected.
3373             gCycle const * otherPlayer = w->Cycle();
3374             if ( !otherPlayer || // valididy
3375                     otherPlayer->Team() != this->Team() || // team protection
3376                     !otherPlayer->currentWall || w == otherPlayer->currentWall->Wall() // pushback protection, if the other player can be pushed back, it's all right as well
3377                )
3378                 return false;
3379         }
3380     }
3381 
3382     return gCycleMovement::EdgeIsDangerous( ww, time, a );
3383 }
3384 
3385 // turn future walls of a cycle into gaping holes of nothingness if it indeed belongs to the cycle
sg_KillFutureWall(gCycle * cycle,gNetPlayerWall * wall)3386 static void sg_KillFutureWall( gCycle * cycle, gNetPlayerWall * wall )
3387 {
3388     if ( cycle && wall && wall->Cycle() == cycle && wall->Pos(1) > cycle->GetDistance() )
3389     {
3390         wall->BlowHole( cycle->GetDistance(), wall->Pos(1) + 100, 0 );
3391     }
3392 }
3393 
3394 // turn future walls of a cycle into gaping holes of nothingness
sg_KillFutureWalls(gCycle * cycle)3395 static void sg_KillFutureWalls( gCycle * cycle )
3396 {
3397 #ifdef DEBUG_X
3398     con << "Removing future walls of the cylce that just got killed mercilessly.\n";
3399 #endif
3400 
3401     // handle future walls that won't be drawn after all. Just make them a big hole.
3402     if ( sn_GetNetState() != nCLIENT )
3403     {
3404         int i;
3405         for ( i = sg_netPlayerWalls.Len()-1; i >= 0; --i )
3406             sg_KillFutureWall( cycle, sg_netPlayerWalls(i) );
3407 
3408         for ( i = sg_netPlayerWallsGridded.Len()-1; i >= 0; --i )
3409             sg_KillFutureWall( cycle, sg_netPlayerWallsGridded(i) );
3410     }
3411 }
3412 
sg_HoleScore(gCycle & cycle)3413 static void sg_HoleScore( gCycle & cycle )
3414 {
3415     cycle.Player()->AddScore( score_hole, tOutput("$player_win_hole"), tOutput("$player_lose_hole") );
3416 }
3417 
3418 static eLadderLogWriter sg_sacrificeWriter("SACRIFICE", true);
3419 
PassEdge(const eWall * ww,REAL time,REAL a,int)3420 void gCycle::PassEdge(const eWall *ww,REAL time,REAL a,int){
3421     {
3422         // deactivate time check
3423         gJustChecking thisIsSerious;
3424 
3425         if (!EdgeIsDangerous(ww,time,a) || !Alive() )
3426         {
3427             // request a sync for everyone if this is a non-bogus wall passage, maybe not all clients know the wall is passable
3428             if ( ( !currentWall || ww != currentWall->Wall() ) && ( !lastWall || ww != lastWall->Wall() ) )
3429                 RequestSyncAll();
3430 
3431             // check whether we drove through a hole in an enemy wall made by a teammate
3432             gPlayerWall const * w = dynamic_cast< gPlayerWall const * >( ww );
3433             if ( Alive() && w && score_hole )
3434             {
3435                 gExplosion * explosion = w->Holer( a, time );
3436                 if ( explosion )
3437                 {
3438                     gCycle * holer = explosion->GetOwner();
3439                     if ( holer && holer != this && holer->Player() &&
3440                          Player() &&
3441                          w->Cycle() && w->Cycle()->Player() &&
3442                          holer->Player()->CurrentTeam() == Player()->CurrentTeam() &&       // holer must have been a teammate
3443                          w->Cycle()->Player()->CurrentTeam() != Player()->CurrentTeam()  // wall must have been an enemy
3444                         )
3445                     {
3446                         // this test must come last, it resets the flag.
3447                         if ( explosion->AccountForHole() )
3448                         {
3449                             sg_sacrificeWriter << Player()->GetUserName() << holer->Player()->GetUserName() << w->Cycle()->Player()->GetUserName();
3450                             sg_sacrificeWriter.write();
3451                             if ( score_hole > 0 )
3452                             {
3453                                 // positive hole score goes to the holer
3454                                 sg_HoleScore( *holer );
3455                             }
3456                             else
3457                             {
3458                                 // negative hole score to the driver
3459                                 sg_HoleScore( *this );
3460 
3461                             }
3462                        }
3463                     }
3464                 }
3465             }
3466 
3467             return;
3468         }
3469 
3470 #ifdef DEBUG
3471         if (!EdgeIsDangerous(ww,time,a) || !Alive() )
3472             return;
3473 #endif
3474     }
3475 
3476     // request a sync to bring everyone up to date about the cycle passing/getting stuck on this wall
3477     RequestSyncOwner();
3478 
3479 #ifdef DEBUG_X
3480     // keep other cycle around
3481     tJUST_CONTROLLED_PTR<gCycleExtrapolator> keepOther( extrapolator_ );
3482 
3483     ResetExtrapolator();
3484 
3485     // extrapolate state from server and copy state when finished
3486     REAL dt = 1;
3487     for ( int i = 9; i>= 0; --i )
3488     {
3489         Extrapolate( dt );
3490     }
3491 
3492     extrapolator_ = keepOther;
3493 #endif
3494 
3495     eCoord collPos = ww->Point( a );
3496 
3497     const gPlayerWall *w = dynamic_cast<const gPlayerWall*>(ww);
3498 
3499     enemyInfluence.AddWall( ww, collPos, 0, this );
3500 
3501     if (w)
3502     {
3503         gCycle *otherPlayer=w->Cycle();
3504 
3505         REAL otherTime = w->Time(a);
3506         if(otherPlayer && time < otherTime*(1-EPS) )
3507         {
3508             // also send updates about the other cylce
3509             otherPlayer->RequestSyncOwner();
3510 
3511             // get the distance of the wall
3512             REAL wallDist = w->Pos(a);
3513             // get the distance the cycle is simulated up to
3514             REAL cycleDist = w->Cycle()->distance;
3515             // comparing these two gives an accurate criterion whether the wall is extrapolated
3516             if ( wallDist > cycleDist * (1 + EPS ) )
3517             {
3518                 static bool fix = false;
3519                 // it's an extrapolation wall, don't simulate further.
3520                 if ( fix && lastTime > se_GameTime() - 2 * Lag() - GetMaxLazyLag() )
3521                     throw gCycleStop();
3522                 else
3523                     return;
3524             }
3525 
3526             // we were first!
3527             if ( otherPlayer->Vulnerable() )
3528             {
3529                 static bool tryToSaveFutureWallOwner = true;
3530                 bool saved = false;
3531 
3532                 if ( tryToSaveFutureWallOwner && otherPlayer->currentWall && w == otherPlayer->currentWall->Wall() )
3533                 {
3534                     // teleport the other cycle back to the point before the collision; its next timestep
3535                     // will simulate the collision again from the right viewpoint
3536                     // determine the distance of the pushback
3537                     REAL d = otherPlayer->GetDistanceSinceLastTurn() * .001;
3538                     if ( d < .01 )
3539                         d = .01;
3540                     REAL maxd = eCoord::F( otherPlayer->dirDrive, collPos - otherPlayer->GetLastTurnPos() ) * .5/otherPlayer->dirDrive.NormSquared();
3541                     if ( d > maxd )
3542                         d = maxd;
3543                     if ( d > 0 )
3544                     {
3545                         saved = true;
3546 
3547                         // do the move
3548                         otherPlayer->MoveSafely( collPos-otherPlayer->dirDrive*d, otherPlayer->LastTime(), otherTime - d/otherPlayer->Speed() );
3549                         otherPlayer->currentWall->Update( otherPlayer->lastTime, otherPlayer->pos );
3550                         otherPlayer->dropWallRequested_ = false;
3551 
3552                         // drop our wall so collisions are more accurate
3553                         dropWallRequested_ = true;
3554                     }
3555                 }
3556 
3557                 // another possibility: if the walls are very short compared to rubber, we could
3558                 // get away with just accounding for some rubber on the cycle that we'd need to kill
3559                 // otherwise.
3560                 if ( !saved && verletSpeed_ >= 0 && this->ThisWallsLength() > 0 )
3561                 {
3562                     REAL dt = otherTime - time;
3563 
3564                     // this long would the other cycle have to sit in front of our wall
3565                     // before he's released by the end
3566                     REAL wallTimeLeft = this->ThisWallsLength()/verletSpeed_ - dt;
3567 
3568                     if ( wallTimeLeft < 0 )
3569                     {
3570                         // isn't hit at all
3571                         return;
3572                     }
3573 
3574                     // check how much rubber would be used
3575                     REAL max, effectiveness;
3576                     sg_RubberValues( otherPlayer->Player(), otherPlayer->verletSpeed_, max, effectiveness );
3577                     if ( effectiveness > 0 )
3578                     {
3579                         REAL rubberToEat = wallTimeLeft * otherPlayer->Speed()/effectiveness;
3580 
3581                         otherPlayer->rubber += rubberToEat;
3582                         if ( otherPlayer->rubber > max )
3583                             otherPlayer->rubber = max; // too much rubber used
3584                         else
3585                             saved = true;              // within bounds, he may survive
3586                     }
3587                 }
3588 
3589                 if ( !saved && sn_GetNetState() != nCLIENT )
3590                 {
3591                     // err, trouble. Can't push the other guy back far enough. Better kill him.
3592                     if ( currentWall )
3593                         otherPlayer->enemyInfluence.AddWall( currentWall->Wall(), lastTime, otherPlayer );
3594                     otherPlayer->distance = wallDist;
3595                     otherPlayer->DropWall();
3596                     otherPlayer->KillAt( collPos );
3597 
3598                     // get rid of future walls
3599                     sg_KillFutureWalls( otherPlayer );
3600                 }
3601             }
3602         }
3603         else // sad but true
3604         {
3605             // this cycle has to die here unless it has rubber left or is invulnerable (checked on catching the exception, and besides, this code path isn't called for invulnerable cycles)
3606             throw gCycleDeath( collPos );
3607 
3608             //			REAL dist = w->Pos( a );
3609             //			const_cast<gPlayerWall*>(w)->BlowHole( dist - explosionRadius, dist + explosionRadius );
3610         }
3611     }
3612     else
3613     {
3614         if (bool(player) && sn_GetNetState()!=nCLIENT && Alive() )
3615         {
3616             throw gCycleDeath( collPos );
3617         }
3618 
3619     }
3620 }
3621 
PathfindingModifier(const eWall * w) const3622 REAL gCycle::PathfindingModifier( const eWall *w ) const
3623 {
3624     if (!w)
3625         return 1;
3626     if (dynamic_cast<const gPlayerWall*>(w))
3627         return .9f;
3628     else
3629         return 1;
3630 }
3631 
3632 
Act(uActionPlayer * Act,REAL x)3633 bool gCycle::Act(uActionPlayer *Act, REAL x){
3634     // don't accept premature input
3635     if (se_mainGameTimer && ( se_mainGameTimer->speed <= 0 || se_mainGameTimer->Time() < -1 ) )
3636         return false;
3637 
3638     if (!Alive() && sn_GetNetState()==nSERVER)
3639         RequestSync(false);
3640 
3641     if(se_turnLeft==*Act && x>.5){
3642         //SendControl(lastTime,&se_turnLeft,1);
3643         Turn(-1);
3644         return true;
3645     }
3646     else if(se_turnRight==*Act && x>.5){
3647         //SendControl(lastTime,&se_turnRight,1);
3648         Turn(1);
3649         return true;
3650     }
3651     else if(s_brake==*Act){
3652         //SendControl(lastTime,&brake,x);
3653         unsigned short newBraking=(x>0);
3654         if ( braking != newBraking )
3655         {
3656             AccelerationDiscontinuity();
3657             braking = newBraking;
3658             AddDestination();
3659         }
3660         return true;
3661     }
3662     else if(s_brakeToggle==*Act){
3663         if ( x > 0 )
3664         {
3665             AccelerationDiscontinuity();
3666             braking = !braking;
3667             AddDestination();
3668         }
3669         return true;
3670     }
3671     return false;
3672 }
3673 
3674 // client side bugfix: network sync messages get actually used
3675 static nVersionFeature sg_SyncsAreUsed( 5 );
3676 
3677 // temporarily override driving directions on wall drops
3678 static eCoord const * sg_fakeDirDrive = NULL;
3679 class gFakeDirDriveSetter
3680 {
3681 public:
gFakeDirDriveSetter(eCoord const & dir)3682     gFakeDirDriveSetter( eCoord const & dir )
3683             : lastFakeDir_( sg_fakeDirDrive )
3684     {
3685         sg_fakeDirDrive = &dir;
3686     }
3687 
~gFakeDirDriveSetter()3688     ~gFakeDirDriveSetter()
3689     {
3690         sg_fakeDirDrive = lastFakeDir_;
3691     }
3692 private:
3693     eCoord const * lastFakeDir_;
3694 };
3695 
DoTurn(int d)3696 bool gCycle::DoTurn(int d)
3697 {
3698 #ifdef DELAYEDTURN_DEBUG
3699     REAL delay = tSysTimeFloat() - sg_turnReceivedTime;
3700     if ( delay > EPS && sn_GetNetState() == nSERVER && Owner() != 0 )
3701     {
3702         con << "Delayed turn execution! " << turns << "\n";
3703     }
3704 #endif
3705 
3706 #ifdef GNUPLOT_DEBUG
3707     if ( sg_gnuplotDebug && Player() )
3708     {
3709         std::ofstream f( Player()->GetUserName() + "_turn", std::ios::app );
3710         f << pos.x << " " << pos.y << "\n";
3711     }
3712 #endif
3713 
3714     if (d >  1) d =  1;
3715     if (d < -1) d = -1;
3716 
3717     if (Alive()){
3718         if ( turning )
3719             turning->Reset();
3720 
3721         clientside_action();
3722 
3723         if ( gCycleMovement::DoTurn( d ) )
3724         {
3725             sg_ArchiveCoord( pos, 1 );
3726 
3727             skewDot+=4*d;
3728 
3729             if (sn_GetNetState() == nCLIENT && Owner() == ::sn_myNetID)
3730                 AddDestination();
3731 
3732             if (sn_GetNetState()!=nCLIENT)
3733             {
3734                 RequestSync();
3735             }
3736 
3737             // hack: while dropping the wall, turn around dirDrive.
3738             // this makes FindCurrentFace work better.
3739             {
3740                 FindCurrentFace();
3741                 REAL factor = -16;
3742                 eCoord dirDriveFake = dirDrive * factor;
3743                 eCoord lastDirDriveBack = lastDirDrive;
3744                 lastDirDrive = lastDirDrive * factor;
3745                 gFakeDirDriveSetter fakeSetter( dirDriveFake );
3746                 DropWall();
3747                 lastDirDrive = lastDirDriveBack;
3748             }
3749 
3750             return true;
3751         }
3752     }
3753 
3754     return false;
3755 }
3756 
DropWall(bool buildNew)3757 void gCycle::DropWall( bool buildNew )
3758 {
3759     // keep this cycle alive
3760     tJUST_CONTROLLED_PTR< gCycle > keep( this->GetRefcount()>0 ? this : 0 );
3761 
3762     // drop last net wall if it is outdated
3763     if ( lastWall && lastNetWall && lastWall->Time(.5) > lastNetWall->Time(0) )
3764         lastNetWall = 0;
3765 
3766     // update and drop current wall
3767     if(currentWall)
3768     {
3769         lastWall=currentWall;
3770         currentWall->Update(lastTime,pos);
3771         currentWall->CopyIntoGrid( grid );
3772         currentWall=NULL;
3773     }
3774 
3775     if ( buildNew && lastTime >= spawnTime_ + sg_cycleWallTime )
3776         currentWall=new gNetPlayerWall(this,pos,dirDrive,lastTime,distance);
3777 
3778     // grid datastructures change on inserting a wall, better recheck
3779     // all game objects. Temporarily override this cycle's driving direction.
3780     eCoord dirDriveBack = dirDrive;
3781     if ( sg_fakeDirDrive )
3782         dirDrive = *sg_fakeDirDrive;
3783 
3784     if ( grid )
3785     {
3786         for(int i=grid->GameObjects().Len()-1;i>=0;i--)
3787         {
3788             eGameObject * c = grid->GameObjects()(i);
3789             if (c->CurrentFace() && !c->CurrentFace()->IsInGrid() )
3790                 c->FindCurrentFace();
3791         }
3792     }
3793     dirDrive = dirDriveBack;
3794 
3795     // reset flag
3796     dropWallRequested_ = false;
3797 }
3798 
Kill()3799 void gCycle::Kill(){
3800     // keep this cycle alive
3801     tJUST_CONTROLLED_PTR< gCycle > keep( this->GetRefcount()>0 ? this : 0 );
3802 
3803     if (sn_GetNetState()!=nCLIENT){
3804         RequestSync(true);
3805         if (Alive() && grid && GOID() >= 0 ){
3806             Die( lastTime );
3807             tNEW(gExplosion)(grid, pos,lastTime, color_, this );
3808             //	 eEdge::SeethroughHasChanged();
3809 
3810             if ( currentWall )
3811             {
3812                 // z-man: updating the wall so it reflects exactly the position of death looks like
3813                 // a good idea, but unfortunately, the collision position reported from above
3814                 // is inaccurate. It's better not to use it at all, or the cycle's wall will stick out
3815                 // a bit on the other side of the wall it crashed into.
3816 
3817                 // but if prediction was active, do it anyway
3818                 if ( currentWall->Pos(1) > distance || currentWall->Time(1) > lastTime )
3819                     currentWall->Update( lastTime, pos );
3820 
3821                 // copy the wall into the grid, but not directly; the grid datastructures are probably currently traversed. Kill() is called from eGameObject::Move().
3822                 currentWall->CopyIntoGrid( 0 );
3823 
3824                 currentWall = NULL;
3825             }
3826         }
3827     }
3828     // z-man: another stupid idea. Why would we need a destination when we're dead?
3829     //    else if (Owner() == ::sn_myNetID)
3830     //        AddDestination();
3831     /*
3832       else if (owner!=::sn_myNetID)
3833       speed=-.01;
3834     */
3835 }
3836 
3837 /*
3838 void gCycle::Turbo(bool turbo){
3839     if (turbo && speed<30){
3840         speed=40;
3841         Turn(0);
3842     }
3843 
3844     if (!turbo && speed>=30){
3845         speed=20;
3846         Turn(0);
3847     }
3848 }
3849 */
3850 
3851 static rFileTexture cycle_shad(rTextureGroups::TEX_FLOOR,"textures/shadow.png",0,0,true);
3852 /*
3853 static tString lala_cycle_shad("Anonymous/original/textures/shadow.png");
3854 static nSettingItem<tString> lalala_cycle_shad("TEXTURE_CYCLE_SHADOW", lala_cycle_shad);
3855 rFileTexture cycle_shad(rTextureGroups::TEX_FLOOR, lala_cycle_shad, 0,0,true);
3856 */
3857 
3858 #define ENABLE_OLD_LAG_O_METER
3859 
3860 REAL sg_laggometerScale=1;
3861 static tSettingItem< REAL > sg_laggometerScaleConf( "LAG_O_METER_SCALE", sg_laggometerScale );
3862 REAL sg_laggometerThreshold=.5;
3863 static tSettingItem< REAL > sg_laggometerThresholdConf( "LAG_O_METER_THRESHOLD", sg_laggometerThreshold );
3864 REAL sg_laggometerBlend=.5;
3865 static tSettingItem< REAL > sg_laggometerBlendConf( "LAG_O_METER_BLEND", sg_laggometerBlend );
3866 #ifdef ENABLE_OLD_LAG_O_METER
3867 bool sg_laggometerUseOld=false;
3868 static tSettingItem< bool > sg_laggometerUseOldConf( "LAG_O_METER_USE_OLD", sg_laggometerUseOld );
3869 #endif
3870 bool sg_axesIndicator=false;
3871 
3872 int sg_blinkFrequency=10;
3873 static tSettingItem< int > sg_blinkFrequencyConf( "CYCLE_BLINK_FREQUENCY", sg_blinkFrequency );
3874 
3875 #ifndef DEDICATED
3876 // put meriton's classes into a namespace so they can't possibly conflict with other code, especially the Colour class. --wrtl
3877 namespace gLaggometer {
3878 class Colour {
3879 public:
3880     REAL cp[3];
Colour(REAL r,REAL g,REAL b)3881     Colour(REAL r, REAL g, REAL b) {
3882         cp[0]=r;
3883         cp[1]=g;
3884         cp[2]=b;
3885     }
Colour(ePlayerNetID * player)3886     Colour(ePlayerNetID* player) {
3887         if ( player )
3888         {
3889             player->Color(cp[0], cp[1], cp[2]);
3890         }
3891         else
3892         {
3893             cp[0]=cp[1]=cp[2]=1;
3894         }
3895     }
blend(REAL factor,const Colour & target)3896     void blend(REAL factor, const Colour& target) {
3897         for (int i=0; i<3; i++) {
3898             cp[i] = (1 - factor) * cp[i] + factor * target.cp[i];
3899         }
3900     }
toGl() const3901     void toGl() const { glColor3f(cp[0], cp[1], cp[2]); }
3902 
3903     static const Colour white;
3904     static const Colour black;
3905 };
3906 
3907 const Colour Colour::white(1,1,1);
3908 const Colour Colour::black(0,0,0);
3909 
3910 class DirectionTransformer {
3911 private:
3912     eGrid* grid;
3913     eCoord factor;
3914 public:
DirectionTransformer(eGrid * theGrid)3915     DirectionTransformer(eGrid* theGrid) : grid(theGrid), factor(theGrid->GetDirection(0).Conj()) { }
get(int i)3916     eCoord get(int i) {
3917         return grid->GetDirection(i).Turn(factor);
3918 
3919     }
ahead()3920     int ahead() { return 2 * (grid->WindingNumber()); }
3921 };
3922 
3923 class LagOMeterRenderer {
3924 private:
3925     DirectionTransformer directions;
3926     REAL delay;
3927     Colour color;
3928 protected:
3929     bool drawTriangle(eCoord loc, int winding, REAL lag, int inc);
3930 public:
LagOMeterRenderer(gCycle * cycle)3931     LagOMeterRenderer(gCycle* cycle) :
3932             directions(cycle->Grid()),
3933             delay(cycle->GetTurnDelay()),
3934             color(cycle->Player())
3935     {
3936         color.blend(sg_laggometerBlend, Colour::white);
3937     }
3938     void render(REAL lag);
3939 };
3940 
3941 //! returns whether the sprial intersects its counterpart
drawTriangle(eCoord loc,int winding,REAL lag,int inc)3942 bool LagOMeterRenderer::drawTriangle(eCoord loc, int winding, REAL lag, int inc) {
3943     eCoord outer = loc + directions.get(winding) * lag;
3944     if (outer.y * inc > 0.01f) {
3945         eCoord oldOuter = loc + directions.get(winding - inc) * lag;
3946         eCoord d = outer - oldOuter;
3947         outer = oldOuter + d * (-oldOuter.y / d.y);
3948         glVertex2f(outer.x, outer.y);
3949         return true;
3950     } else {
3951         glVertex2f(outer.x, outer.y);
3952         if (lag > delay) {
3953             if (drawTriangle(loc + directions.get(winding + inc) * delay, winding + inc, lag - delay, inc)) return true;
3954         } else {
3955             outer = loc + directions.get(winding + inc) * lag;
3956             glVertex2f(outer.x, outer.y);
3957         }
3958         glVertex2f(loc.x, loc.y);
3959         return false;
3960     }
3961 }
3962 
render(REAL lag)3963 void LagOMeterRenderer::render(REAL lag) {
3964     color.toGl();
3965     BeginLineStrip();
3966     drawTriangle(eCoord(0,0), directions.ahead(), lag, 1);
3967     RenderEnd();
3968 
3969     BeginLineStrip();
3970     drawTriangle(eCoord(0,0), directions.ahead(), lag, -1);
3971     RenderEnd();
3972 }
3973 
3974 
3975 class AxesIndicator {
3976 private:
3977     DirectionTransformer directions;
3978     Colour color;
3979 public:
AxesIndicator(gCycle * cycle)3980     AxesIndicator(gCycle* cycle) :
3981             directions(cycle->Grid()),
3982             color(cycle->Player())
3983     {
3984         color.blend(.5f, Colour::white);
3985     }
line(int i)3986     void line(int i) {
3987         eCoord midle = directions.get(directions.ahead() + i) * .1f;
3988         eCoord inner = midle * .5f;
3989         eCoord outer = midle + directions.get(directions.ahead() + 2 * i) * .05f;
3990 
3991         BeginLineStrip();
3992         //Colour::black.toGl();
3993         color.toGl();
3994         glVertex2f(inner.x, inner.y);
3995 
3996 
3997         glVertex2f(midle.x, midle.y);
3998 
3999         Colour::black.toGl();
4000         glVertex2f(outer.x, outer.y);
4001         RenderEnd();
4002     }
render()4003     void render() {
4004         //return; // disable, for now
4005         glShadeModel(GL_SMOOTH);
4006         line(-1);
4007         line(0);
4008         line(1);
4009     }
4010 };
4011 
4012 }
4013 
4014 static REAL mp_eWall_stretch=4;
4015 static tSettingItem<REAL> mpws
4016 ("MOVIEPACK_WALL_STRETCH",mp_eWall_stretch);
4017 
4018 static rFileTexture dir_eWall(rTextureGroups::TEX_WALL,"textures/dir_wall.png",1,0,1);
4019 static rFileTexture dir_eWall_moviepack(rTextureGroups::TEX_WALL,"moviepack/dir_wall.png",1,0,1);
4020 
dir_eWall_select()4021 static void dir_eWall_select()
4022 {
4023     if (sg_MoviePack()){
4024         TexMatrix();
4025         IdentityMatrix();
4026         ScaleMatrix(1/mp_eWall_stretch,1,1);
4027         dir_eWall_moviepack.Select();
4028     }
4029     else
4030     {
4031         dir_eWall.Select();
4032     }
4033 }
4034 
gCycleWallsDisplayListManager()4035 gCycleWallsDisplayListManager::gCycleWallsDisplayListManager()
4036     : wallList_(0)
4037     , wallsWithDisplayList_(0)
4038     , wallsWithDisplayListMinDistance_(0)
4039     , wallsInDisplayList_(0)
4040 {
4041 }
4042 
CannotHaveList(REAL distance,gCycle const * cycle)4043 bool gCycleWallsDisplayListManager::CannotHaveList( REAL distance, gCycle const * cycle )
4044 {
4045     return
4046             ( !cycle->Alive() && gCycle::WallsStayUpDelay() >= 0 && se_GameTime()-cycle->DeathTime()-gCycle::WallsStayUpDelay() > 0 )
4047 
4048             ||
4049 
4050             ( cycle->ThisWallsLength() > 0 && cycle->GetDistance() - cycle->ThisWallsLength() > distance );
4051 }
4052 
RenderAll(eCamera const * camera,gCycle * cycle)4053 void gCycleWallsDisplayListManager::RenderAll( eCamera const * camera, gCycle * cycle )
4054 {
4055     dir_eWall_select();
4056 
4057     glDisable(GL_CULL_FACE);
4058 
4059     gNetPlayerWall * run = 0;
4060     // transfer walls with display list into their list
4061 
4062     int wallsWithPossibleDisplayList = 0;
4063     run = wallList_;
4064     while( run )
4065     {
4066         gNetPlayerWall * next = run->Next();
4067         if ( run->CanHaveDisplayList() )
4068         {
4069             wallsWithPossibleDisplayList++;
4070         }
4071         else
4072         {
4073             // wall has expired, remove it
4074             if ( cycle->ThisWallsLength() > 0 && cycle->GetDistance() - cycle->MaxWallsLength() > run->EndPos() )
4075 
4076             {
4077                 run->Remove();
4078             }
4079             else
4080             {
4081                 run->Render( camera );
4082             }
4083         }
4084         run = next;
4085     }
4086 
4087     // clear display list if needed
4088     bool tailExpired=false;
4089     if ( CannotHaveList( wallsWithDisplayListMinDistance_, cycle ) )
4090     {
4091         tailExpired=true;
4092         displayList_.Clear(0);
4093     }
4094     // check if enough new walls are present to warrant altering the display list
4095     else if ( wallsWithPossibleDisplayList >= 3 ||
4096          wallsWithPossibleDisplayList * 5 > wallsInDisplayList_ )
4097     {
4098         // yes? Ok, rebuild the list in this case, too
4099         displayList_.Clear(0);
4100     }
4101     else if ( wallsWithPossibleDisplayList )
4102     {
4103         // oops, at least render the newcomers normally
4104         run = wallList_;
4105         while( run )
4106         {
4107             gNetPlayerWall * next = run->Next();
4108             if ( run->CanHaveDisplayList() )
4109             {
4110                 run->Render( camera );
4111             }
4112 
4113             run = next;
4114         }
4115     }
4116 
4117     // call display list
4118     if ( displayList_.Call() )
4119     {
4120         return;
4121     }
4122 
4123     // remove and render walls without display list
4124     run = wallsWithDisplayList_;
4125     while( run )
4126     {
4127         gNetPlayerWall * next = run->Next();
4128         if ( !run->CanHaveDisplayList() || ( tailExpired && wallsWithDisplayListMinDistance_ >= run->BegPos() ) )
4129         {
4130             run->Render( camera );
4131             run->Insert( wallList_ );
4132         }
4133         run = next;
4134     }
4135 
4136     if ( wallsWithPossibleDisplayList > 0 )
4137     {
4138         run = wallList_;
4139         while( run )
4140         {
4141             gNetPlayerWall * next = run->Next();
4142             if ( run->CanHaveDisplayList() )
4143             {
4144                 run->Insert( wallsWithDisplayList_ );
4145 
4146                 // clear the wall's own display list, it will no longer be needed
4147                 run->ClearDisplayList(0, -1);
4148             }
4149 
4150             run = next;
4151         }
4152     }
4153 
4154     if ( !wallsWithDisplayList_ )
4155     {
4156         return;
4157     }
4158 
4159     // fill display list
4160     rDisplayListFiller filler( displayList_ );
4161 
4162     if ( !rDisplayList::IsRecording() )
4163     {
4164         // display list recording did not start; render traditionally
4165         run = wallsWithDisplayList_;
4166         while( run )
4167         {
4168             gNetPlayerWall * next = run->Next();
4169             run->Render( camera );
4170             run = next;
4171         }
4172 
4173         return;
4174     }
4175 
4176     wallsWithDisplayListMinDistance_ = 1E+30;
4177     wallsInDisplayList_ = 0;
4178 
4179     // render walls;
4180     // first, render all lines
4181     sr_DepthOffset(true);
4182     if ( rTextureGroups::TextureMode[rTextureGroups::TEX_WALL] != 0 )
4183         glDisable(GL_TEXTURE_2D);
4184 
4185     run = wallsWithDisplayList_;
4186     while( run )
4187     {
4188         gNetPlayerWall * next = run->Next();
4189         if ( run->BegPos() < wallsWithDisplayListMinDistance_ )
4190         {
4191             wallsWithDisplayListMinDistance_ = run->BegPos();
4192         }
4193 
4194         wallsInDisplayList_++;
4195 
4196         run->RenderList( true, gNetPlayerWall::gWallRenderMode_Lines );
4197         run = next;
4198     }
4199 
4200     RenderEnd();
4201     sr_DepthOffset(false);
4202     if ( rTextureGroups::TextureMode[rTextureGroups::TEX_WALL] != 0 )
4203         glEnable(GL_TEXTURE_2D);
4204 
4205     run = wallsWithDisplayList_;
4206     while( run )
4207     {
4208         gNetPlayerWall * next = run->Next();
4209         run->RenderList( true, gNetPlayerWall::gWallRenderMode_Quads );
4210         run = next;
4211     }
4212 
4213     RenderEnd();
4214 }
4215 
Render(const eCamera * cam)4216 void gCycle::Render(const eCamera *cam){
4217     /*
4218     // for use when there's rendering problems on one specific occasion
4219     static int counter = 0;
4220     ++ counter;
4221     if ( counter == -1 )
4222     {
4223         st_Breakpoint();
4224     }
4225     */
4226 
4227     // are we blinking from invulnerability?
4228     bool blinking = false;
4229     if ( lastTime > spawnTime_ && !Vulnerable() )
4230     {
4231         double time = tSysTimeFloat();
4232         double wrap = time - floor(time);
4233         int pulse = int ( 2 * wrap * sg_blinkFrequency );
4234         blinking = pulse & 1;
4235     }
4236 
4237 #ifdef USE_HEADLIGHT
4238 #ifdef LINUX
4239     typedef void (*glProgramStringARB_Func)(GLenum, GLenum, GLsizei, const void*);
4240     glProgramStringARB_Func glProgramStringARB_ptr = 0;
4241 
4242     typedef void (*glProgramLocalParameter4fARB_Func)(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
4243     glProgramLocalParameter4fARB_Func glProgramLocalParameter4fARB_ptr = 0;
4244 
4245     glProgramStringARB_ptr = (glProgramStringARB_Func) SDL_GL_GetProcAddress("glProgramStringARB");
4246     glProgramLocalParameter4fARB_ptr = (glProgramLocalParameter4fARB_Func) SDL_GL_GetProcAddress("glProgramLocalParameter4fARB");
4247 #endif
4248 #endif
4249     if (!finite(z) || !finite(pos.x) ||!finite(pos.y)||!finite(dir.x)||!finite(dir.y)
4250             || !finite(skew))
4251         st_Breakpoint();
4252     if (Alive()){
4253         //con << "Drawing cycle at " << pos << '\n';
4254 
4255 #ifdef DEBUG
4256         /*     {
4257         	   gDestination *l = destinationList;
4258         	   glDisable(GL_LIGHTING);
4259         	   glColor3f(1,1,1);
4260         	   while(l){
4261         	   if (l == currentDestination)
4262         	   glColor3f(0,1,0);
4263 
4264         	   glBegin(GL_LINES);
4265         	   glVertex3f(l->position.x, l->position.y, 0);
4266         	   glVertex3f(l->position.x, l->position.y, 100);
4267         	   glEnd();
4268 
4269         	   if (l == currentDestination)
4270         	   glColor3f(0,1,1);
4271 
4272         	   l=l->next;
4273         	   }
4274         	   } */
4275 #endif
4276 
4277         GLfloat color[4]={1,1,1,1};
4278         static GLfloat lposa[4] = { 320, 240, 200,0};
4279         static GLfloat lposb[4] = { -240, -100, 200,0};
4280         static GLfloat lighta[4] = { 1, .7, .7, 1 };
4281         static GLfloat lightb[4] = { .7, .7, 1, 1 };
4282 
4283         glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,color);
4284         glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,color);
4285 
4286         glLightfv(GL_LIGHT0, GL_DIFFUSE, lighta);
4287         glLightfv(GL_LIGHT0, GL_SPECULAR, lighta);
4288         glLightfv(GL_LIGHT0, GL_POSITION, lposa);
4289         glLightfv(GL_LIGHT1, GL_DIFFUSE, lightb);
4290         glLightfv(GL_LIGHT1, GL_SPECULAR, lightb);
4291         glLightfv(GL_LIGHT1, GL_POSITION, lposb);
4292 
4293 
4294         ModelMatrix();
4295         glPushMatrix();
4296         eCoord p = PredictPosition();
4297         glTranslatef(p.x,p.y,0);
4298         glScalef(.5f,.5f,.5f);
4299 
4300 
4301         eCoord ske(1,skew);
4302         ske=ske*(1/sqrt(ske.NormSquared()));
4303 
4304         GLfloat m[4][4]={{dir.x,dir.y,0,0},
4305                          {-dir.y,dir.x,0,0},
4306                          {0,0,1,0},
4307                          {0,0,0,1}};
4308         glMultMatrixf(&m[0][0]);
4309 
4310         glPushMatrix();
4311         //glTranslatef(-1.84,0,0);
4312         if (!mp)
4313             glTranslatef(-1.5,0,0);
4314 
4315         glPushMatrix();
4316 
4317         GLfloat sk[4][4]={{1,0,0,0},
4318                           {0,ske.x,ske.y,0},
4319                           {0,-ske.y,ske.x,0},
4320                           {0,0,0,1}};
4321 
4322         glMultMatrixf(&sk[0][0]);
4323 
4324 
4325         glEnable(GL_LIGHT0);
4326         glEnable(GL_LIGHT1);
4327         glEnable(GL_LIGHTING);
4328 
4329 
4330 
4331         TexMatrix();
4332         IdentityMatrix();
4333 
4334         if (mp){
4335 
4336             ModelMatrix();
4337             if ( !blinking )
4338             {
4339                 glPushMatrix();
4340                 customTexture->Select();
4341                 glColor3f(1,1,1);
4342                 customModel->Render();
4343                 glPopMatrix();
4344             }
4345 
4346             glPopMatrix();
4347             glTranslatef(-1.5,0,0);
4348         }
4349         else{
4350             glEnable(GL_TEXTURE_2D);
4351 
4352             ModelMatrix();
4353 
4354             if ( !blinking )
4355             {
4356                 bodyTex->Select();
4357                 body->Render();
4358 
4359                 wheelTex->Select();
4360 
4361                 glPushMatrix();
4362                 glTranslatef(0,0,.73);
4363 
4364                 GLfloat mr[4][4]={{rotationRearWheel.x,0,rotationRearWheel.y,0},
4365                                   {0,1,0,0},
4366                                   {-rotationRearWheel.y,0,rotationRearWheel.x,0},
4367                                   {0,0,0,1}};
4368 
4369 
4370                 glMultMatrixf(&mr[0][0]);
4371 
4372                 rear->Render();
4373                 glPopMatrix();
4374 
4375                 glPushMatrix();
4376                 glTranslatef(1.84,0,.43);
4377 
4378                 GLfloat mf[4][4]={{rotationFrontWheel.x,0,rotationFrontWheel.y,0},
4379                                   {0,1,0,0},
4380                                   {-rotationFrontWheel.y,0,rotationFrontWheel.x,0},
4381                                   {0,0,0,1}};
4382 
4383                 glMultMatrixf(&mf[0][0]);
4384 
4385                 front->Render();
4386                 glPopMatrix();
4387             }
4388 
4389             glPopMatrix();
4390         }
4391 
4392 
4393         //     TexMatrix();
4394         //     IdentityMatrix();
4395         ModelMatrix();
4396 
4397         /*
4398           glDisable(GL_TEXTURE_GEN_S);
4399           glDisable(GL_TEXTURE_GEN_T);
4400           glDisable(GL_TEXTURE_GEN_Q);
4401           glDisable(GL_TEXTURE_GEN_R);
4402         */
4403 
4404         glDisable(GL_LIGHT0);
4405         glDisable(GL_LIGHT1);
4406         glDisable(GL_LIGHTING);
4407 
4408         //glDisable(GL_TEXTURE);
4409         glDisable(GL_TEXTURE_2D);
4410         glColor3f(1,1,1);
4411 
4412         {
4413             bool renderPyramid = false;
4414             gRealColor colorPyramid;
4415             REAL alpha = 1;
4416             const REAL timeout = .5f;
4417 
4418             if ( bool(player) )
4419             {
4420                 if ( player->IsChatting() )
4421                 {
4422                     renderPyramid = true;
4423                     colorPyramid.b = 0.0f;
4424                 }
4425                 else if ( !player->IsActive() )
4426                 {
4427                     renderPyramid = true;
4428                     colorPyramid.b = 0.0f;
4429                     colorPyramid.g = 0.0f;
4430                 }
4431                 else if ( cam && cam->Center() == this && se_GameTime() < timeout && player->CurrentTeam() && player->CurrentTeam()->NumPlayers() > 1 )
4432                 {
4433                     renderPyramid = true;
4434                     alpha = timeout - se_GameTime();
4435                 }
4436             }
4437 
4438             if ( renderPyramid )
4439             {
4440                 GLfloat s=sin(lastTime);
4441                 GLfloat c=cos(lastTime);
4442 
4443                 GLfloat m[4][4]={{c,s,0,0},
4444                                  {-s,c,0,0},
4445                                  {0,0,1,0},
4446                                  {0,0,1,1}};
4447 
4448                 glPushMatrix();
4449 
4450                 glMultMatrixf(&m[0][0]);
4451                 glScalef(.5,.5,.5);
4452 
4453 
4454                 BeginTriangles();
4455 
4456                 glColor4f( colorPyramid.r,colorPyramid.g,colorPyramid.b, alpha );
4457                 glVertex3f(0,0,3);
4458                 glVertex3f(0,1,4.5);
4459                 glVertex3f(0,-1,4.5);
4460 
4461                 glColor4f( colorPyramid.r * .7f,colorPyramid.g * .7f,colorPyramid.b * .7f, alpha );
4462                 glVertex3f(0,0,3);
4463                 glVertex3f(1,0,4.5);
4464                 glVertex3f(-1,0,4.5);
4465 
4466                 RenderEnd();
4467 
4468                 glPopMatrix();
4469             }
4470         }
4471 
4472 #ifdef USE_HEADLIGHT
4473         // Headlight contributed by Jonathan
4474         if(headlights) {
4475             if(!cycleprograminited) { // set to false on every sr_InitDisplay, without it I lost my program when I switched to windowed
4476                 const char *program =
4477                     "!!ARBfp1.0\
4478                     \
4479                     PARAM normal = program.local[0];\
4480                     ATTRIB texcoord = fragment.texcoord;\
4481                     TEMP final, diffuse, distance;\
4482                     \
4483                     DP3 distance, texcoord, texcoord;\
4484                     RSQ diffuse, distance.w;\
4485                     RCP distance, distance.w;\
4486                     MUL diffuse, texcoord, diffuse;\
4487                     DP3 diffuse, diffuse, normal;\
4488                     MUL final, diffuse, distance;\
4489                     MOV result.color.w, fragment.color;\
4490                     MUL result.color.xyz, fragment.color, final;\
4491                     \
4492                     END";
4493 #ifdef LINUX
4494                 glProgramStringARB_ptr(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(program), program);
4495 #else
4496                 glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(program), program);
4497 #endif
4498                 cycleprograminited = true;
4499             }
4500 #ifdef LINUX
4501             glProgramLocalParameter4fARB_ptr(GL_FRAGMENT_PROGRAM_ARB, 0, 0, 0, verletSpeed_ * verletSpeed_, 0);
4502 #else
4503             glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0, 0, verletSpeed_ * verletSpeed_, 0);
4504 #endif
4505             glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // blend func and depth mask. Efficient or not, glPushAttrib/glPopAttrib is a quick way to manage state.
4506             glEnable(GL_FRAGMENT_PROGRAM_ARB); // doesn't check if it exists...
4507 
4508             const unsigned sensors = 32; // actually one more
4509             const double mul = 0.25 * M_PI / sensors;
4510             const double add = -0.125 * M_PI;
4511 
4512             double size = gArena::SizeMultiplier() * 500 * M_SQRT2; // is M_SQRT2 in your math.h?
4513             GLfloat array[sensors+2][5];
4514 
4515             array[0][0] = 0;
4516             array[0][1] = 0;
4517             array[0][2] = p.x;
4518             array[0][3] = p.y;
4519             array[0][4] = 0.125;
4520 
4521             for(unsigned i=0; i<=sensors; i++) {
4522                 gSensor sensor(this, p, dir.Turn(cos(i * mul + add), sin(i * mul + add)));
4523                 sensor.detect(size);
4524                 array[i][5] = sensor.before_hit.x - p.x;
4525                 array[i][6] = sensor.before_hit.y - p.y;
4526                 array[i][7] = sensor.before_hit.x;
4527                 array[i][8] = sensor.before_hit.y;
4528                 array[i][9] = 0.125;
4529             }
4530 
4531             glPushMatrix();
4532             glLoadIdentity();
4533 
4534             glMatrixMode(GL_TEXTURE);
4535             glPushMatrix();
4536             glTranslatef(0, 0, 1);
4537 
4538             glBlendFunc(GL_ONE, GL_ONE);
4539             glDepthMask(GL_FALSE);
4540 
4541             glColor3fv(reinterpret_cast<GLfloat *>(&color_)); // 8-)
4542             glEnableClientState(GL_VERTEX_ARRAY);
4543             glEnableClientState(GL_TEXTURE_COORD_ARRAY);
4544 
4545             glInterleavedArrays(GL_T2F_V3F, 0, array);
4546             glDrawArrays(GL_TRIANGLE_FAN, 0, sensors+2);
4547 
4548             glDisableClientState(GL_VERTEX_ARRAY);
4549             glDisableClientState(GL_TEXTURE_COORD_ARRAY);
4550 
4551             glDisable(GL_FRAGMENT_PROGRAM_ARB);
4552 
4553             glPopMatrix();
4554             glMatrixMode(GL_MODELVIEW);
4555 
4556             glPopMatrix();
4557             glPopAttrib();
4558         }
4559 #endif // USE_HEADLIGHT
4560         // Name
4561         RenderName( cam );
4562 
4563 
4564         // shadow:
4565 
4566         sr_DepthOffset(true);
4567 
4568 
4569         REAL h=0;//se_cameraZ*.005+.03;
4570 
4571         glEnable(GL_CULL_FACE);
4572 
4573         if(!blinking && sr_floorDetail>rFLOOR_GRID && rTextureGroups::TextureMode[rTextureGroups::TEX_FLOOR]>0 && sr_alphaBlend){
4574             glColor3f(0,0,0);
4575             cycle_shad.Select();
4576             BeginQuads();
4577             glTexCoord2f(0,1);
4578             glVertex3f(-.6,.4,h);
4579 
4580             glTexCoord2f(1,1);
4581             glVertex3f(-.6,-.4,h);
4582 
4583             glTexCoord2f(1,0);
4584             glVertex3f(2.1,-.4,h);
4585 
4586             glTexCoord2f(0,0);
4587             glVertex3f(2.1,.4,h);
4588 
4589             RenderEnd();
4590         }
4591 
4592         glDisable(GL_CULL_FACE);
4593 
4594         // sr_laggometer;
4595 
4596 
4597         REAL f=verletSpeed_;
4598 
4599         REAL l=Lag();
4600 
4601         glPopMatrix();
4602 
4603         h=cam->CameraZ()*.005+.03;
4604 
4605 #ifdef ENABLE_OLD_LAG_O_METER
4606         if(sg_laggometerUseOld) {
4607             if (sn_GetNetState() != nSTANDALONE && sr_laggometer && f*l>.5) {
4608                 //&& owner!=::sn_myNetID){
4609                 glPushMatrix();
4610 
4611                 glColor3f(1,1,1);
4612                 //glDisable(GL_TEXTURE);
4613                 glDisable(GL_TEXTURE_2D);
4614 
4615                 glTranslatef(0,0,h);
4616                 //glScalef(.5*f,.5*f,.5*f);
4617 
4618                 // compensate for the .5 scaling further up
4619                 f *= 2 * sg_laggometerScale;
4620 
4621                 glScalef(f,f,f);
4622 
4623                 // move the sr_laggometer ahead a bit
4624                 if (!sr_predictObjects || sn_GetNetState()==nSERVER)
4625                     glTranslatef(l,0,0);
4626 
4627 
4628                 BeginLineLoop();
4629 
4630 
4631                 glVertex2f(-l,-l);
4632                 glVertex2f(0,0);
4633                 glVertex2f(-l,l);
4634                 REAL delay = GetTurnDelay();
4635                 if(l> 2*delay){
4636                     glVertex2f(-2*l+delay,delay);
4637                     glVertex2f(-2*l+2*delay,0);
4638                     glVertex2f(-2*l+delay,-delay);
4639                 }
4640                 else if (l>delay){
4641                     glVertex2f(-2*l+delay,delay);
4642                     glVertex2f(-l,2*delay-l);
4643                     glVertex2f(-l,-(2*delay-l));
4644                     glVertex2f(-2*l+delay,-delay);
4645                 }
4646 
4647                 RenderEnd();
4648                 glPopMatrix();
4649             }
4650         } else
4651 #endif
4652         {
4653             glPushMatrix();
4654 
4655             //glDisable(GL_TEXTURE);
4656             glDisable(GL_TEXTURE_2D);
4657 
4658             glTranslatef(0,0,h);
4659             //glScalef(.5*f,.5*f,.5*f);
4660 
4661             // compensate for the .5 scaling further up
4662             f *= 2 * sg_laggometerScale;
4663 
4664             glScalef(f,f,f);
4665 
4666             // move the sr_laggometer back a bit
4667             if (sr_predictObjects || sn_GetNetState()==nSERVER) {
4668                 glTranslatef(-l,0,0);
4669             }
4670 
4671             if (f*l>sg_laggometerThreshold) {
4672                 if (sr_laggometer) {
4673                     gLaggometer::LagOMeterRenderer(this).render(l);
4674                 }
4675             } else if(sg_axesIndicator) {
4676                 gLaggometer::AxesIndicator(this).render();
4677             }
4678 
4679             glPopMatrix();
4680         }
4681         sr_DepthOffset(false);
4682 
4683         glPopMatrix();
4684 
4685     }
4686 }
4687 
4688 static REAL fadeOutNameAfter = 5.0f;	/* 0: never show, < 0 always show */
4689 //static int fadeOutNameMode = 1;			// 0: never show, 1: show for fadeOutNameAfter, 2: always show
4690 static bool showOwnName = 0;			// show name on own cycle?
4691 
4692 static tSettingItem< bool > sg_showOwnName( "SHOW_OWN_NAME", showOwnName );
4693 //static tSettingItem< int > sg_fadeOutNameMode( "FADEOUT_NAME_MODE", showOwnName )
4694 static tSettingItem< REAL > sg_fadeOutNameAfter( "FADEOUT_NAME_DELAY", fadeOutNameAfter );
4695 
4696 
RenderName(const eCamera * cam)4697 void gCycle::RenderName( const eCamera* cam ) {
4698     if ( !this->Player() )
4699         return;
4700 
4701     float modelviewMatrix[16], projectionMatrix[16];
4702     float x, y, z, w;
4703     float xp, yp, wp;
4704     float alpha = 0.75;
4705 
4706     if (fadeOutNameAfter == 0) return; /* XXX put that in ::Render() */
4707     if ( !cam->RenderingMain() ) return; // no name in mirrored image
4708     if ( !showOwnName && cam->Player() == this->player ) return; // don't show own name
4709 
4710     glPushMatrix();
4711     /* position sign above center of cycle */
4712     glTranslatef(0.8, 0, 2.0);
4713     glGetFloatv(GL_MODELVIEW_MATRIX, modelviewMatrix);
4714     glGetFloatv(GL_PROJECTION_MATRIX, projectionMatrix);
4715     glPopMatrix();
4716 
4717     /* get coordinates of sign */
4718     x = modelviewMatrix[12];
4719     y = modelviewMatrix[13];
4720     z = modelviewMatrix[14];
4721     w = modelviewMatrix[15];
4722 
4723     /* multiply by projection matrix */
4724     xp = projectionMatrix[0] * x + projectionMatrix[4] * y +
4725          projectionMatrix[8] * z + projectionMatrix[12] * w;
4726     yp = projectionMatrix[1] * x + projectionMatrix[5] * y +
4727          projectionMatrix[9] * z + projectionMatrix[13] * w;
4728     wp = projectionMatrix[3] * x + projectionMatrix[7] * y +
4729          projectionMatrix[11] * z + projectionMatrix[15] * w;
4730 
4731     if (wp <= 0) {
4732         /* behind camera */
4733         timeCameIntoView = 0;
4734         return;
4735     }
4736 
4737     xp /= wp;
4738     yp /= wp;
4739     yp += rCHEIGHT_NORMAL;// * 2.0;
4740 
4741     if (xp <= -1 || xp >= 1 || yp <= -1 || yp >= 1) {
4742         /* out of screen */
4743         timeCameIntoView = 0;
4744         return;
4745     }
4746 
4747     /* it is visible */
4748 
4749     if (fadeOutNameAfter > 0) {
4750         REAL now = tSysTimeFloat();
4751         if (timeCameIntoView == 0)
4752             timeCameIntoView = now;
4753 
4754         if (now - timeCameIntoView > fadeOutNameAfter) {
4755             return;
4756         } else if (now - timeCameIntoView > fadeOutNameAfter - 1) {
4757             /* start to fade out */
4758             alpha = 0.75 - (now - timeCameIntoView -
4759                             (fadeOutNameAfter - 1)) * 0.75;
4760         }
4761     }
4762 
4763     ModelMatrix();
4764     glPushMatrix();
4765     glLoadIdentity();
4766 
4767     ProjMatrix();
4768     glPushMatrix();
4769     glLoadIdentity();
4770 
4771     glColor4f(1, 1, 1, alpha);
4772     DisplayText(xp, yp, rCWIDTH_NORMAL, rCHEIGHT_NORMAL, this->player->GetName(), 0, 0);
4773 
4774     ProjMatrix();
4775     glPopMatrix();
4776 
4777     ModelMatrix();
4778     glPopMatrix();
4779 }
4780 
4781 
RenderCockpitFixedBefore(bool)4782 bool gCycle::RenderCockpitFixedBefore(bool){
4783     /*
4784       if (alive)
4785       return true;
4786       else{
4787       REAL rd=se_GameTime()-deathTime;
4788       if (rd<1)
4789          return true;
4790       else{
4791          REAL d=1.25-rd;
4792          d*=8;
4793          if (d<0) d=0;
4794          glColor3f(d,d/2,d/4);
4795          glDisable(GL_TEXTURE_2D);
4796          glDisable(GL_TEXTURE);
4797          glDisable(GL_DEPTH_TEST);
4798          glRectf(-1,-1,1,1);
4799          glColor4f(1,1,1,rd*(2-rd/2));
4800          DisplayText(0,0,.05,.15,"You have been deleted.");
4801          return false;
4802       }
4803       }
4804     */
4805     return true;
4806 }
4807 
SoundMix(Uint8 * dest,unsigned int len,int viewer,REAL rvol,REAL lvol)4808 void gCycle::SoundMix(Uint8 *dest,unsigned int len,
4809                       int viewer,REAL rvol,REAL lvol){
4810     if (Alive()){
4811         /*
4812           if (!cycle_run.alt){
4813           rvol*=4;
4814           lvol*=4;
4815           }
4816         */
4817 
4818         if (engine)
4819             engine->Mix(dest,len,viewer,rvol,lvol,verletSpeed_/(sg_speedCycleSound * SpeedMultiplier()));
4820 
4821         if (turning)
4822         {
4823             if (turn_wav.alt)
4824                 turning->Mix(dest,len,viewer,rvol,lvol,5);
4825             else
4826                 turning->Mix(dest,len,viewer,rvol,lvol,1);
4827         }
4828 
4829         if (spark)
4830             spark->Mix(dest,len,viewer,rvol*.5,lvol*.5,4);
4831     }
4832 }
4833 #endif
4834 
PredictPosition() const4835 eCoord gCycle::PredictPosition() const {
4836     return predictPosition_;
4837 
4838     //    eCoord p = pos + dir * (speed * se_PredictTime());
4839     //    return p + correctPosSmooth;
4840 }
4841 
CamPos() const4842 eCoord gCycle::CamPos() const
4843 {
4844     return PredictPosition() + dir.Turn(0 ,-skew*z);
4845 
4846     //    gSensor s(this,pos, PredictPosition() - pos );
4847     //    s.detect(1);
4848 
4849     //    return s.before_hit + dir.Turn(0 ,-skew*z);
4850 
4851     // return pos + dir.Turn(0 ,-skew*z);
4852 }
4853 
CamTop() const4854 eCoord  gCycle::CamTop() const
4855 {
4856     return dir.Turn(0,-skew);
4857 }
4858 
4859 
4860 #ifdef POWERPAK_DEB
PPDisplay()4861 void gCycle::PPDisplay(){
4862     int R=int(r*255);
4863     int G=int(g*255);
4864     int B=int(b*255);
4865     PD_PutPixel(DoubleBuffer,
4866                 se_X_ToScreen(pos.x),
4867                 se_Y_ToScreen(pos.y),
4868                 PD_CreateColor(DoubleBuffer,R,G,B));
4869     /*
4870       PD_PutPixel(DoubleBuffer,
4871       se_X_ToScreen(pos.x+1),
4872       se_Y_ToScreen(pos.y),
4873       PD_CreateColor(DoubleBuffer,R,G,B));
4874       PD_PutPixel(DoubleBuffer,
4875       se_X_ToScreen(pos.x-1),
4876       se_Y_ToScreen(pos.y),
4877       PD_CreateColor(DoubleBuffer,R,G,B));
4878       PD_PutPixel(DoubleBuffer,
4879       se_X_ToScreen(pos.x),
4880       se_Y_ToScreen(pos.y+1),
4881       PD_CreateColor(DoubleBuffer,R,G,B));
4882       PD_PutPixel(DoubleBuffer,
4883       se_X_ToScreen(pos.x),
4884       se_Y_ToScreen(pos.y-1),
4885       PD_CreateColor(DoubleBuffer,R,G,B));
4886     */
4887 }
4888 #endif
4889 
4890 
4891 
4892 
4893 
4894 // cycle network routines:
gCycle(nMessage & m)4895 gCycle::gCycle(nMessage &m)
4896         :gCycleMovement(m),
4897         engine(NULL),
4898         turning(NULL),
4899         spark(NULL),
4900         skew(0),skewDot(0),
4901         rotationFrontWheel(1,0),rotationRearWheel(1,0),heightFrontWheel(0),heightRearWheel(0),
4902         currentWall(NULL),
4903         lastWall(NULL)
4904 {
4905     deathTime=0;
4906     lastNetWall=lastWall=currentWall=NULL;
4907     windingNumberWrapped_ = windingNumber_ = Grid()->DirectionWinding(dirDrive);
4908     dirDrive = Grid()->GetDirection(windingNumberWrapped_);
4909     dir = dirDrive;
4910 
4911     rubber=0;
4912     //correctTimeSmooth =0;
4913     correctDistanceSmooth =0;
4914     //correctSpeedSmooth =0;
4915 
4916     deathTime = 0;
4917     spawnTime_ = se_GameTime() + 100;
4918 
4919     m >> color_.r;
4920     m >> color_.g;
4921     m >> color_.b;
4922 
4923     trailColor_ = color_;
4924 
4925     se_MakeColorValid( color_.r, color_.g, color_.b, 1.0f );
4926     se_MakeColorValid( trailColor_.r, trailColor_.g, trailColor_.b, .5f );
4927 
4928     // set last time so that the first read_sync will not think this is old
4929     lastTimeAnim = lastTime = -EPS;
4930 
4931     nextSync = nextSyncOwner = -1;
4932     lastSyncOwnerGameTime_ = 0;
4933 }
4934 
4935 
WriteCreate(nMessage & m)4936 void gCycle::WriteCreate(nMessage &m){
4937     eNetGameObject::WriteCreate(m);
4938     m << color_.r;
4939     m << color_.g;
4940     m << color_.b;
4941 }
4942 
4943 static nVersionFeature sg_verletIntegration( 7 );
4944 
WriteSync(nMessage & m)4945 void gCycle::WriteSync(nMessage &m){
4946     //	eNetGameObject::WriteSync(m);
4947 
4948     if( SyncedUser() == Owner() )
4949     {
4950         lastSyncOwnerGameTime_ = lastTime;
4951     }
4952 
4953     if ( Alive() )
4954     {
4955         m << lastTime;
4956     }
4957     else
4958     {
4959         m << deathTime;
4960     }
4961     m << Direction();
4962     m << Position();
4963 
4964     REAL speed = verletSpeed_;
4965     // if the clients understand it, send them the real current speed
4966     if ( sg_verletIntegration.Supported() )
4967         speed = Speed();
4968 
4969 #ifdef DEBUG
4970     if ( speed > 15 )
4971     {
4972         int x;
4973         x = 0;
4974     }
4975 #endif
4976 
4977     m << speed;
4978     m << short( Alive() ? 1 : 0 );
4979     m << distance;
4980     if (!currentWall || currentWall->preliminary)
4981         m.Write(0);
4982     else
4983         m.Write(currentWall->ID());
4984 
4985     m.Write(turns);
4986     m.Write(braking);
4987 
4988     // write last turn position
4989     m << GetLastTurnPos();
4990 
4991     // write rubber
4992     compressZeroOne.Write( m, rubber/( sg_rubberCycle + .1 ) );
4993     compressZeroOne.Write( m, 1/( 1 + rubberMalus ) );
4994 
4995     // write last clientside sync message ID
4996     unsigned short lastMessageID = 0;
4997     if ( lastDestination )
4998         lastMessageID = lastDestination->messageID;
4999     m.Write(lastMessageID);
5000 
5001     // write brake
5002     compressZeroOne.Write( m, brakingReservoir );
5003 
5004     // set new sync times
5005     // nextSync=tSysTimeFloat()+sg_syncIntervalEnemy;
5006     // nextSyncOwner=tSysTimeFloat()+sg_GetSyncIntervalSelf( this );
5007 }
5008 
SyncIsNew(nMessage & m)5009 bool gCycle::SyncIsNew(nMessage &m){
5010     bool ret=eNetGameObject::SyncIsNew(m);
5011 
5012 
5013     REAL dummy2;
5014     short al;
5015     unsigned short Turns;
5016 
5017     m >> dummy2;
5018     m >> al;
5019     m >> dummy2;
5020     m.Read(Turns);
5021 
5022 #ifdef DEBUG_X
5023     con << "received sync for player " << player->GetName() << "  ";
5024     if (ret || al!=1){
5025         con << "accepting..\n";
5026         return true;
5027     }
5028     else
5029     {
5030         con << "rejecting..\n";
5031         return false;
5032     }
5033 #endif
5034 
5035     return ret || al!=1;
5036 }
5037 
RequestSyncOwner()5038 void gCycle::RequestSyncOwner()
5039 {
5040     // no more syncs when you're dead
5041     if ( !Alive() )
5042     {
5043         return;
5044     }
5045 
5046     // nothing to do on the client or if the cycle belongs to an AI
5047     if ( sn_GetNetState() != nSERVER || Owner() == 0 )
5048         return;
5049 
5050     REAL syncInterval = sg_GetSyncIntervalSelf( this );
5051     if ( nextSyncOwner < tSysTimeFloat() + syncInterval * 2.0 )
5052     {
5053         // postpone next sync so the notal number of syncs stays the same
5054         RequestSync( Owner(), false );
5055         nextSyncOwner += syncInterval;
5056     }
5057 }
5058 
RequestSyncAll()5059 void gCycle::RequestSyncAll()
5060 {
5061     // no more syncs when you're dead
5062     if ( !Alive() )
5063     {
5064         return;
5065     }
5066 
5067     // nothing to do on the client or if the cycle belongs to an AI
5068     if ( sn_GetNetState() != nSERVER || Owner() == 0 )
5069         return;
5070 
5071     REAL syncInterval = sg_syncIntervalEnemy;
5072     if ( nextSync < tSysTimeFloat() + syncInterval * 2.0 )
5073     {
5074         // postpone next sync so the notal number of syncs stays the same
5075         RequestSync( false );
5076         nextSync += syncInterval;
5077     }
5078 }
5079 
5080 // resets the extrapolator to the last known state
ResetExtrapolator()5081 void gCycle::ResetExtrapolator()
5082 {
5083     resimulate_ = false;
5084     if (!extrapolator_)
5085     {
5086         extrapolator_ = tNEW( gCycleExtrapolator )(grid, pos, dir );
5087     }
5088 
5089     extrapolator_->CopyFrom( lastSyncMessage_, *this );
5090 
5091     // simulate a bit, only to get current rubberSpeedFactor and acceleration
5092     extrapolator_->TimestepCore( extrapolator_->LastTime(), true );
5093 }
5094 
5095 // simulate the extrapolator at higher speed
Extrapolate(REAL dt)5096 bool gCycle::Extrapolate( REAL dt )
5097 {
5098     tASSERT( extrapolator_ );
5099 
5100 #ifdef DEBUG
5101     eCoord posBefore = extrapolator_->Position();
5102 #endif
5103 
5104     // calculate target time
5105     REAL newTime = extrapolator_->LastTime() + dt;
5106 
5107     bool ret = false;
5108 
5109     // clamp: don't simulate further than our current time
5110     if ( newTime >= lastTime )
5111     {
5112         // simulate extrapolator until now
5113         if( lastTime > extrapolator_->LastTime() )
5114         {
5115             eGameObject::TimestepThis( lastTime, extrapolator_ );
5116         }
5117 
5118         // test if there are real (the check for list does that) destinations left; we cannot call it finished if there are.
5119         gDestination* unhandledDestination = extrapolator_->GetCurrentDestination();
5120         ret = !unhandledDestination || !unhandledDestination->list;
5121 
5122         if( !ret )
5123         {
5124             if ( unhandledDestination->gameTime < newTime - Lag() * 2 - sn_Connections[0].ping.GetPing()*2 - GetTurnDelay()*4 )
5125             {
5126                 // emergency reset.
5127                 extrapolator_ = 0;
5128                 resimulate_ = true;
5129             }
5130         }
5131 
5132         newTime = lastTime;
5133     }
5134     else
5135     {
5136         // simulate extrapolator as requested
5137         eGameObject::TimestepThis( newTime, extrapolator_ );
5138     }
5139 
5140     //eCoord posAfter = extrapolator_->Position();
5141     //eDebugLine::SetTimeout( 1.0 );
5142     //eDebugLine::SetColor( 1,0,0 );
5143     //eDebugLine::Draw( posBefore, 8, posAfter, 4 );
5144     //eDebugLine::Draw( posBefore, 4, posAfter, 4 );
5145 
5146     return ret;
5147 }
5148 
5149 // makes sure the given displacement does not cross walls
se_SanifyDisplacement(eGameObject * base,eCoord & displacement)5150 void se_SanifyDisplacement( eGameObject* base, eCoord& displacement )
5151 {
5152     eCoord base_pos = base->Position();
5153     // eCoord reachable_pos = base->Position() + displacement;
5154 
5155     int timeout = 5;
5156     while( timeout > 0 )
5157     {
5158         --timeout;
5159 
5160         // cast ray fron sync_pos to reachable_pos
5161         gSensor test( base, base_pos, displacement );
5162         test.detect(1);
5163 
5164         if ( timeout == 0 )
5165         {
5166             // emergency exit; take something closely before the wall
5167             displacement = ( displacement ) * ( test.hit * .99 );
5168             break;
5169         }
5170 
5171         if ( !test.ehit )
5172         {
5173             // path is clear
5174             break;
5175         }
5176         else
5177         {
5178 #ifdef DEBUG
5179             // see if the wall that was hit is nearly parallel to the desired move. maybe we should add
5180             // special code for that. However, the projection idea seems to be universally robust.
5181             float p = test.ehit->Vec() * displacement;
5182             float m = se_EstimatedRangeOfMult( test.ehit->Vec(), displacement );
5183             if ( fabs(p) < m * .001 )
5184             {
5185                 con << "Almost missed wall during gCycle::ReadSync positon update";
5186             }
5187 #endif
5188 
5189             // project reachable_pos to the line that was hit
5190             REAL alpha = test.ehit->Ratio( base_pos + displacement );
5191             displacement = *test.ehit->Point() + test.ehit->Vec() * alpha - base_pos;
5192 
5193             // move it a bit closer to the known good position
5194             displacement = displacement * .99;
5195         }
5196     }
5197 }
5198 
TransferPositionCorrectionToDistanceCorrection()5199 void gCycle::TransferPositionCorrectionToDistanceCorrection()
5200 {
5201     REAL newCorrectDist = eCoord::F( correctPosSmooth, dirDrive );
5202     distance += newCorrectDist - correctDistanceSmooth;
5203     correctDistanceSmooth = newCorrectDist;
5204 }
5205 
5206 // take over the extrapolator's data
SyncFromExtrapolator()5207 void gCycle::SyncFromExtrapolator()
5208 {
5209     // store old position
5210     eCoord oldPos = pos;
5211 
5212     // con << "Copy: " << LastTime() << ", " << extrapolator_->LastTime() << ", " << extrapolator_->Position() << ", " << pos << "\n";
5213 
5214     tASSERT( extrapolator_ );
5215 
5216     // delegate
5217     CopyFrom( *extrapolator_ );
5218 
5219     // adjust current wall (not essential, don't do it for the first wall)
5220     if ( currentWall && currentWall->tBeg > spawnTime_ + sg_cycleWallTime + .01f )
5221     {
5222         // update start position
5223         currentWall->beg = extrapolator_->GetLastTurnPos();
5224 
5225         // set begin distance as well
5226         REAL dBeg = extrapolator_->GetDistance() - eCoord::F( extrapolator_->Direction(), extrapolator_->Position() - extrapolator_->GetLastTurnPos() );
5227 
5228         currentWall->dbegin = dBeg;
5229         currentWall->coords_[0].Pos = dBeg;
5230 
5231         // and care for consistency
5232         int i;
5233         for ( i = currentWall->coords_.Len() -1 ; i>=0; --i )
5234         {
5235             gPlayerWallCoord & coord = currentWall->coords_( i );
5236             if ( coord.Pos <= dBeg )
5237                 coord.Pos = dBeg;
5238         }
5239     }
5240 
5241     // transfer last turn position
5242     lastTurnPos_ = extrapolator_->GetLastTurnPos();
5243 
5244     // smooth position correction
5245     correctPosSmooth = correctPosSmooth + oldPos - pos;
5246 
5247 #ifdef DEBUG
5248     if ( correctPosSmooth.NormSquared() > .1f )
5249     {
5250         std::cout << "Lag slide! " << correctPosSmooth << "\n";
5251         resimulate_ = true;
5252     }
5253 #endif
5254 
5255     // calculate time difference between this cycle and extrapolator
5256     REAL dt = this->LastTime() - extrapolator_->LastTime();
5257 
5258     // extrapolate true distance ( the best estimate we have for the distance on the server )
5259     REAL trueDistance = extrapolator_->trueDistance_ + extrapolator_->Speed() * dt;
5260 
5261     // update distance correction
5262     // con << 	correctDistanceSmooth << "," << trueDistance << "," << distance << "\n";
5263     // correctDistanceSmooth = trueDistance - distance;
5264     distance = trueDistance;
5265     correctDistanceSmooth=0;
5266 
5267     // split away part in driving direction
5268     TransferPositionCorrectionToDistanceCorrection();
5269 
5270     // make sure correction does not bring us to the wrong side of a wall
5271     // se_SanifyDisplacement( this, correctPosSmooth );
5272 
5273     //eDebugLine::SetTimeout( 1.0 );
5274     //eDebugLine::SetColor( 0,1,0 );
5275     //eDebugLine::Draw( pos, 4, pos + dirDrive * 10, 14 );
5276 
5277     // delete extrapolator
5278     extrapolator_ = 0;
5279 }
5280 
5281 static int sg_useExtrapolatorSync=1;
5282 static tSettingItem<int> sg_useExtrapolatorSyncConf("EXTRAPOLATOR_SYNC",sg_useExtrapolatorSync);
5283 
5284 // make sure no correction moves the cycle backwards beyond the beginning of the last wall
ClampForward(eCoord & newPos,const eCoord & startPos,const eCoord & dir)5285 void ClampForward( eCoord& newPos, const eCoord& startPos, const eCoord& dir )
5286 {
5287     REAL forward = eCoord::F( newPos - startPos, dir )/dir.NormSquared();
5288     if ( forward < 0 )
5289         newPos = newPos - dir * forward;
5290 }
5291 
5292 extern REAL sg_cycleBrakeRefill;
5293 extern REAL sg_cycleBrakeDeplete;
5294 
ReadSync(nMessage & m)5295 void gCycle::ReadSync( nMessage &m )
5296 {
5297     // data from sync message
5298     SyncData sync;
5299 
5300     short sync_alive;               // is this cycle alive?
5301     unsigned short sync_wall=0;     // ID of wall
5302 
5303     // eCoord new_pos = pos;	// the extrapolated position
5304 
5305     // warning: depends on the implementation of eNetGameObject::WriteSync
5306     // since we don't call eNetGameObject::ReadSync.
5307     m >> sync.time;
5308 
5309     // reset values not sent with old protocol messages
5310     sync.rubber = rubber;
5311     sync.turns = turns;
5312     sync.braking = braking;
5313     sync.messageID = 1;
5314 
5315     m >> sync.dir;
5316     m >> sync.pos;
5317 
5318     //eDebugLine::SetTimeout( 1.0 );
5319     //eDebugLine::SetColor( 1,1,1 );
5320     //eDebugLine::Draw( lastSyncMessage_.pos, 0, lastSyncMessage_.pos, 20 );
5321 
5322     m >> sync.speed;
5323     m >> sync_alive;
5324     m >> sync.distance;
5325     m.Read(sync_wall);
5326     if (!m.End())
5327         m.Read(sync.turns);
5328     if (!m.End())
5329         m.Read(sync.braking);
5330 
5331     if ( !m.End() )
5332     {
5333         m >> sync.lastTurn;
5334     }
5335     else if ( currentWall )
5336     {
5337         sync.lastTurn = currentWall->beg;
5338     }
5339 
5340     bool canUseExtrapolatorMethod = false;
5341 
5342     bool rubberSent = false;
5343     if ( !m.End() )
5344     {
5345         rubberSent = true;
5346 
5347         // read rubber
5348         REAL preRubber, preRubberMalus;
5349         preRubber = compressZeroOne.Read( m );
5350         preRubberMalus = compressZeroOne.Read( m );
5351 
5352         // read last message ID
5353         m.Read(sync.messageID);
5354 
5355         // read braking reservoir
5356         sync.brakingReservoir = compressZeroOne.Read( m );
5357         // std::cout << "sync: " << sync.brakingReservoir << ":" << sync.braking << "\n";
5358 
5359         // undo skewing
5360         sync.rubber = preRubber * ( sg_rubberCycle + .1 );
5361         sync.rubberMalus = 1/preRubberMalus - 1;
5362 
5363         // extrapolation is probably safe
5364         canUseExtrapolatorMethod = sg_useExtrapolatorSync && lastTime > 0;
5365     }
5366     else
5367     {
5368         // try to extrapolate brake status backwards in time
5369         sync.brakingReservoir = brakingReservoir;
5370         if ( brakingReservoir > 0 && sync.braking )
5371             sync.brakingReservoir += ( lastTime - sync.time ) * sg_cycleBrakeDeplete;
5372         else if ( brakingReservoir < 1 && !sync.braking )
5373             sync.brakingReservoir -= ( lastTime - sync.time ) * sg_cycleBrakeRefill;
5374 
5375         if ( sync.brakingReservoir < 0 )
5376             sync.brakingReservoir = 0;
5377         else if ( sync.brakingReservoir > 1 )
5378             sync.brakingReservoir = 1;
5379     }
5380 
5381     // abort if sync is not new
5382     if ( lastSyncMessage_.time >= sync.time && lastSyncMessage_.turns >= sync.turns && sync_alive > 0 )
5383     {
5384         //eDebugLine::SetTimeout( 5 );
5385         //eDebugLine::SetColor( 1,1,0);
5386         //eDebugLine::Draw( lastSyncMessage_.pos, 1.5, lastSyncMessage_.pos, 5.0 );
5387         return;
5388     }
5389     lastSyncMessage_ = sync;
5390 
5391     // store last known good position: a bit before the last position confirmed by the server
5392     lastGoodPosition_ = sync.pos + ( sync.lastTurn - sync.pos ) *.01;
5393     // offset it a tiny bit by our last driving direction
5394     if ( eCoord::F( dirDrive, sync.dir ) > .99f*dirDrive.NormSquared() )
5395         lastGoodPosition_ = lastGoodPosition_ - this->lastDirDrive * .0001;
5396 
5397     //eDebugLine::SetTimeout( 2 );
5398     //eDebugLine::SetColor( 0,1,0);
5399     //eDebugLine::Draw( lastSyncMessage_.pos, 1.5, lastSyncMessage_.pos, 5.0 );
5400 
5401     // con << "Sync: " << lastTime << ", " << lastSyncMessage_.time << ", " << lastSyncMessage_.pos << ", " << pos << "\n";
5402 
5403     if ( canUseExtrapolatorMethod )
5404     {
5405         // reset extrapolator if information from server is more up to date
5406         if ( extrapolator_ && extrapolator_->LastTime() < lastSyncMessage_.time )
5407         {
5408             extrapolator_ = 0;
5409         }
5410     }
5411 
5412     // killed?
5413     if (Alive() && sync_alive!=1 && GOID() >= 0 && grid )
5414     {
5415         Die( lastSyncMessage_.time );
5416         MoveSafely( lastSyncMessage_.pos, lastTime, deathTime );
5417         distance=lastSyncMessage_.distance;
5418         correctDistanceSmooth=0;
5419         DropWall( false );
5420 
5421         tNEW(gExplosion)( grid, lastSyncMessage_.pos, lastSyncMessage_.time ,color_, this );
5422 
5423         return;
5424     }
5425 
5426     // no point going on if you're not alive
5427     if (!Alive())
5428     {
5429 #ifdef DEBUG
5430         con << "Received duplicate death sync message; those things confuse old clients!\n";
5431 #endif
5432         return;
5433     }
5434 
5435     gDestination emergency_aft(*this);
5436 
5437     // all the data was read. check where it fits in our destination list:
5438     gDestination *bef= GetDestinationBefore( lastSyncMessage_, destinationList );
5439     gDestination *aft=NULL;
5440     if (bef){
5441         aft=bef->next;
5442         if (!aft)
5443             aft=&emergency_aft;
5444 
5445         //eDebugLine::SetTimeout(1);
5446         //eDebugLine::SetColor( 1,0,0 );
5447         //eDebugLine::Draw( bef->position, 1.5, lastSyncMessage_.pos, 3.0 );
5448         //eDebugLine::SetColor( 0,1,0 );
5449         //eDebugLine::Draw( lastSyncMessage_.pos, 3.0, aft->position, 5.0 );
5450 
5451     }
5452 
5453 
5454     // detect local tunneling by casting a ray from the server certified position
5455     // to the next known client position
5456     if ( lastTime > 0 )
5457     {
5458         eCoord position = pos;
5459         if ( bef )
5460             position = bef->position;
5461         eSensor tunnel( this, lastSyncMessage_.pos, position - lastSyncMessage_.pos );
5462         tunnel.detect( 1 );
5463 
5464         // use extrapolation to undo local tunneling
5465         if ( tunnel.ehit )
5466         {
5467             canUseExtrapolatorMethod = true;
5468             if ( 0 )
5469             {
5470                 con << "possible local tunneling detected\n";
5471                 eSensor tunnel( this, lastSyncMessage_.pos, position - lastSyncMessage_.pos );
5472                 tunnel.detect( 1 );
5473             }
5474         }
5475     }
5476     else
5477     {
5478         // first sync. Accept the position without questioning it.
5479         pos = sync.pos;
5480         FindCurrentFace();
5481     }
5482 
5483     // determine whether we can use the distance based interpolating sync method here
5484     bool distanceBased = aft && aft != &emergency_aft && Owner() == sn_myNetID;
5485 
5486     if ( canUseExtrapolatorMethod && Owner()==sn_myNetID )
5487     {
5488         // exactlu resimulate from sync position for cycles controlled by this client
5489         resimulate_ = true;
5490 
5491         return;
5492     }
5493 
5494     rubber = lastSyncMessage_.rubber;
5495 
5496     // bool fullSync = false;
5497 
5498     // try to get a distance value closer to the client's data by calculating the distance from the sync position to the next destination
5499     REAL interpolatedDistance = lastSyncMessage_.distance;
5500     if ( aft )
5501     {
5502         interpolatedDistance = aft->distance - sqrt((lastSyncMessage_.pos-aft->position).NormSquared());
5503     }
5504 
5505     // determine true lag
5506     // REAL lag = 1;
5507     //if ( player )
5508     //    lag = player->ping;
5509 
5510     if ( distanceBased )
5511     {
5512         // new way: correct our time and speed
5513 
5514 #ifdef DEBUG
5515         // destination *save = bef;
5516 #endif
5517 
5518         REAL ratio = (interpolatedDistance - bef->distance)/
5519                      (aft->distance - bef->distance);
5520 
5521         if (!finite(ratio))
5522             ratio = 0;
5523 
5524         // interpolate when the cycle was at the position the sync message was sent
5525         REAL interpolatedTime = bef->gameTime * (1-ratio) + aft->gameTime * ratio;
5526         // REAL interpolatedSpeed = bef->speed   * (1-ratio) + aft->speed    * ratio;
5527 
5528         // calculate deltas
5529         REAL correctTime  = ( lastSyncMessage_.time     - interpolatedTime     );
5530         // REAL correctSpeed = ( lastSyncMessage_.speed    - interpolatedSpeed    );
5531         REAL correctDist  = ( lastSyncMessage_.distance - interpolatedDistance );
5532 
5533         // don't trust high ratios too much; they may be skewed by rubber application
5534         {
5535             REAL factor = (1 - ratio) * 5;
5536             if ( factor < 1 )
5537             {
5538                 if ( factor < 0 )
5539                     factor = 0;
5540                 correctTime *= factor;
5541                 correctDist *= factor;
5542             }
5543         }
5544 
5545         //if (correctTime > 0)
5546         //{
5547         //    correctTime*=.5;
5548         //    correctSpeed*=.5;
5549         //    correctDist*=.5;
5550         //}
5551 
5552         //correctTimeSmooth       += correctTime;
5553         //correctSpeedSmooth      += correctSpeed;
5554         // correctDistanceSmooth   += correctDist;
5555 
5556         // correctDistanceSmooth   += correctDist;
5557 
5558         // con << ratio << ", " << correctDist << ", " << correctTime << "\n";
5559 
5560         // correct distances according to sync
5561         {
5562             distance += correctDist;
5563             gDestination * run = bef;
5564             while ( run )
5565             {
5566                 run->distance += correctDist;
5567                 run = run->next;
5568             }
5569         }
5570 
5571         // correct time by adapting position and distance
5572         eCoord newPos = pos - Direction() * Speed() * correctTime;
5573         if ( currentWall )
5574         {
5575             ClampForward( newPos, currentWall->beg, Direction() );
5576         }
5577 
5578         // don't tunnel through walls
5579         {
5580             const eCoord & safePos = pos; // aft->position
5581             gSensor test( this, safePos , newPos - safePos );
5582             test.detect(1);
5583             if ( test.ehit )
5584             {
5585                 newPos = test.before_hit;
5586 
5587                 // something bad must be going on, better recheck with accurate extrapolation
5588                 resimulate_ = true;
5589             }
5590         }
5591 
5592         correctPosSmooth = correctPosSmooth + pos - newPos;
5593         distance += eCoord::F( newPos - pos, Direction() )/Direction().NormSquared();
5594 
5595         MoveSafely( newPos, lastTime, lastTime );
5596 
5597         /*
5598         REAL ts = lastSyncMessage_.time - lastTime;
5599 
5600         //        eCoord intPos = pos + dirDrive * (ts * speed + .5 * ts*ts*acceleration);
5601         eCoord intPos = pos + dirDrive * ( ts * ( speed + lastSyncMessage_.speed ) * .5 );
5602         REAL  int_speed = speed + acceleration*ts;
5603 
5604         dirDrive = lastSyncMessage_.dir;
5605 
5606         correctPosSmooth = lastSyncMessage_.pos - intPos;
5607 
5608         distance = lastSyncMessage_.distance - speed * ts - acceleration * ts*ts*.5;
5609 
5610         //correctTimeSmooth = 0;
5611         */
5612     }
5613     else
5614     {
5615         // direct sync
5616         if ( Owner() != sn_myNetID )
5617         {
5618             // direct extrapolation for cycles of other clients or if no turn is newer than the sync
5619             SyncEnemy( lastSyncMessage_.lastTurn );
5620 
5621             // update beginning of current wall
5622             if ( currentWall )
5623             {
5624                 currentWall->beg = lastSyncMessage_.lastTurn;
5625             }
5626 
5627             // update brake status
5628             AccelerationDiscontinuity();
5629             braking = lastSyncMessage_.braking;
5630 
5631             // store last turn
5632             lastTurnPos_ = lastSyncMessage_.lastTurn;
5633         }
5634         else
5635         {
5636             // same algorithm, but update smooth position correction so that there is no immediate visual change
5637             eCoord oldPos = pos + correctPosSmooth;
5638             SyncEnemy( lastSyncMessage_.lastTurn );
5639             correctPosSmooth = oldPos - pos;
5640         }
5641 
5642         // restore rubber meter
5643         if ( !rubberSent )
5644         {
5645             rubber = lastSyncMessage_.rubber;
5646         }
5647     }
5648 
5649     // if this happens during creation, ignore position correction
5650     if ( this->ID() == 0 )
5651     {
5652         correctPosSmooth = eCoord();
5653 
5654         // some other stuff that should happen on the first sync
5655 
5656         // estimate time of spawning (HACK)
5657         spawnTime_=lastTime;
5658         if ( verletSpeed_ > 0 )
5659             spawnTime_ -= distance/verletSpeed_;
5660 
5661         // set spawn time to infinite past if this is the first spawn
5662         if ( !sg_cycleFirstSpawnProtection && spawnTime_ <= 1.0 )
5663         {
5664             spawnTime_ = -1E+20;
5665         }
5666 
5667         // reset position and direction
5668         predictPosition_ = pos;
5669         dir = dirDrive;
5670         skew = skewDot = 0;
5671         lastDirDrive=dirDrive;
5672         lastTurnPos_=pos;
5673     }
5674 #ifdef DEBUG
5675     else
5676         if ( correctPosSmooth.NormSquared() > .1f && lastTime > 0.0 )
5677         {
5678             std::cout << "Lag slide! " << correctPosSmooth << "\n";
5679             int x;
5680             x = 0;
5681         }
5682 #endif
5683 
5684     sn_Update(turns,lastSyncMessage_.turns);
5685 
5686     //if (fabs(correctTimeSmooth > 5))
5687     //    st_Breakpoint();
5688 
5689     // calculate new winding number. Try to change it as little as possible.
5690     this->SetWindingNumberWrapped( Grid()->DirectionWinding(dirDrive) );
5691 
5692     // Resnap to the axis
5693     dirDrive = Grid()->GetDirection(windingNumberWrapped_);
5694 }
5695 
SyncEnemy(const eCoord &)5696 void gCycle::SyncEnemy ( const eCoord& )
5697 {
5698     // keep this cycle alive
5699     tJUST_CONTROLLED_PTR< gCycle > keep( this->GetRefcount()>0 ? this : 0 );
5700 
5701     resimulate_ = false;
5702 
5703     // calculate turning
5704     bool turned = false;
5705     REAL turnDirection=( dirDrive*lastSyncMessage_.dir );
5706     REAL notTurned=eCoord::F( dirDrive, lastSyncMessage_.dir )/dirDrive.NormSquared();
5707 
5708     // the last known time
5709     REAL lastKnownTime = lastTime;
5710 
5711     // calculate the position of the last turn from the sync data
5712     if ( distance > 0 && ( notTurned < .99 || this->turns < lastSyncMessage_.turns ) )
5713     {
5714         // reset sound
5715         if (turning)
5716             turning->Reset();
5717 
5718         // update old wall as good as we can
5719         eCoord crossPos = lastSyncMessage_.pos;
5720         REAL crossDist = lastSyncMessage_.distance;
5721         REAL crossTime = lastSyncMessage_.time;
5722 
5723         // calculate intersection of old and new trajectory (if only one turn was made)
5724         // the second condition is for old servers that don't send the turn count; they
5725         // don't support multiple axes, so we can possibly detect missed turns if
5726         // the dot product of the current and last direction is negative.
5727         if (this->turns+1 >= lastSyncMessage_.turns && ( lastSyncMessage_.turns > 0 || notTurned > -.5 ) )
5728         {
5729             if ( fabs( turnDirection ) > .01 )
5730             {
5731                 REAL b = ( crossPos - pos ) * dirDrive;
5732                 REAL distplace = b/turnDirection;
5733                 crossPos = crossPos + lastSyncMessage_.dir * distplace;
5734                 crossDist += distplace;
5735                 if ( lastSyncMessage_.speed > 0 )
5736                     crossTime += distplace / lastSyncMessage_.speed;
5737 
5738                 tASSERT( fabs ( ( crossPos - pos ) * dirDrive ) < 1 );
5739                 tASSERT( fabs ( ( crossPos - lastSyncMessage_.pos ) * lastSyncMessage_.dir ) < 1 );
5740 
5741                 // update the old wall
5742                 if (currentWall)
5743                     currentWall->Update(crossTime,crossPos);
5744             }
5745         }
5746         else
5747         {
5748             // a turn sync was dropped for whatever reason. The last wall is not reliable.
5749             // make it disappear immediately.
5750             if (currentWall)
5751             {
5752                 currentWall->real_CopyIntoGrid(grid);
5753             }
5754         }
5755 
5756         eDebugLine::SetTimeout(5);
5757         eDebugLine::SetColor( 1,0,0 );
5758         eDebugLine::Draw( crossPos, 0, crossPos, 8 );
5759 
5760         // drop old wall
5761         if(currentWall){
5762             lastWall=currentWall;
5763             currentWall->CopyIntoGrid( grid );
5764             tControlledPTR< gNetPlayerWall > bounce( currentWall );
5765             currentWall=NULL;
5766         }
5767 
5768         // create new wall at sync location
5769         distance = lastSyncMessage_.distance;
5770         correctDistanceSmooth=0;
5771 
5772         REAL startBuildWallAt = spawnTime_ + sg_cycleWallTime;
5773         if ( crossTime > startBuildWallAt )
5774             currentWall=new gNetPlayerWall
5775                         (this,crossPos,lastSyncMessage_.dir,crossTime,crossDist);
5776 
5777         turned = true;
5778 
5779         // save last driving direction
5780         lastDirDrive = dirDrive;
5781 
5782         // move to cross position
5783         MoveSafely( crossPos, lastTime, crossTime );
5784         lastKnownTime = crossTime;
5785     }
5786 
5787     // side bending
5788     skewDot+=4*turnDirection;
5789 
5790     // calculate timestep
5791     // REAL ts = lastSyncMessage_.time - lastTime;
5792 
5793     // backup current time
5794     REAL oldTime = lastTime;
5795 
5796     // update position, speed, distance and direction
5797     MoveSafely( lastSyncMessage_.pos, lastKnownTime, lastSyncMessage_.time );
5798     verletSpeed_  = lastSyncMessage_.speed;
5799     lastTimestep_ = 0;
5800     distance = lastSyncMessage_.distance;
5801     correctDistanceSmooth=0;
5802     dirDrive = lastSyncMessage_.dir;
5803     rubber = lastSyncMessage_.rubber;
5804     brakingReservoir = lastSyncMessage_.brakingReservoir;
5805 
5806     // update time to values from sync
5807     lastTime = lastSyncMessage_.time;
5808     if ( oldTime < 0 )
5809         oldTime = lastTime;
5810     clientside_action();
5811 
5812     // bend last driving direction if it is antiparallel to the current one
5813     if ( lastDirDrive * dirDrive < EPS && eCoord::F( lastDirDrive, dirDrive ) < 0 )
5814     {
5815         lastDirDrive = dirDrive.Turn(0,1);
5816     }
5817 
5818     // correct laggometer to actual facts
5819     if (Owner() != sn_myNetID )
5820     {
5821         // calculate lag from sync delay
5822         REAL lag = se_GameTime() - lastSyncMessage_.time;
5823         if ( lag < 0 )
5824             lag = 0;
5825 
5826         // try not to let the lag jump
5827         REAL maxLag = laggometer * 1.2;
5828         REAL minLag = laggometer * .8;
5829 
5830         // store it
5831         laggometer = lag;
5832 
5833         // clamp smooth laggometer to it (or set it if a turn was made)
5834         //if ( laggometerSmooth > lag )
5835         //    laggometerSmooth = lag;
5836 
5837         // For the client and without prediction:
5838         // do not simulate if a turn was made, just accept sync as it is
5839         // (so turns don't look awkward)
5840         if (
5841             sn_GetNetState()==nCLIENT && turned )
5842         {
5843             laggometerSmooth = lag;
5844 
5845             // but at least update the current wall
5846             if ( currentWall )
5847                 currentWall->Update(lastTime,pos);
5848 
5849             // and reset the animation time
5850             lastTimeAnim=lastTime;
5851 
5852             return;
5853         }
5854         else
5855         {
5856             // sanity check: the lag should not jump up suddenly
5857             // (and if it does, we'll catch up at the next turn)
5858             // REAL maxLag = 1.2 * laggometerSmooth;
5859             if ( laggometer > maxLag )
5860                 laggometer = maxLag;
5861             if ( laggometer < minLag )
5862                 laggometer = minLag;
5863         }
5864     }
5865 
5866     // simulate to extrapolate, but don't touch the smooth laggometer
5867     {
5868         REAL laggometerSmoothBackup = this->laggometerSmooth;
5869         TimestepThis(oldTime, this);
5870         this->laggometerSmooth = laggometerSmoothBackup;
5871     }
5872 }
5873 
5874 /*
5875 void gCycle::old_ReadSync(nMessage &m){
5876   REAL oldTime=lastTime;
5877   eCoord oldpos=pos;
5878   //+correctPosSmooth;
5879   //correctPosSmooth=0;
5880   eCoord olddir=dir;
5881   eNetGameObject::ReadSync(m);
5882 
5883   REAL t=(dirDrive*dir);
5884   if (fabs(t)>.2){
5885 #ifdef DEBUG
5886     if (owner==sn_myNetID)
5887       con << "Warning! Turned cycle!\n";
5888 #endif
5889     turning.Reset();
5890   }
5891 
5892   // side bending
5893   skewDot+=4*t;
5894 
5895 
5896   dirDrive=dir;
5897   dir=olddir;
5898 
5899   m >> speed;
5900   short new_alive;
5901   m >> new_alive;
5902   if (alive && new_alive!=1){
5903     new gExplosion(pos,oldTime,r,g,b);
5904     deathTime=oldTime;
5905     eEdge::SeethroughHasChanged();
5906   }
5907   alive=new_alive;
5908 
5909   m >> distance;
5910 
5911   // go to old time frame
5912 
5913   eCoord realpos=pos;
5914   REAL realtime=lastTime;
5915   REAL realdist=distance;
5916 
5917   if (currentWall)
5918     lastWall=currentWall;
5919 
5920   m.Read(currentWallID);
5921 
5922   unsigned short Turns;
5923   if (!m.End())
5924     m.Read(Turns);
5925   else
5926     Turns=turns;
5927 
5928   if (!m.End())
5929     m.Read(braking);
5930   else
5931     braking=false;
5932 
5933   TimestepThis(oldTime,this);
5934 
5935   if (!currentWall || fabs(t)>.3 || currentWall->inGrid){
5936     //REAL d=eCoord::F(pos-realpos,olddir);
5937     //eCoord crosspos=realpos+olddir*d;
5938     //d=eCoord::F(oldpos-crosspos,dir);
5939     //crosspos=crosspos+dir*d;
5940 
5941     eCoord crosspos=realpos;
5942 
5943     if (currentWall){
5944       currentWall->Update(realtime,crosspos);
5945       //currentWall->Update(realtime,realpos);
5946       currentWall->CopyIntoGrid();
5947     }
5948     //con << "NEW\n";
5949     currentWall=new gNetPlayerWall
5950       (this,crosspos,dirDrive,realtime,realdist);
5951   }
5952 
5953 
5954   // smooth correction
5955   if ((oldpos-pos).NormSquared()<4 && fabs(t)<.5){
5956     correctPosSmooth=pos-oldpos;
5957     pos=oldpos;
5958   }
5959 
5960 
5961 
5962 #ifdef DEBUG
5963   //int old_t=turns;
5964   //if(sn_Update(turns,Turns))
5965   //con << "Updated turns form " << old_t << " to " << turns << "\n";
5966 #endif
5967   sn_Update(turns,Turns);
5968 }
5969 */
5970 
ReceiveControl(REAL time,uActionPlayer * act,REAL x)5971 void gCycle::ReceiveControl(REAL time,uActionPlayer *act,REAL x){
5972     //forceTime=true;
5973     TimestepThis(time,this);
5974     Act(act,x);
5975     //forceTime=false;
5976 }
5977 
PrintName(tString & s) const5978 void gCycle::PrintName(tString &s) const
5979 {
5980     s << "gCycle nr. " << ID();
5981     if ( this->player )
5982     {
5983         s << " owned by ";
5984         this->player->PrintName( s );
5985     }
5986 }
5987 
ActionOnQuit()5988 bool gCycle::ActionOnQuit()
5989 {
5990     //  currentWall = NULL;
5991     //  lastWall = NULL;
5992     if ( sn_GetNetState() == nSERVER )
5993     {
5994         TakeOwnership();
5995         Kill();
5996         return false;
5997     }
5998     else
5999     {
6000         return true;
6001     }
6002 }
6003 
6004 
CreatorDescriptor() const6005 nDescriptor &gCycle::CreatorDescriptor() const{
6006     return cycle_init;
6007 }
6008 
6009 /*
6010 void gCycle::AddDestination(gDestination *dest){
6011     //  con << "got new dest " << dest->position << "," << dest->direction
6012     // << "," << dest->distance << "\n";
6013 
6014     dest->InsertIntoList(&destinationList);
6015 
6016     // if the new destination was inserted at the end of the list
6017     //if (!currentDestination && !dest->next &&
6018     //if ((dest->distance >= distance || (!currentDestination && !dest->next)) &&
6019 
6020     if (dest->next && dest->next->hasBeenUsed){
6021         delete dest;
6022         return;
6023     }
6024 
6025     // go back one destination if the new destination appears to be older than the current one
6026     if ((!currentDestination || currentDestination == dest->next ) &&
6027             sn_GetNetState()!=nSTANDALONE && Owner()!=::sn_myNetID){
6028         currentDestination=dest;
6029         // con << "setting new cd\n";
6030 	}
6031 }
6032 */
6033 
RightBeforeDeath(int numTries)6034 void gCycle::RightBeforeDeath( int numTries )
6035 {
6036     if ( player )
6037     {
6038         player->RightBeforeDeath( numTries );
6039     }
6040 
6041     // delay syncs for old clients; they would tunnel locally
6042     if ( sg_avoidBadOldClientSync && !sg_NoLocalTunnelOnSync.Supported( Owner() ) )
6043     {
6044         nextSync=tSysTimeFloat()+sg_syncIntervalEnemy;
6045         nextSyncOwner=tSysTimeFloat()+sg_GetSyncIntervalSelf( this );
6046     }
6047 
6048     correctPosSmooth = correctPosSmooth * .5;
6049 }
6050 
6051 // *******************************************************************************************
6052 // *
6053 // *	DoIsDestinationUsed
6054 // *
6055 // *******************************************************************************************
6056 //!
6057 //!		@param	dest	the destination to test
6058 //!		@return			true if the destination is still in active use
6059 //!
6060 // *******************************************************************************************
6061 
DoIsDestinationUsed(const gDestination * dest) const6062 bool gCycle::DoIsDestinationUsed( const gDestination * dest ) const
6063 {
6064     return ( extrapolator_ && extrapolator_->IsDestinationUsed( dest ) ) || gCycleMovement::DoIsDestinationUsed( dest );
6065 }
6066 
6067 // *******************************************************************************************
6068 // *
6069 // *   DoGetDistanceSinceLastTurn
6070 // *
6071 // *******************************************************************************************
6072 //!
6073 //!        @return     the distance driven since the last turn
6074 //!
6075 // *******************************************************************************************
6076 
6077 /*
6078 REAL gCycle::DoGetDistanceSinceLastTurn( void ) const
6079 {
6080     if ( currentWall )
6081     {
6082         return ( currentWall->Pos(1) - currentWall->Pos(0) );
6083     }
6084 
6085     return 0;
6086 }
6087 */
6088 
6089 // *******************************************************************************************
6090 // *
6091 // *   DoGetDistanceSinceLastTurn
6092 // *
6093 // *******************************************************************************************
6094 //!
6095 //!        @return     the distance driven since the last turn
6096 //!
6097 // *******************************************************************************************
6098 
6099 /*
6100 REAL gCycleExtrapolator::DoGetDistanceSinceLastTurn( void ) const
6101 {
6102     return eCoord::F( pos - lastTurn_, dirDrive );
6103 }
6104 */
6105 
6106 // *******************************************************************************************
6107 // *
6108 // *	Vulnerable
6109 // *
6110 // *******************************************************************************************
6111 //!
6112 //!		@return		true if the cycle is vulnerable
6113 //!
6114 // *******************************************************************************************
6115 
Vulnerable() const6116 bool gCycle::Vulnerable() const
6117 {
6118     return Alive() && lastTime > spawnTime_ + sg_cycleInvulnerableTime;
6119 }
6120