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