3
*************************************************************************
5
ArmageTron -- Just another Tron Lightcycle Game in 3D.
6
Copyright (C) 2000 Manuel Moos (manuel@moosnet.de)
8
**************************************************************************
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.
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.
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.
24
***************************************************************************
35
#include "tInitExit.h"
36
#include "tRecorder.h"
45
#include "gExplosion.h"
49
#include "eDebugLine.h"
61
// TODO: get rid of this
62
#include "tDirectories.h"
64
bool sg_gnuplotDebug = false;
67
static tSettingItem<bool> sg_("DEBUG_GNUPLOT",sg_gnuplotDebug);
70
// *****************************************************************
72
static nNOInitialisator<gCycle> cycle_init(320,"cycle");
74
// *****************************************************************
76
// static nVersionFeature sg_DoubleSpeed( 1 );
78
//tCONTROLLED_PTR(ePlayerNetID) lastEnemyInfluence; // the last enemy wall we encountered
79
//REAL lastTime; // the time it was drawn at
81
bool cycleprograminited=0;
83
// enemy influence settings
84
static REAL sg_enemyChatbotTimePenalty = 30.0f; //!< penalty for victim in chatbot mode
85
static REAL sg_enemyFriendTimePenalty = 2500.0f; //!< penalty for teammate influence
86
// REAL sg_enemySelfTimePenalty = 5000.0f; //!< penalty for self influence
87
static REAL sg_enemyDeadTimePenalty = 0.0f; //!< penalty for influence from dead players
88
static REAL sg_suicideTimeout = 10000.0f; //!< influences older than this don't count as kill cause
89
static REAL sg_enemyCurrentTimeInfluence = 0.0f; //!< blends in the current time into the relevant time
91
static tSettingItem<REAL> sg_enemyChatbotTimePenaltyConf( "ENEMY_CHATBOT_PENALTY", sg_enemyChatbotTimePenalty );
92
static tSettingItem<REAL> sg_enemyFriendTimePenaltyConf( "ENEMY_TEAMMATE_PENALTY", sg_enemyFriendTimePenalty );
93
static tSettingItem<REAL> sg_enemyDeadTimePenaltyConf( "ENEMY_DEAD_PENALTY", sg_enemyDeadTimePenalty );
94
static tSettingItem<REAL> sg_suicideTimeoutConf( "ENEMY_SUICIDE_TIMEOUT", sg_suicideTimeout );
95
static tSettingItem<REAL> sg_enemyCurrentTimeInfluenceConf( "ENEMY_CURRENTTIME_INFLUENCE", sg_enemyCurrentTimeInfluence );
97
// the last enemy possibly responsible for our death
98
const ePlayerNetID* gEnemyInfluence::GetEnemy() const
100
return lastEnemyInfluence.GetPointer();
103
REAL gEnemyInfluence::GetTime() const
108
gEnemyInfluence::gEnemyInfluence()
110
lastTime = -sg_suicideTimeout;
113
// add the result of the sensor scan to our data
114
void gEnemyInfluence::AddSensor( const gSensor& sensor, REAL timePenalty, gCycle * thisCycle )
116
// the client has no need for this, it does not execute AI code
117
if ( sn_GetNetState() == nCLIENT )
120
// check if the sensor hit an enemy wall
121
// if ( sensor.type != gSENSOR_ENEMY )
128
eWall* wall = sensor.ehit->GetWall();
132
AddWall( wall, sensor.before_hit, timePenalty, thisCycle );
135
// add the interaction with a wall to our data
136
void gEnemyInfluence::AddWall( const eWall * wall, eCoord const & pos, REAL timePenalty, gCycle * thisCycle )
138
// the client has no need for this, it does not execute AI code
139
if ( sn_GetNetState() == nCLIENT )
142
// see if it is a player wall
143
gPlayerWall const * playerWall = dynamic_cast<gPlayerWall const *>( wall );
147
// get the approximate time the wall was drawn
149
// try to get a more accurate value
150
if ( playerWall->Edge() )
152
// get the position of the collision point
153
alpha = playerWall->Edge()->Ratio( pos );
155
REAL timeBuilt = playerWall->Time( 0.5f );
157
AddWall( playerWall, timeBuilt - timePenalty, thisCycle );
160
// add the interaction with a wall to our data
161
void gEnemyInfluence::AddWall( const gPlayerWall * wall, REAL timeBuilt, gCycle * thisCycle )
163
// the client has no need for this, it does not execute AI code
164
if ( sn_GetNetState() == nCLIENT )
171
gCycle *cycle = wall->Cycle();
175
// don't count self influence
176
if ( thisCycle == cycle )
179
REAL time = timeBuilt;
182
REAL currentTime = thisCycle->LastTime();
183
time += ( currentTime - time ) * sg_enemyCurrentTimeInfluence;
187
ePlayerNetID* player = cycle->Player();
191
// don't accept milkers.
192
if ( thisCycle && !ePlayerNetID::Enemies( thisCycle->Player(), player ) )
197
// if the player is not our enemy, add extra time penalty
198
if ( thisCycle->Player() && player->CurrentTeam() == thisCycle->Player()->CurrentTeam() )
200
// the time shall be at most the time of the last turn, it should count as suicide if
201
// I drive into your three mile long wall
202
if ( time > cycle->GetLastTurnTime() )
203
time = cycle->GetLastTurnTime();
204
time -= sg_enemyFriendTimePenalty;
206
const ePlayerNetID* pInfluence = this->lastEnemyInfluence.GetPointer();
208
// calculate effective last time. Add malus if the player is dead.
209
REAL lastEffectiveTime = lastTime;
210
if ( !pInfluence || !pInfluence->Object() || !pInfluence->Object()->Alive() )
212
lastEffectiveTime -= sg_enemyDeadTimePenalty;
215
// same for the current influence
216
REAL effectiveTime = time;
217
if ( !cycle->Alive() )
219
effectiveTime -= sg_enemyDeadTimePenalty;
222
// if the new influence is newer, take it.
223
if ( effectiveTime > lastEffectiveTime || !bool(lastEnemyInfluence) )
225
lastEnemyInfluence = player;
230
static float sg_cycleSyncSmoothTime = .1f;
231
static tSettingItem<float> conf_smoothTime ("CYCLE_SMOOTH_TIME", sg_cycleSyncSmoothTime);
233
static float sg_cycleSyncSmoothMinSpeed = .2f;
234
static tSettingItem<float> conf_smoothMinSpeed ("CYCLE_SMOOTH_MIN_SPEED", sg_cycleSyncSmoothMinSpeed);
236
static float sg_cycleSyncSmoothThreshold = .2f;
237
static tSettingItem<float> conf_smoothThreshold ("CYCLE_SMOOTH_THRESHOLD", sg_cycleSyncSmoothThreshold);
239
static inline void clamp(REAL &c, REAL min, REAL max){
253
REAL gCycle::wallsStayUpDelay=8.0f; // the time the cycle walls stay up ( negative values: they stay up forever )
255
REAL gCycle::wallsLength=-1.0f; // the maximum total length of the walls
256
REAL gCycle::explosionRadius=4.0f; // the radius of the holes blewn in by an explosion
258
static nSettingItem<REAL> *c_pwsud = NULL, *c_pwl = NULL, *c_per = NULL;
260
void gCycle::PrivateSettings()
262
static nSettingItem<REAL> c_wsud("CYCLE_WALLS_STAY_UP_DELAY",wallsStayUpDelay);
263
static nSettingItem<REAL> c_wl("CYCLE_WALLS_LENGTH",wallsLength);
264
static nSettingItem<REAL> c_er("CYCLE_EXPLOSION_RADIUS",explosionRadius);
271
// sound speed divisor
272
static REAL sg_speedCycleSound=15;
273
static nSettingItem<REAL> c_ss("CYCLE_SOUND_SPEED",
276
// time after spawning it takes the cycle to start building a wall
277
static REAL sg_cycleWallTime=0.0;
278
static nSettingItemWatched<REAL>
279
sg_cycleWallTimeConf("CYCLE_WALL_TIME",
281
nConfItemVersionWatcher::Group_Bumpy,
284
// time after spawning during which a cycle can't be killed
285
static REAL sg_cycleInvulnerableTime=0.0;
286
static nSettingItemWatched<REAL>
287
sg_cycleInvulnerableTimeConf("CYCLE_INVULNERABLE_TIME",
288
sg_cycleInvulnerableTime,
289
nConfItemVersionWatcher::Group_Bumpy,
292
// time after spawning during which a cycle can't be killed
293
static bool sg_cycleFirstSpawnProtection=false;
294
static nSettingItemWatched<bool>
295
sg_cycleFirstSpawnProtectionConf("CYCLE_FIRST_SPAWN_PROTECTION",
296
sg_cycleFirstSpawnProtection,
297
nConfItemVersionWatcher::Group_Bumpy,
300
// time intevals between server-client syncs
301
static REAL sg_syncIntervalEnemy=1;
302
static tSettingItem<REAL> c_sie( "CYCLE_SYNC_INTERVAL_ENEMY",
303
sg_syncIntervalEnemy );
305
static REAL sg_syncIntervalSelf=.1;
306
static tSettingItem<REAL> c_sis( "CYCLE_SYNC_INTERVAL_SELF",
307
sg_syncIntervalSelf );
309
// flag controlling case-by-case-niceness for older clients
310
static bool sg_avoidBadOldClientSync=true;
311
static tSettingItem<bool> c_sbs( "CYCLE_AVOID_OLDCLIENT_BAD_SYNC",
312
sg_avoidBadOldClientSync );
314
// fast forward factor for extrapolating sync
315
static REAL sg_syncFF=10;
316
static tSettingItem<REAL> c_sff( "CYCLE_SYNC_FF",
319
static int sg_syncFFSteps=1;
320
static tSettingItem<int> c_sffs( "CYCLE_SYNC_FF_STEPS",
323
// client side bugfix: local tunneling is avoided on syncs
324
static nVersionFeature sg_NoLocalTunnelOnSync( 4 );
326
static REAL sg_GetSyncIntervalSelf( gCycle* cycle )
328
if ( sg_NoLocalTunnelOnSync.Supported( cycle->Owner() ) )
329
return sg_syncIntervalSelf;
331
return sg_syncIntervalEnemy * 10;
335
//static bool moviepack_hack=false; // do we use it?
336
//static tSettingItem<bool> ump("MOVIEPACK_HACK",moviepack_hack);
340
static int score_die=-2;
341
static tSettingItem<int> s_d("SCORE_DIE",score_die);
343
static int score_kill=3;
344
static tSettingItem<int> s_k("SCORE_KILL",score_kill);
346
static int score_suicide=-4;
347
static tSettingItem<int> s_s("SCORE_SUICIDE",score_suicide);
351
uActionPlayer gCycle::s_brake("CYCLE_BRAKE", -5);
352
static uActionPlayer s_brakeToggle("CYCLE_BRAKE_TOGGLE", -5);
354
static eWavData cycle_run("moviesounds/engine.wav","sound/cyclrun.wav");
355
static eWavData turn_wav("moviesounds/cycturn.wav","sound/expl.wav");
356
static eWavData scrap("sound/expl.wav");
358
// a class of textures where the transparent part of the
359
// image is replaced by the player color
360
class gTextureCycle: public rSurfaceTexture
362
gRealColor color_; // player color
363
bool wheel; // wheel or body
365
gTextureCycle(rSurface const & surface, const gRealColor& color,bool repx=0,bool repy=0,bool wheel=false);
367
virtual void ProcessImage(SDL_Surface *im);
369
virtual void OnSelect(bool enforce);
372
gTextureCycle::gTextureCycle(rSurface const & surface, const gRealColor& color,bool repx,bool repy,bool w)
373
:rSurfaceTexture(rTextureGroups::TEX_OBJ,surface,repx,repy),
374
color_(color),wheel(w)
379
void gTextureCycle::ProcessImage(SDL_Surface *im)
382
// blend transparent texture parts with cycle color
383
tVERIFY(im->format->BytesPerPixel == 4);
384
GLubyte R=int(color_.r*255);
385
GLubyte G=int(color_.g*255);
386
GLubyte B=int(color_.b*255);
388
GLubyte *pixels =reinterpret_cast<GLubyte *>(im->pixels);
390
for(int i=im->w*im->h-1;i>=0;i--){
391
GLubyte alpha=pixels[4*i+3];
392
pixels[4*i ] = (alpha * pixels[4*i ] + (255-alpha)*R) >> 8;
393
pixels[4*i+1] = (alpha * pixels[4*i+1] + (255-alpha)*G) >> 8;
394
pixels[4*i+2] = (alpha * pixels[4*i+2] + (255-alpha)*B) >> 8;
400
void gTextureCycle::OnSelect(bool enforce){
402
rISurfaceTexture::OnSelect(enforce);
404
if(rTextureGroups::TextureMode[rTextureGroups::TEX_OBJ]<0){
405
REAL R=color_.r,G=color_.g,B=color_.b;
412
GLfloat color[4]={R,G,B,1};
414
glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,color);
415
glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,color);
421
// *****************************************************************
423
static void sg_ArchiveCoord( eCoord & coord, int level )
425
static char const * section = "_COORD";
426
tRecorderSync< eCoord >::Archive( section, level, coord );
429
static void sg_ArchiveReal( REAL & real, int level )
431
static char const * section = "_REAL";
432
tRecorderSync< REAL >::Archive( section, level, real );
435
// *****************************************************************
439
// *****************************************************************
442
// take pos,dir and time from a cycle
443
gDestination::gDestination(const gCycleMovement &c)
455
gDestination::gDestination(const gCycle &c)
468
gDestination::gDestination(nMessage &m)
469
:gameTime(0),distance(0),speed(0),
473
next(NULL),list(NULL){
478
unsigned short flags;
480
braking = flags & 0x01;
481
chatting = flags & 0x02;
483
messageID = m.MessageID();
488
void gDestination::CopyFrom(const gCycleMovement &other)
490
position = other.Position();
491
direction = other.Direction();
492
gameTime = other.LastTime();
493
distance = other.GetDistance();
494
speed = other.Speed();
495
braking = other.GetBraking();
496
turns = other.GetTurns();
499
if (!finite(gameTime) || !finite(speed) || !finite(distance))
502
if ( other.Owner() && other.Player() )
503
chatting = other.Player()->IsChatting();
506
void gDestination::CopyFrom(const gCycle &other)
508
CopyFrom( static_cast<const gCycleMovement&>(other) );
511
distance += other.correctDistanceSmooth;
514
// *******************************************************************************************
518
// *******************************************************************************************
520
//! @param other the destination to compare with
521
//! @return -1 if this destination came earler, +1 if other was earler, 0 if no difference can be found
523
// *******************************************************************************************
525
int gDestination::CompareWith( const gDestination & other ) const
527
// 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
529
// compare message IDs with overflow ( if both are at good values )
530
if ( messageID > 1 && other.messageID > 1 )
532
short messageIDDifference = messageID - other.messageID;
533
if ( messageIDDifference < 0 )
535
if ( messageIDDifference > 0 )
539
// compare travelled distance
540
if ( distance < other.distance )
542
else if ( distance > other.distance )
545
// no relevant difference found
549
class gFloatCompressor
552
gFloatCompressor( REAL min, REAL max )
553
: min_( min ), max_( max ){}
555
// write compressed float to message
556
void Write( nMessage& m, REAL value ) const
558
clamp( value, min_, max_ );
559
unsigned short compressed = static_cast< unsigned short > ( maxShort_ * ( value - min_ )/( max_ - min_ ) );
560
m.Write( compressed );
563
REAL Read( nMessage& m ) const
565
unsigned short compressed;
566
m.Read( compressed );
568
return min_ + compressed * ( max_ - min_ )/maxShort_;
571
REAL min_, max_; // minimal and maximal expected value
573
static const unsigned short maxShort_;
578
const unsigned short gFloatCompressor::maxShort_ = 0xFFFF;
580
static gFloatCompressor compressZeroOne( 0, 1 );
582
// write all the data into a nMessage
583
void gDestination::WriteCreate(nMessage &m){
588
unsigned short flags = 0;
595
// store message ID for later reference
596
messageID = m.MessageID();
599
gDestination *gDestination::RightBefore(gDestination *list, REAL dist){
600
if (!list || list->distance > dist)
603
gDestination *ret=list;
604
while (ret && ret->next && ret->next->distance < dist)
610
gDestination *gDestination::RightAfter(gDestination *list, REAL dist){
614
gDestination *ret=list;
615
while (ret && ret->distance < dist)
621
// insert yourself into a list ordered by gameTime
622
void gDestination::InsertIntoList(gDestination **l){
628
// let message find its place
629
while (l && *l && CompareWith( **l ) > 0 )
642
void gDestination::RemoveFromList(){
647
while (list && *list && *list != this)
648
list = &((*list)->next);
659
// z-man: HUH? I don't understand the code above any more and it seems to be leaky.
660
// This simple alternative sounds better:
671
// *******************************************************************************************
675
// *******************************************************************************************
677
//! @return game time of the command
679
// *******************************************************************************************
681
REAL gDestination::GetGameTime( void ) const
683
return this->gameTime;
686
// *******************************************************************************************
690
// *******************************************************************************************
692
//! @param gameTime game time of the command to fill
693
//! @return A reference to this to allow chaining
695
// *******************************************************************************************
697
gDestination const & gDestination::GetGameTime( REAL & gameTime ) const
699
gameTime = this->gameTime;
703
// *******************************************************************************************
707
// *******************************************************************************************
709
//! @param gameTime game time of the command to set
710
//! @return A reference to this to allow chaining
712
// *******************************************************************************************
714
gDestination & gDestination::SetGameTime( REAL gameTime )
716
this->gameTime = gameTime;
720
// ********************************************************
722
static void new_destination_handler(nMessage &m)
724
// read the destination
725
gDestination *dest=new gDestination(m);
727
// and the ID of the cycle the destination is added to
728
unsigned short cycle_id;
730
nNetObject *o=nNetObject::ObjectDangerous(cycle_id);
731
if (o && &o->CreatorDescriptor() == &cycle_init){
732
if ((sn_GetNetState() == nSERVER) && (m.SenderID() != o->Owner()))
734
Cheater(m.SenderID());
737
if(o->Owner() != ::sn_myNetID)
739
gCycle* c = dynamic_cast<gCycle *>(o);
742
c->AddDestination(dest);
743
if ( c->Player() && !dest->Chatting() )
744
c->Player()->Activity();
746
// read game time from message
747
REAL gameTime = se_GameTime()+c->Lag()*3;
751
// uncomment to test sync error compensation code on client :)
754
dest->SetGameTime( gameTime );
760
// delete the destination if it has not been used
761
// TODO: exception safety!
765
static nDescriptor destination_descriptor(321,&new_destination_handler,"destinaton");
767
static void BroadCastNewDestination(gCycleMovement *c, gDestination *dest){
768
nMessage *m=new nMessage(destination_descriptor);
769
dest->WriteCreate(*m);
771
*m << dest->GetGameTime();
775
void gCycle::OnNotifyNewDestination( gDestination* dest )
778
if ( sg_gnuplotDebug && Player() )
780
std::ofstream f( Player()->GetUserName() + "_sync", std::ios::app );
781
f << dest->position.x << " " << dest->position.y << "\n";
785
gCycleMovement::OnNotifyNewDestination( dest );
787
// if (sn_GetNetState()==nSERVER || ::sn_myNetID == owner)
788
if (sn_GetNetState()==nCLIENT && ::sn_myNetID == Owner())
789
BroadCastNewDestination(this,dest);
793
// add destination and try to squeeze it into the schedule.
794
extrapolator_->NotifyNewDestination( dest );
797
// start a new simulation in any case. The server may simulate the movement a bit differently at the turn.
798
// resimulate_ = ( sn_GetNetState() == nCLIENT );
802
// *******************************************************************************************
806
// *******************************************************************************************
808
//! @param wall the wall the other cycle is grinding
810
// *******************************************************************************************
812
void gCycle::OnDropTempWall( gPlayerWall * wall )
816
// drop the current wall if eiter this or the last wall is grinded
817
gNetPlayerWall * nw = wall->NetWall();
818
if ( nw == currentWall || ( nw == lastWall && currentWall && currentWall->Vec().NormSquared() > EPS ) )
820
// just request the drop, Timestep() will execute it later
821
dropWallRequested_ = true;
825
// ************************************************************
830
// ************************************************************
833
// the time the cycle walls stay up ( negative values: they stay up forever )
834
void gCycle::SetWallsStayUpDelay ( REAL delay )
836
c_pwsud->Set( delay );
839
// how much rubber usage shortens the walls
840
static REAL sg_cycleRubberWallShrink = 0;
841
static nSettingItemWatched<REAL>
842
sg_cycleRubberWallShrinkConf("CYCLE_RUBBER_WALL_SHRINK",
843
sg_cycleRubberWallShrink,
844
nConfItemVersionWatcher::Group_Bumpy,
847
// make walls grow with distance traveled
848
static REAL sg_cycleDistWallShrink = 0;
849
static nSettingItemWatched<REAL>
850
sg_cycleDistWallShrinkConf("CYCLE_DIST_WALL_SHRINK",
851
sg_cycleDistWallShrink,
852
nConfItemVersionWatcher::Group_Bumpy,
855
static REAL sg_cycleDistWallShrinkOffset = 0;
856
static nSettingItemWatched<REAL>
857
sg_cycleDistWallShrinkOffsetConf("CYCLE_DIST_WALL_SHRINK_OFFSET",
858
sg_cycleDistWallShrinkOffset,
859
nConfItemVersionWatcher::Group_Bumpy,
862
// calculates the effect of driving distance to wall length
863
static REAL sg_CycleWallLengthFromDist( REAL distance )
865
REAL len = gCycle::WallsLength();
867
// make base length longer or shorter, depending on the sign of sg_cycleDistWallShrink
868
REAL d = sg_cycleDistWallShrinkOffset - distance;
870
len -= sg_cycleDistWallShrink * d;
875
// the current length of the walls of this cycle
876
REAL gCycle::ThisWallsLength() const
878
// get distance influence
879
REAL len = sg_CycleWallLengthFromDist( distance );
881
// apply rubber shortening
882
return len - GetRubber() * sg_cycleRubberWallShrink;
885
// the maximum total length of the walls
886
REAL gCycle::MaxWallsLength() const
888
REAL len = sg_CycleWallLengthFromDist( distance );
890
// exception: if the wall grows faster than it receeds, take the maximum, because the wall will
892
if ( sg_cycleDistWallShrink > 1 )
897
// if the wall grows from rubber use, add the maximal growth
898
if ( sg_cycleRubberWallShrink >= 0 || sg_rubberCycle < 0 )
901
return len - sg_cycleRubberWallShrink * sg_rubberCycle;
904
// the maximum total length of the walls
905
void gCycle::SetWallsLength ( REAL length)
907
c_pwl->Set( length );
910
// the radius of the holes blewn in by an explosion
911
void gCycle::SetExplosionRadius ( REAL radius)
913
c_per->Set( radius );
916
// *****************************************************************
918
// copies relevant info from other cylce
919
void gCycleExtrapolator::CopyFrom( const gCycleMovement& other )
922
gCycleMovement::CopyFrom( other );
928
trueDistance_ = GetDistance();
931
// copies relevant info from sync data and everything else from other cycle
932
void gCycleExtrapolator::CopyFrom( const SyncData& sync, const gCycle& other )
935
gCycleMovement::CopyFrom( sync, other );
937
//eFace* face1 = currentFace;
938
MoveSafely( other.lastGoodPosition_, sync.time, sync.time );
939
//eFace* face2 = currentFace;
940
MoveSafely( sync.pos, sync.time, sync.time );
943
if ( face1 != face2 || face1 != currentFace )
946
MoveSafely( sync.lastTurn *.01 + sync.pos * .99, sync.time, sync.time );
947
MoveSafely( sync.pos, sync.time, sync.time );
955
lastTurnPos_ = sync.lastTurn;
958
trueDistance_ = GetDistance();
961
gCycleExtrapolator::gCycleExtrapolator(eGrid *grid, const eCoord &pos,const eCoord &dir,ePlayerNetID *p,bool autodelete)
962
:gCycleMovement( grid, pos, dir, p, autodelete )
966
// an extrapolator should not be visible as a gameobject from the outside
970
// gCycleExtrapolator::gCycleExtrapolator(nMessage &m);
971
gCycleExtrapolator::~gCycleExtrapolator()
976
// returns the current destination
977
gDestination* gCycleExtrapolator::GetCurrentDestination() const
979
return currentDestination;
981
// the code below does not appear to be such a good idea after all...
982
if ( currentDestination )
984
return currentDestination;
990
// return a destination with the current parent position
991
static gDestination parentPos( *parent_ );
992
parentPos.missable = false;
993
parentPos.CopyFrom( *parent_ );
999
bool gCycleExtrapolator::EdgeIsDangerous(const eWall *ww, REAL time, REAL alpha ) const
1001
// ignore temporary walls, they may not be real
1002
const gPlayerWall *w = dynamic_cast<const gPlayerWall*>(ww);
1005
gNetPlayerWall* nw = w->NetWall();
1006
if ( nw && nw->Preliminary() && w->Cycle() == parent_ )
1009
// get time the wall was built
1010
REAL builtTime = w->Time(alpha);
1012
// is the wall built in the future ( happens during extrapolation )
1013
if ( builtTime > time )
1016
// ignore recent walls of parent cycle
1017
gCycle *otherPlayer=w->Cycle();
1018
if (otherPlayer==parent_ &&
1019
( time < builtTime + 4 * GetTurnDelay() || GetDistance() < w->Pos( alpha ) + .01 * sg_delayCycle*Speed()/SpeedMultiplier() )
1025
return parent_->EdgeIsDangerous( ww, time, alpha ) && gCycleMovement::EdgeIsDangerous( ww, time, alpha );
1028
bool gCycleExtrapolator::TimestepCore(REAL currentTime)
1030
// determine a suitable next destination
1031
gDestination destDefault( *parent_ ), *dest=&destDefault;
1032
if ( GetCurrentDestination() )
1034
dest = GetCurrentDestination();
1038
distance = dest->distance - DistanceToDestination( *dest );
1039
REAL distanceBefore = GetDistance();
1042
bool ret = gCycleMovement::TimestepCore( currentTime );
1044
// update true distance
1045
trueDistance_ += GetDistance() - distanceBefore;
1051
bool gCycleExtrapolator::DoTurn( int dir )
1054
return gCycleMovement::DoTurn( dir );
1058
nDescriptor &gCycleExtrapolator::CreatorDescriptor() const{
1059
// should never be called
1064
// *****************************************************************
1066
const eTempEdge* gCycle::Edge(){
1068
return currentWall->Edge();
1073
const gPlayerWall* gCycle::CurrentWall(){
1075
return currentWall->Wall();
1081
const gPlayerWall* gCycle::LastWall(){
1083
return lastWall->Wall();
1090
static tString lala_bodyTexture("Anonymous/original/textures/cycle_body.png");
1091
static nSettingItem<tString> gg_tex("TEXTURE_CYCLE_BODY", lala_bodyTexture);
1092
//static tConfItemLine g_tex("CYCLE_BODY", sg_bodyTexture);
1093
//static tSettingItem<tString> gg_tex("TEXTURE_CYCLE_BODY", sg_bodyTexture);
1094
static tString lala_wheelTexture("Anonymous/original/textures/cycle_wheel.png");
1095
static nSettingItem<tString> gg_wheelTexture("TEXTURE_CYCLE_WHEEL", lala_wheelTexture);
1097
static tString lala_bikeTexture("Anonymous/original/moviepack/bike.png");
1098
static nSettingItem<tString> lalala_bikeTexture("TEXTURE_MP_BIKE", lala_bikeTexture);
1101
// HACK! Flexible model loader that eats anything a moviepack currently can throw at it.
1103
struct gCycleVisuals
1105
rModel *customModel, *bodyModel, *frontModel, *rearModel; // cycle models
1106
gTextureCycle *customTexture, *bodyTexture, *wheelTexture; // cycle textures
1107
gRealColor color; // cycle color
1108
bool mpType; // true if moviepack type model/texture can be used
1109
int mpPreference; // the user preference for the texture/model search path
1111
gCycleVisuals( gRealColor const & a_color )
1113
customModel = bodyModel = frontModel = rearModel = 0;
1114
customTexture = bodyTexture = wheelTexture = 0;
1128
delete customTexture;
1130
delete wheelTexture;
1133
enum Slot{ SLOT_CUSTOM=0, SLOT_BODY=1, SLOT_WHEEL=2, SLOT_MAX=3 };
1135
// loads a specific texture from a specific folder
1136
static rSurface * LoadTextureSafe2( Slot slot, int mp )
1138
static std::auto_ptr<rSurface> cache[SLOT_MAX][2];
1139
std::auto_ptr<rSurface> & surface = cache[slot][mp];
1140
if ( surface.get() == NULL )
1142
static char const * names[SLOT_MAX]={"bike.png","cycle_body.png", "cycle_wheel.png"};
1143
char const * name = names[slot];
1145
char const * folder = mp ? "moviepack" : "textures";
1146
tString file = tString(folder) + "/" + name;
1148
surface = std::auto_ptr<rSurface> ( tNEW( rSurface( file ) ) );
1151
if ( surface->GetSurface() )
1152
return surface.get();
1157
// load one texture from the prefered folder (or the other one as fallback)
1158
gTextureCycle * LoadTextureSafe( Slot slot, bool wheel )
1160
rSurface * surface = LoadTextureSafe2( slot, mpPreference );
1162
surface = LoadTextureSafe2( slot, 1-mpPreference );
1165
return tNEW( gTextureCycle )( *surface, color, 0, 0, wheel );
1170
// load textures from the specified folder (or the other one) and the format the model has just been read for
1175
if ( !customTexture )
1176
customTexture = LoadTextureSafe( SLOT_CUSTOM, false );
1178
return customTexture;
1183
bodyTexture = LoadTextureSafe( SLOT_BODY, false );
1184
if ( !wheelTexture )
1185
wheelTexture = LoadTextureSafe( SLOT_WHEEL, true );
1187
return bodyTexture && wheelTexture;
1191
// loads a model, checking before if the file exists
1192
static rModel * LoadModelSafe( char const * filename )
1195
if ( tDirectories::Data().Open( in, filename ) )
1197
return tNEW(rModel( filename ));
1202
// load a model of specified type from a specified directory
1203
bool LoadModel( bool a_mpType, bool mpFolder )
1206
char const * folder = mpFolder ? "moviepack" : "models";
1210
tString base = tString(folder);
1212
if (!customModel) customModel = LoadModelSafe( base + ".ASE" );
1213
if (!customModel) customModel = LoadModelSafe( base + ".ase" );
1215
return customModel && LoadTextures();
1219
tString base = tString(folder) + "/cycle_";
1221
if (!bodyModel) bodyModel = LoadModelSafe( base + "body.mod" );
1222
if (!frontModel) frontModel = LoadModelSafe( base + "front.mod" );
1223
if (!rearModel) rearModel = LoadModelSafe( base + "rear.mod" );
1225
return bodyModel && frontModel && rearModel && LoadTextures();
1229
// tries to load everything from the given data folder
1230
bool LoadModel2( bool mp )
1232
// first, try the right type, then the 'unnatural' choice
1233
return LoadModel( mp, mp ) || LoadModel( !mp, mp );
1236
// top level load function: tries to load all variations, starting with passed moviepack folder flag
1237
bool LoadModel( bool mp )
1239
mpPreference = mp ? 1 : 0;
1241
// delegate to try loading both formats from both directories
1242
return LoadModel2( mp ) || LoadModel2( !mp );
1247
void gCycle::MyInitAfterCreation(){
1248
dropWallRequested_ = false;
1249
lastGoodPosition_ = pos;
1252
// con << "creating cycle.\n";
1254
engine = tNEW(eSoundPlayer)(cycle_run,true);
1255
turning = tNEW(eSoundPlayer)(turn_wav);
1256
spark = tNEW(eSoundPlayer)(scrap);
1258
//correctDistSmooth=correctTimeSmooth=correctSpeedSmooth=0;
1259
correctDistanceSmooth = 0;
1261
resimulate_ = false;
1268
timeCameIntoView = 0;
1276
customTexture = NULL;
1278
correctPosSmooth=eCoord(0,0);
1280
rotationFrontWheel=rotationRearWheel=eCoord(1,0);
1286
if (sn_GetNetState()!=nCLIENT){
1288
{ // distribute AI colors
1289
// con << current_ai << ':' << take_ai << ':' << maxmindist << "\n\n\n";
1300
player->Color(color_.r,color_.g,color_.b);
1301
player->TrailColor(trailColor_.r,trailColor_.g,trailColor_.b);
1304
se_MakeColorValid( color_.r, color_.g, color_.b, 1.0f );
1305
se_MakeColorValid( trailColor_.r, trailColor_.g, trailColor_.b, .5f );
1308
// load model and texture
1310
gCycleVisuals visuals( color_ );
1311
if ( !visuals.LoadModel( mp ) )
1313
tERR_ERROR( "Neither classic style nor moviepack style model and textures found. "
1314
"The folders \"textures\" and \"moviepack\" need to contain either "
1315
"cycle.ase and bike.png or body.mod, front.mod, rear.mod, body.png and wheel.png." );
1318
mp = visuals.mpType;
1320
// transfer models and textures
1323
// use moviepack style body and texture
1324
customModel = visuals.customModel;
1325
visuals.customModel = 0;
1326
customTexture = visuals.customTexture;
1327
visuals.customTexture = 0;
1331
// use classic style body and texture
1332
body = visuals.bodyModel;
1333
visuals.bodyModel = 0;
1334
front = visuals.frontModel;
1335
visuals.frontModel = 0;
1336
rear = visuals.rearModel;
1337
visuals.rearModel = 0;
1338
bodyTex = visuals.bodyTexture;
1339
visuals.bodyTexture = 0;
1340
wheelTex = visuals.wheelTexture;
1341
visuals.wheelTexture = 0;
1343
tASSERT ( body && front && rear && bodyTex && wheelTex );
1350
the old, less flexible, loading code
1354
customModel=new rModel("moviepack/cycle.ASE","moviepack/cycle.ase");
1357
body=new rModel("models/cycle_body.mod");
1358
front=new rModel("models/cycle_front.mod");
1359
rear=new rModel("models/cycle_rear.mod");
1364
// static rSurface bike(lala_bikeTexture);
1365
static rSurface bike("moviepack/bike.png");
1366
customTexture=new gTextureCycle(bike,color_,0,0);
1369
static rSurface body("textures/cycle_body.png");
1370
static rSurface wheel("textures/cycle_wheel.png");
1371
//static rSurface body((const char*) lala_bodyTexture);
1372
//static rSurface wheel((const char*) lala_wheelTexture);
1373
wheelTex=new gTextureCycle(wheel,color_,0,0,true);
1374
bodyTex=new gTextureCycle(body,color_,0,0);
1379
//con << "Created cycle.\n";
1381
nextSyncOwner=nextSync=tSysTimeFloat()-1;
1383
if (sn_GetNetState()!=nCLIENT)
1386
grid->AddGameObjectInteresting(this);
1388
spawnTime_=lastTimeAnim=lastTime;
1389
// set spawn time to infinite past if this is the first spawn
1390
if ( !sg_cycleFirstSpawnProtection && spawnTime_ <= 1.0 )
1392
spawnTime_ = -1E+20;
1397
engine->Reset(10000);
1402
nextChatAI=lastTime;
1408
if ( sg_gnuplotDebug && Player() )
1410
std::ofstream f( Player()->GetUserName() + "_step" );
1411
f << pos.x << " " << pos.y << "\n";
1412
std::ofstream g( Player()->GetUserName() + "_sync" );
1413
g << pos.x << " " << pos.y << "\n";
1414
std::ofstream h( Player()->GetUserName() + "_turn" );
1415
h << pos.x << " " << pos.y << "\n";
1420
void gCycle::InitAfterCreation(){
1422
if (!finite(Speed()))
1425
gCycleMovement::InitAfterCreation();
1427
if (!finite(Speed()))
1430
MyInitAfterCreation();
1433
gCycle::gCycle(eGrid *grid, const eCoord &pos,const eCoord &d,ePlayerNetID *p,bool autodelete)
1434
:gCycleMovement(grid, pos,d,p,autodelete),
1438
rotationFrontWheel(1,0),rotationRearWheel(1,0),heightFrontWheel(0),heightRearWheel(0),
1442
windingNumberWrapped_ = windingNumber_ = Grid()->DirectionWinding(dirDrive);
1443
dirDrive = Grid()->GetDirection(windingNumberWrapped_);
1446
lastNetWall=lastWall=currentWall=NULL;
1448
MyInitAfterCreation();
1450
sg_ArchiveCoord( this->dirDrive, 1 );
1451
sg_ArchiveCoord( this->dir, 1 );
1452
sg_ArchiveCoord( this->pos, 1 );
1453
sg_ArchiveReal( this->verletSpeed_, 1 );
1458
// con << "deleting cylce...\n";
1460
// clear the destination list
1466
this->RemoveFromGame();
1470
delete customTexture;
1480
//con << "Deleted cycle.\n";
1488
void gCycle::RemoveFromGame()
1490
// keep this cycle alive
1491
tJUST_CONTROLLED_PTR< gCycle > keep;
1493
if ( this->GetRefcount() > 0 )
1500
// really kill the cycle even on the client
1501
if ( this->Alive() )
1504
tNEW(gExplosion)(grid, pos, lastTime, color_);
1509
currentWall->CopyIntoGrid( grid );
1513
gCycleMovement::RemoveFromGame();
1516
static inline void rotate(eCoord &r,REAL angle){
1520
r=r*(1/sqrt(r.NormSquared()));
1524
// Sparks have a large performance problem on Macs. See http://guru3.sytes.net/viewtopic.php?t=2167
1525
bool crash_sparks=false;
1527
bool crash_sparks=true;
1530
//static bool forceTime=false;
1533
extern REAL planned_rate_control[MAXCLIENTS+2];
1535
static REAL sg_minDropInterval=0.05;
1536
static tSettingItem< REAL > sg_minDropIntervalConf( "CYCLE_MIN_WALLDROP_INTERVAL", sg_minDropInterval );
1538
bool gCycle::Timestep(REAL currentTime){
1539
// drop current wall if it was requested
1540
if ( dropWallRequested_ )
1542
// but don't do so too often globally (performance)
1543
static double nextDrop = 0;
1544
double time = tSysTimeFloat();
1545
if ( time >= nextDrop )
1547
nextDrop = time + sg_minDropInterval;
1553
if ( sg_gnuplotDebug && Player() )
1555
std::ofstream f( Player()->GetUserName() + "_step", std::ios::app );
1556
f << pos.x << " " << pos.y << "\n";
1559
// timewarp test debug code
1560
//if ( Player() && Player()->IsHuman() )
1561
// currentTime -= .1;
1563
// don't timestep when you're dead
1566
if ( sn_GetNetState() == nSERVER )
1572
// and let yourself be removed from the lists so we don't have to go
1573
// through this again.
1577
// Debug archive position and speed
1578
sg_ArchiveCoord( pos, 7 );
1579
sg_ArchiveReal( verletSpeed_, 7 );
1581
if ( !destinationList && sn_GetNetState() == nCLIENT )
1583
// insert emergency first destination without broadcasting it
1584
gDestination* dest = tNEW(gDestination)(*this);
1585
dest->messageID = 0;
1586
dest->distance = -.001;
1587
dest->InsertIntoList(&destinationList);
1590
// start new extrapolation
1591
if ( !extrapolator_ && resimulate_ )
1592
ResetExtrapolator();
1594
// extrapolate state from server and copy state when finished
1595
if ( extrapolator_ )
1597
REAL dt = ( currentTime - lastTime ) * sg_syncFF / sg_syncFFSteps;
1600
//if ( !resimulate_ )
1603
for ( int i = sg_syncFFSteps - 1; i>= 0; --i )
1605
if ( Extrapolate( dt ) )
1607
SyncFromExtrapolator();
1613
// nothing special if simulating backwards
1614
if (currentTime < lastTime)
1615
return gCycleMovement::Timestep(currentTime);
1617
// no targets are given: activate chat AI
1618
if (!currentDestination && pendingTurns.empty()){
1619
if (currentTime>nextChatAI && bool(player) &&
1620
((player->IsChatting() && player->Owner() == sn_myNetID) ||
1621
(!player->IsActive() && sn_GetNetState() == nSERVER) )
1623
nextChatAI=currentTime+1;
1625
gSensor front(this,pos,dir);
1626
gSensor left(this,pos,dir.Turn(eCoord(0,1)));
1627
gSensor right(this,pos,dir.Turn(eCoord(0,-1)));
1629
REAL range=verletSpeed_*4;
1631
front.detect(range);
1633
right.detect(range);
1635
enemyInfluence.AddSensor( front, sg_enemyChatbotTimePenalty, this );
1636
enemyInfluence.AddSensor( right, sg_enemyChatbotTimePenalty, this );
1637
enemyInfluence.AddSensor( left, sg_enemyChatbotTimePenalty, this );
1640
if ( rand() > RAND_MAX / 4 )
1642
if ( rand() > RAND_MAX / 4 )
1644
if ( rand() > RAND_MAX / 4 )
1648
if (front.hit<left.hit*.9 || front.hit<right.hit*.9){
1650
if (front.type==gSENSOR_SELF) // NEVER close yourself in.
1652
lr-=left.hit-right.hit;
1654
Act(&se_turnRight,1);
1656
Act(&se_turnLeft,1);
1661
bool simulate=Alive();
1663
if ( !pendingTurns.empty() || currentDestination )
1670
return gCycleMovement::Timestep(currentTime);
1672
catch ( gCycleDeath const & death )
1674
KillAt( death.pos_ );
1683
// just basic movement to do: let base class handle that.
1684
gCycleMovement::Timestep( currentTime );
1687
// do the rest of the timestep
1688
return gCycleMovement::Timestep( currentTime );
1691
static void blocks(const gSensor &s, const gCycle *c, int lr)
1693
if ( nCLIENT == sn_GetNetState() )
1696
if (s.type == gSENSOR_RIM)
1697
gAIPlayer::CycleBlocksRim(c, lr);
1698
else if (s.type == gSENSOR_TEAMMATE || s.type == gSENSOR_ENEMY && s.ehit)
1700
gPlayerWall *w = dynamic_cast<gPlayerWall*>(s.ehit->GetWall());
1703
// int turn = c->Grid()->WindingNumber();
1704
// int halfTurn = turn >> 1;
1706
// calculate the winding number.
1707
int windingBefore = c->WindingNumber(); // we start driving in c's direction
1708
// we need to make a sharp turn in the lr-direction
1709
// windingBefore += lr * halfTurn;
1711
// after the transfer, we need to drive in the direction of the other
1713
int windingAfter = w->WindingNumber();
1715
// if the other wall drives in the opposite direction, we
1716
// need to turn around again:
1718
// windingAfter -= lr * halfTurn;
1720
// make the winding difference a multiple of the winding number
1722
int compensation = ((windingAfter - windingBefore - halfTurn) % turn)
1724
while (compensation < -halfTurn)
1725
compensation += turn;
1728
// only if the two walls are parallel/antiparallel, there is true blocking.
1729
if (((windingBefore - windingAfter) & 1) == 0)
1730
gAIPlayer::CycleBlocksWay(c, w->Cycle(),
1732
w->Pos(s.ehit->Ratio(s.before_hit)),
1733
- windingAfter + windingBefore);
1738
// lets a value decay smoothly
1739
static void DecaySmooth( REAL& smooth, REAL relSpeed, REAL minSpeed, REAL clamp )
1741
if ( fabs(smooth) > .01 )
1747
// increase correction speed if the value is out of bounds
1749
relSpeed *= ( 1 + smooth * smooth / ( clamp * clamp ) );
1757
// calculate speed from relative speed
1758
REAL speed = smooth * relSpeed;
1760
// apply minimal correction
1761
if ( fabs( speed ) < minSpeed )
1762
speed = copysign ( minSpeed , smooth );
1765
if ( fabs( speed ) > fabs( smooth ) )
1771
// clamps a cycle displacement to non-confusing values
1772
static REAL ClampDisplacement( gCycle* cycle, eCoord& displacement, const eCoord& lookout, const eCoord& pos )
1774
gSensor sensor( cycle, pos, lookout );
1776
if ( sensor.ehit && sensor.hit >= 0 && sensor.hit < 1 )
1780
gSensor sensor( cycle, pos, lookout );
1783
displacement = displacement * sensor.hit;
1788
bool gCycle::TimestepCore(REAL currentTime){
1791
if (!finite(skewDot))
1796
REAL ts=(currentTime-lastTime);
1799
//clamp(correctTimeSmooth, -100, 100);
1800
clamp(correctDistanceSmooth, -100, 100);
1801
//clamp(correctSpeedSmooth, -100, 100);
1803
// scale factor for smoothing. It is always used as
1804
// value += smooth * smooth_correction;
1805
// smooth_correction -= smooth * smooth_correction;
1811
// go a bit of the way
1812
smooth = 1 - 1/( 1 + ts / sg_cycleSyncSmoothTime );
1817
REAL scd = correctDistanceSmooth * smooth;
1819
correctDistanceSmooth -= scd;
1822
// apply smooth position correction
1824
//correctPosSmooth = eCoord(0,0);
1826
// correctPosSmooth = correctPosSmooth * ( 1 - smooth );
1830
REAL animts=currentTime-lastTimeAnim;
1831
if (animts<0 || !finite(animts))
1834
lastTimeAnim=currentTime;
1836
// handle decaying of smooth position correction
1838
// let components of smooth position correction decay
1839
REAL minSpeed = sg_cycleSyncSmoothMinSpeed * Speed() * animts;
1840
REAL clamp = sg_cycleSyncSmoothThreshold * Speed();
1842
DecaySmooth( correctPosSmooth.x, smooth, minSpeed, clamp );
1843
DecaySmooth( correctPosSmooth.y, smooth, minSpeed, clamp );
1846
if ( correctPosSmooth.NormSquared() > EPS )
1848
// cast ray to make sure corrected position lies on the right side of walls
1849
ClampDisplacement( this, correctPosSmooth, correctPosSmooth * 2, pos );
1851
// same for negative direction, players should see it when they are close to a wall
1852
ClampDisplacement( this, correctPosSmooth, -correctPosSmooth, pos );
1854
// cast another some rays into the future
1855
eCoord lookahead = dirDrive * ( 10 * correctPosSmooth.Norm() );
1856
ClampDisplacement( this, correctPosSmooth, lookahead + correctPosSmooth, pos );
1857
ClampDisplacement( this, correctPosSmooth, lookahead - correctPosSmooth, pos );
1858
lookahead = dirDrive * Speed();
1859
ClampDisplacement( this, correctPosSmooth, correctPosSmooth, pos + lookahead );
1860
ClampDisplacement( this, correctPosSmooth, - correctPosSmooth, pos + lookahead );
1867
rotate(rotationFrontWheel,2*verletSpeed_*animts/.43);
1868
rotate(rotationRearWheel,2*verletSpeed_*animts/.73);
1870
const REAL extension=.25;
1872
// REAL step=speed*ts; // +.5*acceleration*ts*ts;
1874
// animate cycle direction
1876
// move it a bit closer to dirDrive
1877
dir=dir+dirDrive*animts*verletSpeed_*3;
1879
// if it's too far away from dirDrive, clamp it
1880
eCoord dirDistance = dir - dirDrive;
1881
REAL dist = dirDistance.NormSquared();
1882
const REAL maxDist = .8;
1883
if (dirDistance.NormSquared() > maxDist)
1884
dir = dirDrive + dirDistance* (1/sqrt(dist/maxDist));
1887
dir=dir*(1/sqrt(dir.NormSquared()));
1891
eCoord oldPos = pos;
1894
// delegate core work to base class
1897
// start building wall
1898
REAL startBuildWallAt = spawnTime_ + sg_cycleWallTime;
1899
if ( !currentWall && currentTime > startBuildWallAt )
1901
// simulate right to the spot where the wall should begin
1902
if ( currentTime < startBuildWallAt )
1903
if ( gCycleMovement::TimestepCore( startBuildWallAt ) )
1906
// build the wall, modifying the spawn time to make sure it happens
1907
REAL lastSpawn = spawnTime_;
1908
spawnTime_ += -1E+20;
1910
spawnTime_ = lastSpawn;
1913
// simulate rest of frame
1914
if ( gCycleMovement::TimestepCore( currentTime ) )
1917
catch ( gCycleDeath const & death )
1919
KillAt( death.pos_ );
1921
// death exceptions are precise; we can safely take over the position from it
1922
oldPos = death.pos_;
1926
// die where you started
1929
MoveSafely(oldPos,currentTime,currentTime);
1934
// Debug archive position and speed
1935
sg_ArchiveCoord( pos, 7 );
1936
sg_ArchiveReal( verletSpeed_, 7 );
1941
eCoord wallEndPos = pos;
1943
// 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.
1944
//if ( sn_GetNetState() != nSERVER )
1945
// wallEndPos = pos + dirDrive * ( verletSpeed_ * se_PredictTime() );
1946
currentWall->Update(currentTime, wallEndPos );
1950
gSensor fl(this,pos,dirDrive.Turn(1,1));
1951
gSensor fr(this,pos,dirDrive.Turn(1,-1));
1953
fl.detect(extension*4);
1954
fr.detect(extension*4);
1956
enemyInfluence.AddSensor( fr, 0, this );
1957
enemyInfluence.AddSensor( fl, 0, this );
1960
blocks(fl, this, -1);
1963
blocks(fr, this, 1);
1966
if (fl.hit > extension)
1969
if (fr.hit > extension)
1972
REAL lr=(fl.hit-fr.hit)/extension;
1975
const REAL skewrelax=24;
1976
skewDot-=128*(skew+lr/2)*animts;
1977
skewDot/=1+skewrelax*animts;
1978
if (skew*skewDot>4) skewDot=4/skew;
1979
skew+=skewDot*animts;
1982
if ( skew > fr.hit * fac )
1984
skew = fr.hit * fac;
1986
if ( skew < -fl.hit * fac )
1988
skew = -fl.hit * fac;
1992
eCoord sparkpos,sparkdir;
1994
if (fl.ehit && fl.hit<extension){
1995
sparkpos=pos+dirDrive.Turn(1,1)*fl.hit;
1996
sparkdir=dirDrive.Turn(0,-1);
1998
if (fr.ehit && fr.hit<extension){
1999
// blocks(fr, this, -1);
2000
sparkpos=pos+dirDrive.Turn(1,-1)*fr.hit;
2001
sparkdir=dirDrive.Turn(0,1);
2005
if (crash_sparks && animts>0)
2006
new gSpark(pos,dirDrive,currentTime);
2008
if (fabs(skew)<fabs(lr*.8) ){
2009
skewDot-=lr*1000*animts;
2010
if (crash_sparks && animts>0)
2012
gPlayerWall *tmpplayerWall=0;
2014
if(fl.ehit) tmpplayerWall=dynamic_cast<gPlayerWall*>(fl.ehit->GetWall());
2015
if(fr.ehit) tmpplayerWall=dynamic_cast<gPlayerWall*>(fr.ehit->GetWall());
2018
gCycle *tmpcycle = tmpplayerWall->Cycle();
2021
new gSpark(grid, sparkpos-dirDrive*.1,sparkdir,currentTime,color_.r,color_.g,color_.b,tmpcycle->color_.r,tmpcycle->color_.g,tmpcycle->color_.b);
2024
new gSpark(grid, sparkpos-dirDrive*.1,sparkdir,currentTime,color_.r,color_.g,color_.b,1,1,1);
2031
if (fabs(skew)<fabs(lr*.9) ){
2032
skewDot-=lr*100*animts;
2033
if (crash_sparks && animts>0)
2035
gPlayerWall *tmpplayerWall=0;
2037
if(fl.ehit) tmpplayerWall=dynamic_cast<gPlayerWall*>(fl.ehit->GetWall());
2038
if(fr.ehit) tmpplayerWall=dynamic_cast<gPlayerWall*>(fr.ehit->GetWall());
2041
gCycle *tmpcycle = tmpplayerWall->Cycle();
2044
new gSpark(grid, sparkpos-dirDrive*.1,sparkdir,currentTime,color_.r,color_.g,color_.b,tmpcycle->color_.r,tmpcycle->color_.g,tmpcycle->color_.b);
2047
new gSpark(grid, sparkpos-dirDrive*.1,sparkdir,currentTime,color_.r,color_.g,color_.b,1,1,1);
2052
if (fl.hit+fr.hit<extension*.4)
2068
if ( sn_GetNetState()==nSERVER )
2070
if (nextSync < tSysTimeFloat() )
2072
// delay syncs for old clients when there is a wall ahead; they would tunnel locally
2073
REAL lookahead = Speed() * sg_syncIntervalEnemy*.5;
2074
if ( !sg_avoidBadOldClientSync || sg_NoLocalTunnelOnSync.Supported( Owner() ) || MaxSpaceAhead( this, ts, lookahead, lookahead ) >= lookahead )
2079
nextSync=tSysTimeFloat()+sg_syncIntervalEnemy;
2080
nextSyncOwner=tSysTimeFloat()+sg_GetSyncIntervalSelf( this );
2082
else if ( nextSyncOwner < tSysTimeFloat() &&
2084
sn_Connections[Owner()].bandwidthControl_.Control( nBandwidthControl::Usage_Planning ) > 200 )
2086
// sync only to the owner (provided there is enough bandwidth available)
2087
RequestSync(Owner(), false);
2088
nextSyncOwner=tSysTimeFloat()+sg_GetSyncIntervalSelf( this );
2096
void gCycle::InteractWith(eGameObject *target,REAL,int){
2098
if (alive && target->type()==ArmageTron_CYCLE){
2099
gCycle *c=(gCycle *)target;
2101
const eEdge *current_eEdge=Edge();
2102
const eEdge *other_eEdge=c->Edge();
2103
if(current_eEdge && other_eEdge){
2104
ePoint *meet=current_eEdge->IntersectWith(other_eEdge);
2105
if (meet){ // somebody is going to die!
2106
REAL ratio=current_eEdge->Ratio(*meet);
2107
REAL time=CurrentWall()->Time(ratio);
2108
PassEdge(other_eEdge,time,other_eEdge->Ratio(*meet),0);
2116
void gCycle::KillAt( const eCoord& pos){
2117
// find the killer from the enemy influence storage
2118
ePlayerNetID const * constHunter = enemyInfluence.GetEnemy();
2119
ePlayerNetID * hunter = Player();
2121
// cast away const the safe way
2122
if ( constHunter && constHunter->Object() )
2123
hunter = constHunter->Object()->Player();
2125
// only take it if it is not too old
2126
if ( LastTime() - enemyInfluence.GetTime() > sg_suicideTimeout )
2134
if (!Alive() || sn_GetNetState()==nCLIENT)
2137
#ifdef KRAWALL_SERVER
2138
if ( hunter && Player() &&
2139
!dynamic_cast<gAIPlayer*>(hunter) &&
2140
!dynamic_cast<gAIPlayer*>(Player()) &&
2141
hunter->IsAuth() && Player()->IsAuth())
2142
nKrawall::ServerFrag(hunter->GetUserName(), Player()->GetUserName());
2145
if (hunter==Player())
2150
ladderLog << "DEATH_SUICIDE " << hunter->GetUserName() << "\n";
2151
se_SaveToLadderLog( ladderLog );
2153
if ( score_suicide )
2154
hunter->AddScore(score_suicide, tOutput(), "$player_lose_suicide" );
2157
tColoredString hunterName;
2158
hunterName << *hunter << tColoredString::ColorString(1,1,1);
2159
sn_ConsoleOut( tOutput( "$player_free_suicide", hunterName ) );
2170
tColoredString preyName;
2171
preyName << *Player();
2172
preyName << tColoredString::ColorString(1,1,1);
2173
if (Player()->CurrentTeam() != hunter->CurrentTeam()) {
2175
ladderLog << "DEATH_FRAG " << Player()->GetUserName() << " " << hunter->GetUserName() << "\n";
2176
se_SaveToLadderLog( ladderLog );
2178
win.SetTemplateParameter(3, preyName);
2179
win << "$player_win_frag";
2180
if ( score_kill != 0 )
2181
hunter->AddScore(score_kill, win, lose );
2184
tColoredString hunterName;
2185
hunterName << *hunter << tColoredString::ColorString(1,1,1);
2186
sn_ConsoleOut( tOutput( "$player_free_frag", hunterName, preyName ) );
2191
ladderLog << "DEATH_TEAMKILL " << Player()->GetUserName() << " " << hunter->GetUserName() << "\n";
2192
se_SaveToLadderLog( ladderLog );
2194
tColoredString hunterName;
2195
hunterName << *hunter << tColoredString::ColorString(1,1,1);
2196
sn_ConsoleOut( tOutput( "$player_teamkill", hunterName, preyName ) );
2201
win << "$player_win_frag_ai";
2202
hunter->AddScore(score_kill, win, lose);
2205
// if (prey->player && (prey->player->CurrentTeam() != hunter->player->CurrentTeam()))
2207
Player()->AddScore(score_die,tOutput(),"$player_lose_frag");
2216
static bool justChecking;
2218
gJustChecking(){ justChecking = false; }
2219
~gJustChecking(){ justChecking = true; }
2222
bool gJustChecking::justChecking = true;
2224
bool gCycle::EdgeIsDangerous(const eWall* ww, REAL time, REAL a) const{
2225
gPlayerWall const * w = dynamic_cast< gPlayerWall const * >( ww );
2228
if ( !Vulnerable() )
2231
gNetPlayerWall *nw = w->NetWall();
2232
if( nw == currentWall || nw == lastWall || nw == lastNetWall )
2235
// see if the wall is from another player and from the future.
2236
// if it is, it's not dangerous, this cycle was here first.
2237
// on passing the wall later, the other cycle will be pushed back
2238
// to the collision point or killed if that is no longer possible.
2239
// z-man notes: I've got the vaque feeling something like this was
2240
// here before, but got thrown out again for making problems.
2241
if ( gJustChecking::justChecking && w->CycleMovement() != static_cast< const gCycleMovement * >( this ) && w->Time(a) > time )
2245
return gCycleMovement::EdgeIsDangerous( ww, time, a );
2248
void gCycle::PassEdge(const eWall *ww,REAL time,REAL a,int){
2250
// deactivate time check
2251
gJustChecking thisIsSerious;
2253
if (!EdgeIsDangerous(ww,time,a) || !Alive() )
2257
if (!EdgeIsDangerous(ww,time,a) || !Alive() )
2263
// keep other cycle around
2264
tJUST_CONTROLLED_PTR<gCycleExtrapolator> keepOther( extrapolator_ );
2266
ResetExtrapolator();
2268
// extrapolate state from server and copy state when finished
2270
for ( int i = 9; i>= 0; --i )
2275
extrapolator_ = keepOther;
2278
eCoord collPos = ww->Point( a );
2280
const gPlayerWall *w = dynamic_cast<const gPlayerWall*>(ww);
2282
enemyInfluence.AddWall( ww, collPos, 0, this );
2286
gCycle *otherPlayer=w->Cycle();
2288
REAL otherTime = w->Time(a);
2289
if(time < otherTime*(1-EPS))
2292
static bool tryToSaveFutureWallOwner = false;
2293
if ( tryToSaveFutureWallOwner && sn_GetNetState() != nCLIENT && w->NetWall() == otherPlayer->currentWall && otherPlayer->LastTime() < time + .5f )
2295
// teleport the other cycle back to the point before the collision; its next timestep
2296
// will simulate the collision again from the right viewpoint
2297
// determine the distance of the pushback
2298
REAL d = otherPlayer->GetDistanceSinceLastTurn() * .001;
2301
REAL maxd = eCoord::F( otherPlayer->dirDrive, collPos - otherPlayer->GetLastTurnPos() ) * .5;
2306
// err, trouble. Better kill the other player.
2308
otherPlayer->enemyInfluence.AddWall( currentWall->Wall(), lastTime, otherPlayer );
2309
otherPlayer->KillAt( collPos );
2313
otherPlayer->MoveSafely( collPos-otherPlayer->dirDrive*d, otherPlayer->LastTime(), otherTime - d/otherPlayer->Speed() );
2315
// drop our wall so collisions are more accurate
2316
dropWallRequested_ = true;
2320
// the unfortunate other player made a turn in the meantime or too much time has passed. We cannot unroll its movement,
2321
// so we have to destroy it.
2323
otherPlayer->enemyInfluence.AddWall( currentWall->Wall(), lastTime, otherPlayer );
2325
otherPlayer->KillAt( collPos );
2328
else if (time > w->Time(a)+EPS) // sad but true
2330
// this cycle has to die here unless it has rubber left
2331
throw gCycleDeath( collPos );
2333
// REAL dist = w->Pos( a );
2334
// const_cast<gPlayerWall*>(w)->BlowHole( dist - explosionRadius, dist + explosionRadius );
2339
if (bool(player) && sn_GetNetState()!=nCLIENT && Alive() )
2341
throw gCycleDeath( collPos );
2347
REAL gCycle::PathfindingModifier( const eWall *w ) const
2351
if (dynamic_cast<const gPlayerWall*>(w))
2358
bool gCycle::Act(uActionPlayer *Act, REAL x){
2359
// don't accept premature input
2360
if (se_mainGameTimer && ( se_mainGameTimer->speed <= 0 || se_mainGameTimer->Time() < -1 ) )
2363
if (!Alive() && sn_GetNetState()==nSERVER)
2366
if(se_turnLeft==*Act && x>.5){
2367
//SendControl(lastTime,&se_turnLeft,1);
2371
else if(se_turnRight==*Act && x>.5){
2372
//SendControl(lastTime,&se_turnRight,1);
2376
else if(s_brake==*Act){
2377
//SendControl(lastTime,&brake,x);
2378
unsigned short newBraking=(x>0);
2379
if ( braking != newBraking )
2381
braking = newBraking;
2386
else if(s_brakeToggle==*Act){
2397
// client side bugfix: network sync messages get actually used
2398
static nVersionFeature sg_SyncsAreUsed( 5 );
2400
// temporarily override driving directions on wall drops
2401
static eCoord const * sg_fakeDirDrive = NULL;
2402
class gFakeDirDriveSetter
2405
gFakeDirDriveSetter( eCoord const & dir )
2406
: lastFakeDir_( sg_fakeDirDrive )
2408
sg_fakeDirDrive = &dir;
2411
~gFakeDirDriveSetter()
2413
sg_fakeDirDrive = lastFakeDir_;
2416
eCoord const * lastFakeDir_;
2419
bool gCycle::DoTurn(int d)
2422
if ( sg_gnuplotDebug && Player() )
2424
std::ofstream f( Player()->GetUserName() + "_turn", std::ios::app );
2425
f << pos.x << " " << pos.y << "\n";
2436
clientside_action();
2438
if ( gCycleMovement::DoTurn( d ) )
2440
sg_ArchiveCoord( pos, 1 );
2444
if (sn_GetNetState() == nCLIENT && Owner() == ::sn_myNetID)
2447
if (sn_GetNetState()!=nCLIENT)
2452
// hack: while dropping the wall, turn around dirDrive.
2453
// this makes FindCurrentFace work better.
2457
eCoord dirDriveFake = dirDrive * factor;
2458
eCoord lastDirDriveBack = lastDirDrive;
2459
lastDirDrive = lastDirDrive * factor;
2460
gFakeDirDriveSetter fakeSetter( dirDriveFake );
2462
lastDirDrive = lastDirDriveBack;
2472
void gCycle::DropWall( bool buildNew )
2474
// keep this cycle alive
2475
tJUST_CONTROLLED_PTR< gCycle > keep( this->GetRefcount()>0 ? this : 0 );
2477
// drop last net wall if it is outdated
2478
if ( lastWall && lastNetWall && lastWall->Time(.5) > lastNetWall->Time(0) )
2481
// update and drop current wall
2484
lastWall=currentWall;
2485
currentWall->Update(lastTime,pos);
2486
currentWall->CopyIntoGrid( grid );
2490
if ( buildNew && lastTime >= spawnTime_ + sg_cycleWallTime )
2491
currentWall=new gNetPlayerWall(this,pos,dirDrive,lastTime,distance);
2493
// grid datastructures change on inserting a wall, better recheck
2494
// all game objects. Temporarily override this cycle's driving direction.
2495
eCoord dirDriveBack = dirDrive;
2496
if ( sg_fakeDirDrive )
2497
dirDrive = *sg_fakeDirDrive;
2501
for(int i=grid->GameObjects().Len()-1;i>=0;i--)
2503
eGameObject * c = grid->GameObjects()(i);
2504
if (c->CurrentFace() && !c->CurrentFace()->IsInGrid() )
2505
c->FindCurrentFace();
2508
dirDrive = dirDriveBack;
2511
dropWallRequested_ = false;
2514
void gCycle::Kill(){
2515
// keep this cycle alive
2516
tJUST_CONTROLLED_PTR< gCycle > keep( this->GetRefcount()>0 ? this : 0 );
2518
if (sn_GetNetState()!=nCLIENT){
2522
tNEW(gExplosion)(grid, pos,lastTime, color_);
2523
// eEdge::SeethroughHasChanged();
2527
// z-man: updating the wall so it reflects exactly the position of death looks like
2528
// a good idea, but unfortunately, the collision position reported from above
2529
// is inaccurate. It's better not to use it at all, or the cycle's wall will stick out
2530
// a bit on the other side of the wall it crashed into.
2533
// currentWall->Update( lastTime, pos );
2535
// copy the wall into the grid, but not directly; the grid datastructures are probably currently traversed. Kill() is called from eGameObject::Move().
2536
currentWall->CopyIntoGrid( 0 );
2541
// request a new sync
2545
// z-man: another stupid idea. Why would we need a destination when we're dead?
2546
// else if (Owner() == ::sn_myNetID)
2547
// AddDestination();
2549
else if (owner!=::sn_myNetID)
2555
void gCycle::Turbo(bool turbo){
2556
if (turbo && speed<30){
2561
if (!turbo && speed>=30){
2568
static rFileTexture cycle_shad(rTextureGroups::TEX_FLOOR,"textures/shadow.png",0,0,true);
2570
static tString lala_cycle_shad("Anonymous/original/textures/shadow.png");
2571
static nSettingItem<tString> lalala_cycle_shad("TEXTURE_CYCLE_SHADOW", lala_cycle_shad);
2572
rFileTexture cycle_shad(rTextureGroups::TEX_FLOOR, lala_cycle_shad, 0,0,true);
2575
REAL sg_laggometerScale=1;
2576
static tSettingItem< REAL > sg_laggometerScaleConf( "LAG_O_METER_SCALE", sg_laggometerScale );
2578
int sg_blinkFrequency=10;
2579
static tSettingItem< int > sg_blinkFrequencyConf( "CYCLE_BLINK_FREQUENCY", sg_blinkFrequency );
2582
void gCycle::Render(const eCamera *cam){
2583
if ( lastTime > spawnTime_ && !Vulnerable() )
2585
double time = tSysTimeFloat();
2586
double wrap = time - floor(time);
2587
int pulse = int ( 2 * wrap * sg_blinkFrequency );
2588
if ( ( pulse & 1 ) == 0 )
2592
#ifdef USE_HEADLIGHT
2594
typedef void (*glProgramStringARB_Func)(GLenum, GLenum, GLsizei, const void*);
2595
glProgramStringARB_Func glProgramStringARB_ptr = 0;
2597
typedef void (*glProgramLocalParameter4fARB_Func)(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
2598
glProgramLocalParameter4fARB_Func glProgramLocalParameter4fARB_ptr = 0;
2600
glProgramStringARB_ptr = (glProgramStringARB_Func) SDL_GL_GetProcAddress("glProgramStringARB");
2601
glProgramLocalParameter4fARB_ptr = (glProgramLocalParameter4fARB_Func) SDL_GL_GetProcAddress("glProgramLocalParameter4fARB");
2604
if (!finite(z) || !finite(pos.x) ||!finite(pos.y)||!finite(dir.x)||!finite(dir.y)
2608
//con << "Drawing cycle at " << pos << '\n';
2612
gDestination *l = destinationList;
2613
glDisable(GL_LIGHTING);
2616
if (l == currentDestination)
2620
glVertex3f(l->position.x, l->position.y, 0);
2621
glVertex3f(l->position.x, l->position.y, 100);
2624
if (l == currentDestination)
2632
GLfloat color[4]={1,1,1,1};
2633
static GLfloat lposa[4] = { 320, 240, 200,0};
2634
static GLfloat lposb[4] = { -240, -100, 200,0};
2635
static GLfloat lighta[4] = { 1, .7, .7, 1 };
2636
static GLfloat lightb[4] = { .7, .7, 1, 1 };
2638
glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,color);
2639
glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,color);
2641
glLightfv(GL_LIGHT0, GL_DIFFUSE, lighta);
2642
glLightfv(GL_LIGHT0, GL_SPECULAR, lighta);
2643
glLightfv(GL_LIGHT0, GL_POSITION, lposa);
2644
glLightfv(GL_LIGHT1, GL_DIFFUSE, lightb);
2645
glLightfv(GL_LIGHT1, GL_SPECULAR, lightb);
2646
glLightfv(GL_LIGHT1, GL_POSITION, lposb);
2651
eCoord p = PredictPosition();
2652
glTranslatef(p.x,p.y,0);
2653
glScalef(.5f,.5f,.5f);
2657
ske=ske*(1/sqrt(ske.NormSquared()));
2659
GLfloat m[4][4]={{dir.x,dir.y,0,0},
2663
glMultMatrixf(&m[0][0]);
2666
//glTranslatef(-1.84,0,0);
2668
glTranslatef(-1.5,0,0);
2672
GLfloat sk[4][4]={{1,0,0,0},
2677
glMultMatrixf(&sk[0][0]);
2680
glEnable(GL_LIGHT0);
2681
glEnable(GL_LIGHT1);
2682
glEnable(GL_LIGHTING);
2694
GLfloat sk[4][4]={{0,.1,0,0},
2700
glMultMatrixf(&sk[0][0]);
2703
customTexture->Select();
2704
//glDisable(GL_TEXTURE_2D);
2705
//glDisable(GL_TEXTURE);
2708
//glPolygonMode(GL_FRONT, GL_FILL);
2709
//glDepthFunc(GL_LESS);
2710
//glCullFace(GL_BACK);
2711
customModel->Render();
2713
//glPolygonMode(GL_BACK,GL_LINE);
2714
//glDepthFunc(GL_LEQUAL);
2715
//glCullFace(GL_FRONT);
2716
//customModel->Render();
2717
//sr_ResetRenderState();
2721
glTranslatef(-1.5,0,0);
2725
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
2726
glEnable(GL_TEXTURE_GEN_S);
2728
glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
2729
glEnable(GL_TEXTURE_GEN_T);
2731
glTexGeni(GL_R,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
2732
glEnable(GL_TEXTURE_GEN_R);
2734
glTexGeni(GL_Q,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
2735
glEnable(GL_TEXTURE_GEN_Q);
2738
glEnable(GL_TEXTURE_2D);
2741
static GLfloat tswap[4][4]={{1,0,0,0},
2746
static GLfloat tswapb[4][4]={{1,0,0,0},
2753
// glLoadMatrixf(&tswapb[0][0]);
2754
// glScalef(.4,.4,.8);
2764
glTranslatef(0,0,.73);
2766
GLfloat mr[4][4]={{rotationRearWheel.x,0,rotationRearWheel.y,0},
2768
{-rotationRearWheel.y,0,rotationRearWheel.x,0},
2772
glMultMatrixf(&mr[0][0]);
2776
// glLoadMatrixf(&tswap[0][0]);
2777
// glScalef(.65,.65,.65);
2784
glTranslatef(1.84,0,.43);
2786
GLfloat mf[4][4]={{rotationFrontWheel.x,0,rotationFrontWheel.y,0},
2788
{-rotationFrontWheel.y,0,rotationFrontWheel.x,0},
2791
glMultMatrixf(&mf[0][0]);
2795
// glLoadMatrixf(&tswap[0][0]);
2796
// glScalef(1.2,1.2,1.2);
2808
// IdentityMatrix();
2812
glDisable(GL_TEXTURE_GEN_S);
2813
glDisable(GL_TEXTURE_GEN_T);
2814
glDisable(GL_TEXTURE_GEN_Q);
2815
glDisable(GL_TEXTURE_GEN_R);
2818
glDisable(GL_LIGHT0);
2819
glDisable(GL_LIGHT1);
2820
glDisable(GL_LIGHTING);
2822
//glDisable(GL_TEXTURE);
2823
glDisable(GL_TEXTURE_2D);
2827
bool renderPyramid = false;
2828
gRealColor colorPyramid;
2830
const REAL timeout = .5f;
2834
if ( player->IsChatting() )
2836
renderPyramid = true;
2837
colorPyramid.b = 0.0f;
2839
else if ( !player->IsActive() )
2841
renderPyramid = true;
2842
colorPyramid.b = 0.0f;
2843
colorPyramid.g = 0.0f;
2845
else if ( cam && cam->Center() == this && se_GameTime() < timeout && player->CurrentTeam() && player->CurrentTeam()->NumPlayers() > 1 )
2847
renderPyramid = true;
2848
alpha = timeout - se_GameTime();
2852
if ( renderPyramid )
2854
GLfloat s=sin(lastTime);
2855
GLfloat c=cos(lastTime);
2857
GLfloat m[4][4]={{c,s,0,0},
2864
glMultMatrixf(&m[0][0]);
2870
glColor4f( colorPyramid.r,colorPyramid.g,colorPyramid.b, alpha );
2872
glVertex3f(0,1,4.5);
2873
glVertex3f(0,-1,4.5);
2875
glColor4f( colorPyramid.r * .7f,colorPyramid.g * .7f,colorPyramid.b * .7f, alpha );
2877
glVertex3f(1,0,4.5);
2878
glVertex3f(-1,0,4.5);
2886
#ifdef USE_HEADLIGHT
2887
// Headlight contributed by Jonathan
2889
if(!cycleprograminited) { // set to false on every sr_InitDisplay, without it I lost my program when I switched to windowed
2890
const char *program =
2893
PARAM normal = program.local[0];\
2894
ATTRIB texcoord = fragment.texcoord;\
2895
TEMP final, diffuse, distance;\
2897
DP3 distance, texcoord, texcoord;\
2898
RSQ diffuse, distance.w;\
2899
RCP distance, distance.w;\
2900
MUL diffuse, texcoord, diffuse;\
2901
DP3 diffuse, diffuse, normal;\
2902
MUL final, diffuse, distance;\
2903
MOV result.color.w, fragment.color;\
2904
MUL result.color.xyz, fragment.color, final;\
2908
glProgramStringARB_ptr(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(program), program);
2910
glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen(program), program);
2912
cycleprograminited = true;
2915
glProgramLocalParameter4fARB_ptr(GL_FRAGMENT_PROGRAM_ARB, 0, 0, 0, verletSpeed_ * verletSpeed_, 0);
2917
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0, 0, verletSpeed_ * verletSpeed_, 0);
2919
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.
2920
glEnable(GL_FRAGMENT_PROGRAM_ARB); // doesn't check if it exists...
2922
const unsigned sensors = 32; // actually one more
2923
const double mul = 0.25 * M_PI / sensors;
2924
const double add = -0.125 * M_PI;
2926
double size = gArena::SizeMultiplier() * 500 * M_SQRT2; // is M_SQRT2 in your math.h?
2927
GLfloat array[sensors+2][5];
2933
array[0][4] = 0.125;
2935
for(unsigned i=0; i<=sensors; i++) {
2936
gSensor sensor(this, p, dir.Turn(cos(i * mul + add), sin(i * mul + add)));
2937
sensor.detect(size);
2938
array[i][5] = sensor.before_hit.x - p.x;
2939
array[i][6] = sensor.before_hit.y - p.y;
2940
array[i][7] = sensor.before_hit.x;
2941
array[i][8] = sensor.before_hit.y;
2942
array[i][9] = 0.125;
2948
glMatrixMode(GL_TEXTURE);
2950
glTranslatef(0, 0, 1);
2952
glBlendFunc(GL_ONE, GL_ONE);
2953
glDepthMask(GL_FALSE);
2955
glColor3fv(reinterpret_cast<GLfloat *>(&color_)); // 8-)
2956
glEnableClientState(GL_VERTEX_ARRAY);
2957
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
2959
glInterleavedArrays(GL_T2F_V3F, 0, array);
2960
glDrawArrays(GL_TRIANGLE_FAN, 0, sensors+2);
2962
glDisableClientState(GL_VERTEX_ARRAY);
2963
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
2965
glDisable(GL_FRAGMENT_PROGRAM_ARB);
2968
glMatrixMode(GL_MODELVIEW);
2973
#endif // USE_HEADLIGHT
2980
sr_DepthOffset(true);
2983
REAL h=0;//se_cameraZ*.005+.03;
2985
glEnable(GL_CULL_FACE);
2987
if(sr_floorDetail>rFLOOR_GRID && rTextureGroups::TextureMode[rTextureGroups::TEX_FLOOR]>0 && sr_alphaBlend){
2989
cycle_shad.Select();
2992
glVertex3f(-.6,.4,h);
2995
glVertex3f(-.6,-.4,h);
2998
glVertex3f(2.1,-.4,h);
3001
glVertex3f(2.1,.4,h);
3006
glDisable(GL_CULL_FACE);
3011
REAL f=verletSpeed_;
3017
h=cam->CameraZ()*.005+.03;
3019
if (sn_GetNetState() != nSTANDALONE && sr_laggometer && f*l>.5){
3020
//&& owner!=::sn_myNetID){
3024
//glDisable(GL_TEXTURE);
3025
glDisable(GL_TEXTURE_2D);
3027
glTranslatef(0,0,h);
3028
//glScalef(.5*f,.5*f,.5*f);
3030
// compensate for the .5 scaling further up
3031
f *= 2 * sg_laggometerScale;
3035
// move the sr_laggometer ahead a bit
3036
if (!sr_predictObjects || sn_GetNetState()==nSERVER)
3037
glTranslatef(l,0,0);
3046
REAL delay = GetTurnDelay();
3048
glVertex2f(-2*l+delay,delay);
3049
glVertex2f(-2*l+2*delay,0);
3050
glVertex2f(-2*l+delay,-delay);
3053
glVertex2f(-2*l+delay,delay);
3054
glVertex2f(-l,2*delay-l);
3055
glVertex2f(-l,-(2*delay-l));
3056
glVertex2f(-2*l+delay,-delay);
3062
sr_DepthOffset(false);
3069
static REAL fadeOutNameAfter = 5.0f; /* 0: never show, < 0 always show */
3070
//static int fadeOutNameMode = 1; // 0: never show, 1: show for fadeOutNameAfter, 2: always show
3071
static bool showOwnName = 0; // show name on own cycle?
3073
static tSettingItem< bool > sg_showOwnName( "SHOW_OWN_NAME", showOwnName );
3074
//static tSettingItem< int > sg_fadeOutNameMode( "FADEOUT_NAME_MODE", showOwnName )
3075
static tSettingItem< REAL > sg_fadeOutNameAfter( "FADEOUT_NAME_DELAY", fadeOutNameAfter );
3078
void gCycle::RenderName( const eCamera* cam ) {
3079
if ( !this->Player() )
3082
float modelviewMatrix[16], projectionMatrix[16];
3087
if (fadeOutNameAfter == 0) return; /* XXX put that in ::Render() */
3088
if ( !cam->RenderingMain() ) return; // no name in mirrored image
3089
if ( !showOwnName && cam->Player() == this->player ) return; // don't show own name
3092
/* position sign above center of cycle */
3093
glTranslatef(0.8, 0, 2.0);
3094
glGetFloatv(GL_MODELVIEW_MATRIX, modelviewMatrix);
3095
glGetFloatv(GL_PROJECTION_MATRIX, projectionMatrix);
3098
/* get coordinates of sign */
3099
x = modelviewMatrix[12];
3100
y = modelviewMatrix[13];
3101
z = modelviewMatrix[14];
3102
w = modelviewMatrix[15];
3104
/* multiply by projection matrix */
3105
xp = projectionMatrix[0] * x + projectionMatrix[4] * y +
3106
projectionMatrix[8] * z + projectionMatrix[12] * w;
3107
yp = projectionMatrix[1] * x + projectionMatrix[5] * y +
3108
projectionMatrix[9] * z + projectionMatrix[13] * w;
3109
wp = projectionMatrix[3] * x + projectionMatrix[7] * y +
3110
projectionMatrix[11] * z + projectionMatrix[15] * w;
3114
timeCameIntoView = 0;
3120
yp += rCHEIGHT_NORMAL;// * 2.0;
3122
if (xp <= -1 || xp >= 1 || yp <= -1 || yp >= 1) {
3124
timeCameIntoView = 0;
3130
if (fadeOutNameAfter > 0) {
3131
REAL now = tSysTimeFloat();
3132
if (timeCameIntoView == 0)
3133
timeCameIntoView = now;
3135
if (now - timeCameIntoView > fadeOutNameAfter) {
3137
} else if (now - timeCameIntoView > fadeOutNameAfter - 1) {
3138
/* start to fade out */
3139
alpha = 0.75 - (now - timeCameIntoView -
3140
(fadeOutNameAfter - 1)) * 0.75;
3152
glColor4f(1, 1, 1, alpha);
3153
DisplayText(xp, yp, rCWIDTH_NORMAL, rCHEIGHT_NORMAL, this->player->GetName(), 0, 0);
3163
bool gCycle::RenderCockpitFixedBefore(bool){
3168
REAL rd=se_GameTime()-deathTime;
3175
glColor3f(d,d/2,d/4);
3176
glDisable(GL_TEXTURE_2D);
3177
glDisable(GL_TEXTURE);
3178
glDisable(GL_DEPTH_TEST);
3180
glColor4f(1,1,1,rd*(2-rd/2));
3181
DisplayText(0,0,.05,.15,"You have been deleted.");
3189
void gCycle::SoundMix(Uint8 *dest,unsigned int len,
3190
int viewer,REAL rvol,REAL lvol){
3193
if (!cycle_run.alt){
3200
engine->Mix(dest,len,viewer,rvol,lvol,verletSpeed_/(sg_speedCycleSound * SpeedMultiplier()));
3204
turning->Mix(dest,len,viewer,rvol,lvol,5);
3206
turning->Mix(dest,len,viewer,rvol,lvol,1);
3209
spark->Mix(dest,len,viewer,rvol*.5,lvol*.5,4);
3214
eCoord gCycle::PredictPosition(){
3215
gSensor s(this,pos, dir * (verletSpeed_ * se_PredictTime()) + correctPosSmooth );
3217
return s.before_hit;
3219
// eCoord p = pos + dir * (speed * se_PredictTime());
3220
// return p + correctPosSmooth;
3223
eCoord gCycle::CamPos()
3225
return PredictPosition() + dir.Turn(0 ,-skew*z);
3227
// gSensor s(this,pos, PredictPosition() - pos );
3230
// return s.before_hit + dir.Turn(0 ,-skew*z);
3232
// return pos + dir.Turn(0 ,-skew*z);
3235
eCoord gCycle::CamTop()
3237
return dir.Turn(0,-skew);
3242
void gCycle::PPDisplay(){
3246
PD_PutPixel(DoubleBuffer,
3247
se_X_ToScreen(pos.x),
3248
se_Y_ToScreen(pos.y),
3249
PD_CreateColor(DoubleBuffer,R,G,B));
3251
PD_PutPixel(DoubleBuffer,
3252
se_X_ToScreen(pos.x+1),
3253
se_Y_ToScreen(pos.y),
3254
PD_CreateColor(DoubleBuffer,R,G,B));
3255
PD_PutPixel(DoubleBuffer,
3256
se_X_ToScreen(pos.x-1),
3257
se_Y_ToScreen(pos.y),
3258
PD_CreateColor(DoubleBuffer,R,G,B));
3259
PD_PutPixel(DoubleBuffer,
3260
se_X_ToScreen(pos.x),
3261
se_Y_ToScreen(pos.y+1),
3262
PD_CreateColor(DoubleBuffer,R,G,B));
3263
PD_PutPixel(DoubleBuffer,
3264
se_X_ToScreen(pos.x),
3265
se_Y_ToScreen(pos.y-1),
3266
PD_CreateColor(DoubleBuffer,R,G,B));
3275
// cycle network routines:
3276
gCycle::gCycle(nMessage &m)
3282
rotationFrontWheel(1,0),rotationRearWheel(1,0),heightFrontWheel(0),heightRearWheel(0),
3286
lastNetWall=lastWall=currentWall=NULL;
3287
windingNumberWrapped_ = windingNumber_ = Grid()->DirectionWinding(dirDrive);
3288
dirDrive = Grid()->GetDirection(windingNumberWrapped_);
3292
//correctTimeSmooth =0;
3293
correctDistanceSmooth =0;
3294
//correctSpeedSmooth =0;
3297
spawnTime_ = se_GameTime() + 100;
3303
trailColor_ = color_;
3305
se_MakeColorValid( color_.r, color_.g, color_.b, 1.0f );
3306
se_MakeColorValid( trailColor_.r, trailColor_.g, trailColor_.b, .5f );
3308
// set last time so that the first read_sync will not think this is old
3309
lastTimeAnim = lastTime = -EPS;
3311
nextSync = nextSyncOwner = -1;
3315
void gCycle::WriteCreate(nMessage &m){
3316
eNetGameObject::WriteCreate(m);
3322
static nVersionFeature sg_verletIntegration( 7 );
3324
void gCycle::WriteSync(nMessage &m){
3325
// eNetGameObject::WriteSync(m);
3338
REAL speed = verletSpeed_;
3339
// if the clients understand it, send them the real current speed
3340
if ( sg_verletIntegration.Supported() )
3344
m << short( Alive() ? 1 : 0 );
3346
if (!currentWall || currentWall->preliminary)
3349
m.Write(currentWall->ID());
3354
// write last turn position
3355
m << GetLastTurnPos();
3358
compressZeroOne.Write( m, rubber/( sg_rubberCycle + .1 ) );
3359
compressZeroOne.Write( m, 1/( 1 + rubberMalus ) );
3361
// write last clientside sync message ID
3362
unsigned short lastMessageID = 0;
3363
if ( lastDestination )
3364
lastMessageID = lastDestination->messageID;
3365
m.Write(lastMessageID);
3368
compressZeroOne.Write( m, brakingReservoir );
3370
// set new sync times
3371
nextSync=tSysTimeFloat()+sg_syncIntervalEnemy;
3372
nextSyncOwner=tSysTimeFloat()+sg_GetSyncIntervalSelf( this );
3375
bool gCycle::SyncIsNew(nMessage &m){
3376
bool ret=eNetGameObject::SyncIsNew(m);
3381
unsigned short Turns;
3389
con << "received sync for player " << player->GetName() << " ";
3391
con << "accepting..\n";
3396
con << "rejecting..\n";
3401
return ret || al!=1;
3404
// resets the extrapolator to the last known state
3405
void gCycle::ResetExtrapolator()
3407
resimulate_ = false;
3410
extrapolator_ = tNEW( gCycleExtrapolator )(grid, pos, dir );
3413
extrapolator_->CopyFrom( lastSyncMessage_, *this );
3416
// simulate the extrapolator at higher speed
3417
bool gCycle::Extrapolate( REAL dt )
3419
tASSERT( extrapolator_ );
3421
eCoord posBefore = extrapolator_->Position();
3423
// calculate target time
3424
REAL newTime = extrapolator_->LastTime() + dt;
3428
// clamp: don't simulate further than our current time
3429
if ( newTime >= lastTime )
3431
// simulate extrapolator until now
3432
eGameObject::TimestepThis( lastTime, extrapolator_ );
3434
// test if there are real (the check for list does that) destinations left; we cannot call it finished if there are.
3435
gDestination* unhandledDestination = extrapolator_->GetCurrentDestination();
3436
ret = !unhandledDestination || !unhandledDestination->list;
3442
// simulate extrapolator as requested
3443
eGameObject::TimestepThis( newTime, extrapolator_ );
3446
//eCoord posAfter = extrapolator_->Position();
3447
//eDebugLine::SetTimeout( 1.0 );
3448
//eDebugLine::SetColor( 1,0,0 );
3449
//eDebugLine::Draw( posBefore, 8, posAfter, 4 );
3450
//eDebugLine::Draw( posBefore, 4, posAfter, 4 );
3455
// makes sure the given displacement does not cross walls
3456
void se_SanifyDisplacement( eGameObject* base, eCoord& displacement )
3458
eCoord base_pos = base->Position();
3459
eCoord reachable_pos = base->Position() + displacement;
3462
while( timeout > 0 )
3466
// cast ray fron sync_pos to reachable_pos
3467
gSensor test( base, base_pos, displacement );
3472
// emergency exit; take something closely before the wall
3473
displacement = ( displacement ) * ( test.hit * .99 );
3485
// see if the wall that was hit is nearly parallel to the desired move. maybe we should add
3486
// special code for that. However, the projection idea seems to be universally robust.
3487
float p = test.ehit->Vec() * displacement;
3488
float m = se_EstimatedRangeOfMult( test.ehit->Vec(), displacement );
3489
if ( fabs(p) < m * .001 )
3491
con << "Almost missed wall during gCycle::ReadSync positon update";
3495
// project reachable_pos to the line that was hit
3496
REAL alpha = test.ehit->Ratio( base_pos + displacement );
3497
displacement = *test.ehit->Point() + test.ehit->Vec() * alpha - base_pos;
3499
// move it a bit closer to the known good position
3500
displacement = displacement * .99;
3505
void gCycle::TransferPositionCorrectionToDistanceCorrection()
3507
// REAL correctDist = eCoord::F( correctPosSmooth, dirDrive );
3508
// correctDistanceSmooth += correctDist;
3509
// correctPosSmooth = correctPosSmooth - dirDrive * correctDist;
3512
// take over the extrapolator's data
3513
void gCycle::SyncFromExtrapolator()
3515
// store old position
3516
eCoord oldPos = pos;
3518
// con << "Copy: " << LastTime() << ", " << extrapolator_->LastTime() << ", " << extrapolator_->Position() << ", " << pos << "\n";
3520
tASSERT( extrapolator_ );
3523
CopyFrom( *extrapolator_ );
3525
// adjust current wall (not essential, don't do it for the first wall)
3526
if ( currentWall && currentWall->tBeg > spawnTime_ + sg_cycleWallTime + .01f )
3527
currentWall->beg = extrapolator_->GetLastTurnPos();
3529
// smooth position correction
3530
correctPosSmooth = correctPosSmooth + oldPos - pos;
3533
if ( correctPosSmooth.NormSquared() > .1f )
3535
std::cout << "Lag slide! " << correctPosSmooth << "\n";
3541
// calculate time difference between this cycle and extrapolator
3542
REAL dt = this->LastTime() - extrapolator_->LastTime();
3544
// extrapolate true distance ( the best estimate we have for the distance on the server )
3545
REAL trueDistance = extrapolator_->trueDistance_ + extrapolator_->Speed() * dt;
3547
// update distance correction
3548
// con << correctDistanceSmooth << "," << trueDistance << "," << distance << "\n";
3549
// correctDistanceSmooth = trueDistance - distance;
3550
distance = trueDistance;
3552
// split away part in driving direction
3553
TransferPositionCorrectionToDistanceCorrection();
3555
// make sure correction does not bring us to the wrong side of a wall
3556
// se_SanifyDisplacement( this, correctPosSmooth );
3558
//eDebugLine::SetTimeout( 1.0 );
3559
//eDebugLine::SetColor( 0,1,0 );
3560
//eDebugLine::Draw( pos, 4, pos + dirDrive * 10, 14 );
3562
// delete extrapolator
3566
static int sg_useExtrapolatorSync=1;
3567
static tSettingItem<int> sg_useExtrapolatorSyncConf("EXTRAPOLATOR_SYNC",sg_useExtrapolatorSync);
3569
// make sure no correction moves the cycle backwards beyond the beginning of the last wall
3570
void ClampForward( eCoord& newPos, const eCoord& startPos, const eCoord& dir )
3572
REAL forward = eCoord::F( newPos - startPos, dir );
3574
newPos = newPos - dir * forward;
3577
extern REAL sg_cycleBrakeRefill;
3578
extern REAL sg_cycleBrakeDeplete;
3580
void gCycle::ReadSync( nMessage &m )
3582
// data from sync message
3585
short sync_alive; // is this cycle alive?
3586
unsigned short sync_wall=0; // ID of wall
3588
eCoord new_pos = pos; // the extrapolated position
3590
// warning: depends on the implementation of eNetGameObject::WriteSync
3591
// since we don't call eNetGameObject::ReadSync.
3594
// reset values not sent with old protocol messages
3595
sync.rubber = rubber;
3597
sync.braking = braking;
3603
//eDebugLine::SetTimeout( 1.0 );
3604
//eDebugLine::SetColor( 1,1,1 );
3605
//eDebugLine::Draw( lastSyncMessage_.pos, 0, lastSyncMessage_.pos, 20 );
3614
m.Read(sync.braking);
3620
else if ( currentWall )
3622
sync.lastTurn = currentWall->beg;
3625
bool canUseExtrapolatorMethod = false;
3627
bool rubberSent = false;
3633
REAL preRubber, preRubberMalus;
3634
preRubber = compressZeroOne.Read( m );
3635
preRubberMalus = compressZeroOne.Read( m );
3637
// read last message ID
3638
m.Read(sync.messageID);
3640
// read braking reservoir
3641
sync.brakingReservoir = compressZeroOne.Read( m );
3642
// std::cout << "sync: " << sync.brakingReservoir << ":" << sync.braking << "\n";
3645
sync.rubber = preRubber * ( sg_rubberCycle + .1 );
3646
sync.rubberMalus = 1/preRubberMalus - 1;
3648
// extrapolation is probably safe
3649
canUseExtrapolatorMethod = sg_useExtrapolatorSync && lastTime > 0;
3653
// try to extrapolate brake status backwards in time
3654
sync.brakingReservoir = brakingReservoir;
3655
if ( brakingReservoir > 0 && sync.braking )
3656
sync.brakingReservoir += ( lastTime - sync.time ) * sg_cycleBrakeDeplete;
3657
else if ( brakingReservoir < 1 && !sync.braking )
3658
sync.brakingReservoir -= ( lastTime - sync.time ) * sg_cycleBrakeRefill;
3660
if ( sync.brakingReservoir < 0 )
3661
sync.brakingReservoir = 0;
3662
else if ( sync.brakingReservoir > 1 )
3663
sync.brakingReservoir = 1;
3666
// abort if sync is not new
3667
if ( lastSyncMessage_.time >= sync.time && lastSyncMessage_.turns >= sync.turns && sync_alive > 0 )
3669
//eDebugLine::SetTimeout( 5 );
3670
//eDebugLine::SetColor( 1,1,0);
3671
//eDebugLine::Draw( lastSyncMessage_.pos, 1.5, lastSyncMessage_.pos, 5.0 );
3674
lastSyncMessage_ = sync;
3676
// store last known good position: a bit before the last position confirmed by the server
3677
lastGoodPosition_ = sync.pos + ( sync.lastTurn - sync.pos ) *.01;
3678
// offset it a tiny bit by our last driving direction
3679
if ( eCoord::F( dirDrive, sync.dir ) > .99f )
3680
lastGoodPosition_ = lastGoodPosition_ - this->lastDirDrive * .0001;
3682
//eDebugLine::SetTimeout( 2 );
3683
//eDebugLine::SetColor( 0,1,0);
3684
//eDebugLine::Draw( lastSyncMessage_.pos, 1.5, lastSyncMessage_.pos, 5.0 );
3686
// con << "Sync: " << lastTime << ", " << lastSyncMessage_.time << ", " << lastSyncMessage_.pos << ", " << pos << "\n";
3688
if ( canUseExtrapolatorMethod )
3690
// reset extrapolator if information from server is more up to date
3691
if ( extrapolator_ && extrapolator_->LastTime() < lastSyncMessage_.time )
3698
if (Alive() && sync_alive!=1)
3700
Die( lastSyncMessage_.time );
3701
MoveSafely( lastSyncMessage_.pos, lastTime, deathTime );
3702
distance=lastSyncMessage_.distance;
3706
tNEW(gExplosion)( grid, lastSyncMessage_.pos, lastSyncMessage_.time ,color_ );
3711
gDestination emergency_aft(*this);
3713
// all the data was read. check where it fits in our destination list:
3714
gDestination *bef= GetDestinationBefore( lastSyncMessage_, destinationList );
3715
gDestination *aft=NULL;
3721
//eDebugLine::SetTimeout(1);
3722
//eDebugLine::SetColor( 1,0,0 );
3723
//eDebugLine::Draw( bef->position, 1.5, lastSyncMessage_.pos, 3.0 );
3724
//eDebugLine::SetColor( 0,1,0 );
3725
//eDebugLine::Draw( lastSyncMessage_.pos, 3.0, aft->position, 5.0 );
3730
// detect local tunneling by casting a ray from the server certified position
3731
// to the next known client position
3734
eCoord position = pos;
3736
position = bef->position;
3737
eSensor tunnel( this, lastSyncMessage_.pos, position - lastSyncMessage_.pos );
3740
// use extrapolation to undo local tunneling
3743
canUseExtrapolatorMethod = true;
3746
con << "possible local tunneling detected\n";
3747
eSensor tunnel( this, lastSyncMessage_.pos, position - lastSyncMessage_.pos );
3753
// determine whether we can use the distance based interpolating sync method here
3754
bool distanceBased = aft && aft != &emergency_aft && Owner() == sn_myNetID;
3756
if ( canUseExtrapolatorMethod && Owner()==sn_myNetID )
3758
// exactlu resimulate from sync position for cycles controlled by this client
3764
rubber = lastSyncMessage_.rubber;
3766
// bool fullSync = false;
3768
// try to get a distance value closer to the client's data by calculating the distance from the sync position to the next destination
3769
REAL interpolatedDistance = lastSyncMessage_.distance;
3772
interpolatedDistance = aft->distance - sqrt((lastSyncMessage_.pos-aft->position).NormSquared());
3775
// determine true lag
3778
// lag = player->ping;
3780
if ( distanceBased )
3782
// new way: correct our time and speed
3785
// destination *save = bef;
3788
REAL ratio = (interpolatedDistance - bef->distance)/
3789
(aft->distance - bef->distance);
3794
// interpolate when the cycle was at the position the sync message was sent
3795
REAL interpolatedTime = bef->gameTime * (1-ratio) + aft->gameTime * ratio;
3796
// REAL interpolatedSpeed = bef->speed * (1-ratio) + aft->speed * ratio;
3799
REAL correctTime = ( lastSyncMessage_.time - interpolatedTime );
3800
// REAL correctSpeed = ( lastSyncMessage_.speed - interpolatedSpeed );
3801
REAL correctDist = ( lastSyncMessage_.distance - interpolatedDistance );
3803
// don't trust high ratios too much; they may be skewed by rubber application
3805
REAL factor = (1 - ratio) * 5;
3810
correctTime *= factor;
3811
correctDist *= factor;
3815
//if (correctTime > 0)
3818
// correctSpeed*=.5;
3822
//correctTimeSmooth += correctTime;
3823
//correctSpeedSmooth += correctSpeed;
3824
// correctDistanceSmooth += correctDist;
3826
// correctDistanceSmooth += correctDist;
3828
// con << ratio << ", " << correctDist << ", " << correctTime << "\n";
3830
// correct distances according to sync
3832
distance += correctDist;
3833
gDestination * run = bef;
3836
run->distance += correctDist;
3841
// correct time by adapting position and distance
3842
eCoord newPos = pos - Direction() * Speed() * correctTime;
3845
ClampForward( newPos, currentWall->beg, Direction() );
3848
// don't tunnel through walls
3850
const eCoord & safePos = pos; // aft->position
3851
gSensor test( this, safePos , newPos - safePos );
3855
newPos = test.before_hit;
3857
// something bad must be going on, better recheck with accurate extrapolation
3862
correctPosSmooth = correctPosSmooth + pos - newPos;
3863
distance += eCoord::F( newPos - pos, Direction() );
3865
MoveSafely( newPos, lastTime, lastTime );
3868
REAL ts = lastSyncMessage_.time - lastTime;
3870
// eCoord intPos = pos + dirDrive * (ts * speed + .5 * ts*ts*acceleration);
3871
eCoord intPos = pos + dirDrive * ( ts * ( speed + lastSyncMessage_.speed ) * .5 );
3872
REAL int_speed = speed + acceleration*ts;
3874
dirDrive = lastSyncMessage_.dir;
3876
correctPosSmooth = lastSyncMessage_.pos - intPos;
3878
distance = lastSyncMessage_.distance - speed * ts - acceleration * ts*ts*.5;
3880
//correctTimeSmooth = 0;
3886
if ( Owner() != sn_myNetID )
3888
// direct extrapolation for cycles of other clients or if no turn is newer than the sync
3889
SyncEnemy( lastSyncMessage_.lastTurn );
3891
// update beginning of current wall
3894
currentWall->beg = lastSyncMessage_.lastTurn;
3897
// update brake status
3898
braking = lastSyncMessage_.braking;
3902
// same algorithm, but update smooth position correction so that there is no immediate visual change
3903
eCoord oldPos = pos + correctPosSmooth;
3904
SyncEnemy( lastSyncMessage_.lastTurn );
3905
correctPosSmooth = oldPos - pos;
3908
if ( correctPosSmooth.NormSquared() > .1f && lastTime > 0.0 )
3910
std::cout << "Lag slide! " << correctPosSmooth << "\n";
3917
// restore rubber meter
3920
rubber = lastSyncMessage_.rubber;
3924
sn_Update(turns,lastSyncMessage_.turns);
3926
//if (fabs(correctTimeSmooth > 5))
3929
// calculate new winding number. Try to change it as little as possible.
3930
this->SetWindingNumberWrapped( Grid()->DirectionWinding(dirDrive) );
3932
// Resnap to the axis
3933
dirDrive = Grid()->GetDirection(windingNumberWrapped_);
3936
void gCycle::SyncEnemy ( const eCoord& )
3938
// keep this cycle alive
3939
tJUST_CONTROLLED_PTR< gCycle > keep( this->GetRefcount()>0 ? this : 0 );
3941
resimulate_ = false;
3943
// calculate turning
3944
bool turned = false;
3945
REAL turnDirection=( dirDrive*lastSyncMessage_.dir );
3946
REAL notTurned=eCoord::F( dirDrive, lastSyncMessage_.dir );
3948
// calculate the position of the last turn from the sync data
3949
if ( distance > 0 && ( notTurned < .99 || this->turns < lastSyncMessage_.turns ) )
3955
// update old wall as good as we can
3956
eCoord crossPos = lastSyncMessage_.pos;
3957
REAL crossDist = lastSyncMessage_.distance;
3958
REAL crossTime = lastSyncMessage_.time;
3960
// calculate intersection of old and new trajectory (if only one turn was made)
3961
// the second condition is for old servers that don't send the turn count; they
3962
// don't support multiple axes, so we can possibly detect missed turns if
3963
// the dot product of the current and last direction is negative.
3964
if (this->turns+1 >= lastSyncMessage_.turns && ( lastSyncMessage_.turns > 0 || notTurned > -.5 ) )
3966
if ( fabs( turnDirection ) > .01 )
3968
REAL b = ( crossPos - pos ) * dirDrive;
3969
REAL distplace = b/turnDirection;
3970
crossPos = crossPos + lastSyncMessage_.dir * distplace;
3971
crossDist += distplace;
3972
if ( lastSyncMessage_.speed > 0 )
3973
crossTime += distplace / lastSyncMessage_.speed;
3975
tASSERT( fabs ( ( crossPos - pos ) * dirDrive ) < 1 );
3976
tASSERT( fabs ( ( crossPos - lastSyncMessage_.pos ) * lastSyncMessage_.dir ) < 1 );
3978
// update the old wall
3980
currentWall->Update(crossTime,crossPos);
3985
// a turn sync was dropped for whatever reason. The last wall is not reliable.
3986
// make it disappear immediately.
3989
currentWall->real_CopyIntoGrid(grid);
3993
eDebugLine::SetTimeout(5);
3994
eDebugLine::SetColor( 1,0,0 );
3995
eDebugLine::Draw( crossPos, 0, crossPos, 8 );
3999
lastWall=currentWall;
4000
currentWall->CopyIntoGrid( grid );
4001
tControlledPTR< gNetPlayerWall > bounce( currentWall );
4005
// create new wall at sync location
4006
distance = lastSyncMessage_.distance;
4007
currentWall=new gNetPlayerWall
4008
(this,crossPos,lastSyncMessage_.dir,crossTime,crossDist);
4012
// save last driving direction
4013
lastDirDrive = dirDrive;
4017
skewDot+=4*turnDirection;
4019
// calculate timestep
4020
// REAL ts = lastSyncMessage_.time - lastTime;
4022
// update position, speed, distance and direction
4023
MoveSafely( lastSyncMessage_.pos, lastTime, lastTime );
4024
verletSpeed_ = lastSyncMessage_.speed;
4026
distance = lastSyncMessage_.distance;
4027
dirDrive = lastSyncMessage_.dir;
4028
rubber = lastSyncMessage_.rubber;
4029
brakingReservoir = lastSyncMessage_.brakingReservoir;
4031
// update time to values from sync
4032
REAL oldTime = lastTime;
4033
lastTime = lastSyncMessage_.time;
4036
clientside_action();
4038
// bend last driving direction if it is antiparallel to the current one
4039
if ( lastDirDrive * dirDrive < EPS && eCoord::F( lastDirDrive, dirDrive ) < 0 )
4041
lastDirDrive = dirDrive.Turn(0,1);
4044
// correct laggometer to actual facts
4045
if (Owner() != sn_myNetID )
4047
// calculate lag from sync delay
4048
REAL lag = se_GameTime() - lastSyncMessage_.time;
4052
// try not to let the lag jump
4053
REAL maxLag = laggometer * 1.2;
4054
REAL minLag = laggometer * .8;
4059
// clamp smooth laggometer to it (or set it if a turn was made)
4060
//if ( laggometerSmooth > lag )
4061
// laggometerSmooth = lag;
4063
// For the client and without prediction:
4064
// do not simulate if a turn was made, just accept sync as it is
4065
// (so turns don't look awkward)
4067
sn_GetNetState()==nCLIENT && turned )
4069
laggometerSmooth = lag;
4074
// sanity check: the lag should not jump up suddenly
4075
// (and if it does, we'll catch up at the next turn)
4076
// REAL maxLag = 1.2 * laggometerSmooth;
4077
if ( laggometer > maxLag )
4078
laggometer = maxLag;
4079
if ( laggometer < minLag )
4080
laggometer = minLag;
4084
// simulate to extrapolate, but don't touch the smooth laggometer
4086
REAL laggometerSmoothBackup = this->laggometerSmooth;
4087
TimestepThis(oldTime, this);
4088
this->laggometerSmooth = laggometerSmoothBackup;
4093
void gCycle::old_ReadSync(nMessage &m){
4094
REAL oldTime=lastTime;
4096
//+correctPosSmooth;
4097
//correctPosSmooth=0;
4099
eNetGameObject::ReadSync(m);
4101
REAL t=(dirDrive*dir);
4104
if (owner==sn_myNetID)
4105
con << "Warning! Turned cycle!\n";
4120
if (alive && new_alive!=1){
4121
new gExplosion(pos,oldTime,r,g,b);
4123
eEdge::SeethroughHasChanged();
4129
// go to old time frame
4132
REAL realtime=lastTime;
4133
REAL realdist=distance;
4136
lastWall=currentWall;
4138
m.Read(currentWallID);
4140
unsigned short Turns;
4151
TimestepThis(oldTime,this);
4153
if (!currentWall || fabs(t)>.3 || currentWall->inGrid){
4154
//REAL d=eCoord::F(pos-realpos,olddir);
4155
//eCoord crosspos=realpos+olddir*d;
4156
//d=eCoord::F(oldpos-crosspos,dir);
4157
//crosspos=crosspos+dir*d;
4159
eCoord crosspos=realpos;
4162
currentWall->Update(realtime,crosspos);
4163
//currentWall->Update(realtime,realpos);
4164
currentWall->CopyIntoGrid();
4167
currentWall=new gNetPlayerWall
4168
(this,crosspos,dirDrive,realtime,realdist);
4172
// smooth correction
4173
if ((oldpos-pos).NormSquared()<4 && fabs(t)<.5){
4174
correctPosSmooth=pos-oldpos;
4182
//if(sn_Update(turns,Turns))
4183
//con << "Updated turns form " << old_t << " to " << turns << "\n";
4185
sn_Update(turns,Turns);
4189
void gCycle::ReceiveControl(REAL time,uActionPlayer *act,REAL x){
4191
TimestepThis(time,this);
4196
void gCycle::PrintName(tString &s) const
4198
s << "gCycle nr. " << ID();
4202
this->player->PrintName( s );
4206
bool gCycle::ActionOnQuit()
4208
// currentWall = NULL;
4210
if ( sn_GetNetState() == nSERVER )
4223
nDescriptor &gCycle::CreatorDescriptor() const{
4228
void gCycle::AddDestination(gDestination *dest){
4229
// con << "got new dest " << dest->position << "," << dest->direction
4230
// << "," << dest->distance << "\n";
4232
dest->InsertIntoList(&destinationList);
4234
// if the new destination was inserted at the end of the list
4235
//if (!currentDestination && !dest->next &&
4236
//if ((dest->distance >= distance || (!currentDestination && !dest->next)) &&
4238
if (dest->next && dest->next->hasBeenUsed){
4243
// go back one destination if the new destination appears to be older than the current one
4244
if ((!currentDestination || currentDestination == dest->next ) &&
4245
sn_GetNetState()!=nSTANDALONE && Owner()!=::sn_myNetID){
4246
currentDestination=dest;
4247
// con << "setting new cd\n";
4252
void gCycle::RightBeforeDeath( int numTries )
4256
player->RightBeforeDeath( numTries );
4259
// delay syncs for old clients; they would tunnel locally
4260
if ( sg_avoidBadOldClientSync && !sg_NoLocalTunnelOnSync.Supported( Owner() ) )
4262
nextSync=tSysTimeFloat()+sg_syncIntervalEnemy;
4263
nextSyncOwner=tSysTimeFloat()+sg_GetSyncIntervalSelf( this );
4266
correctPosSmooth = correctPosSmooth * .5;
4269
// *******************************************************************************************
4271
// * DoIsDestinationUsed
4273
// *******************************************************************************************
4275
//! @param dest the destination to test
4276
//! @return true if the destination is still in active use
4278
// *******************************************************************************************
4280
bool gCycle::DoIsDestinationUsed( const gDestination * dest ) const
4282
return ( extrapolator_ && extrapolator_->IsDestinationUsed( dest ) ) || gCycleMovement::DoIsDestinationUsed( dest );
4285
// *******************************************************************************************
4287
// * DoGetDistanceSinceLastTurn
4289
// *******************************************************************************************
4291
//! @return the distance driven since the last turn
4293
// *******************************************************************************************
4296
REAL gCycle::DoGetDistanceSinceLastTurn( void ) const
4300
return ( currentWall->Pos(1) - currentWall->Pos(0) );
4307
// *******************************************************************************************
4309
// * DoGetDistanceSinceLastTurn
4311
// *******************************************************************************************
4313
//! @return the distance driven since the last turn
4315
// *******************************************************************************************
4318
REAL gCycleExtrapolator::DoGetDistanceSinceLastTurn( void ) const
4320
return eCoord::F( pos - lastTurn_, dirDrive );
4324
// *******************************************************************************************
4328
// *******************************************************************************************
4330
//! @return true if the cycle is vulnerable
4332
// *******************************************************************************************
4334
bool gCycle::Vulnerable() const
4336
return lastTime > spawnTime_ + sg_cycleInvulnerableTime;