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
***************************************************************************
32
#include "eGameObject.h"
40
#include "eDebugLine.h"
41
#include "gAICharacter.h"
42
#include "tReferenceHolder.h"
44
#include "tRecorder.h"
48
#define AI_EMERGENCY 1
50
#define AI_STATE_TRACE 3
51
#define AI_STATE_CLOSECOMBAT 4
52
#define AI_STATE_PATH 5
56
#define AI_DETECTTRACE 9
57
#define AI_STARTSTATE 10
58
#define AI_STARTSTRAIGHT 11
59
#define AI_STATECHANGE 12
61
static tReferenceHolder< gAIPlayer > sg_AIReferences;
64
//#define TESTSTATE AI_PATH
65
//#define TESTSTATE AI_TRACE
69
static tCONTROLLED_PTR(gAITeam) sg_AITeam = NULL;
71
static gAITeam* AITeam()
75
sg_AITeam = tNEW( gAITeam );
81
static void ClearAITeam()
86
// an instance of this class will prevent deterministic random lookups
87
class gRandomController
90
static gRandomController * random_;
91
gRandomController * lastRandom_;
92
tRandomizer & randomizer_;
94
gRandomController( tRandomizer & randomizer = tRandomizer::GetInstance() )
95
: lastRandom_( random_ ), randomizer_( randomizer )
102
random_ = lastRandom_;
106
gRandomController * gRandomController::random_ = 0;
110
if ( gRandomController::random_ )
112
tRandomizer & randomizer = gRandomController::random_->randomizer_;
113
return randomizer.Get();
117
tRandomizer & randomizer = tRandomizer::GetInstance();
118
return randomizer.Get();
124
REAL delay = sg_delayCycle * .9f;
126
REAL fd = se_AverageFrameTime()*1.5f;
135
static gAICharacter* BestIQ( int iq )
139
static tArray<bool> inGame(gAICharacter::s_Characters.Len());
140
for (i = gAICharacter::s_Characters.Len()-1; i>=0; i--)
143
for (i = se_PlayerNetIDs.Len()-1; i>=0; i--)
145
// count the active AIs
146
ePlayerNetID *p = se_PlayerNetIDs(i);
147
gAIPlayer *ai = dynamic_cast<gAIPlayer*>(p);
148
if (ai && ai->Character() )
150
int index = ai->Character() - &(gAICharacter::s_Characters(0));
151
inGame(index) = true;
155
// find the best fitting IQ that could be inserted:
156
gAICharacter* bestIQ = 0;
157
for (i = gAICharacter::s_Characters.Len()-1; i>=0; i--)
159
if (!bestIQ || fabs(bestIQ->iq - iq) > fabs(gAICharacter::s_Characters(i).iq - iq))
160
bestIQ = &gAICharacter::s_Characters(i);
167
gAITeam::gAITeam(nMessage &m) : eTeam(m)
169
// teams.Remove( this, listID );
174
// teams.Remove( this, listID );
177
static nNOInitialisator<gAITeam> gAITeam_init(331,"gAITeam");
179
nDescriptor &gAITeam::CreatorDescriptor() const
184
// fill empty team positions with AI players
185
void gAITeam::BalanceWithAIs(bool balanceWithAIs)
187
// set correct team number
188
EnforceConstraints();
190
int numTeams = 0, numTeamsWithPlayers = 0;
192
// determine the maximum number of human players on a team
194
int maxP = eTeam::minPlayers;
195
for ( i = teams.Len()-1; i>=0; --i )
199
t->UpdateProperties();
201
if ( t->BalanceThisTeam() )
204
if ( t->NumHumanPlayers() > 0 )
205
numTeamsWithPlayers++;
207
int humans = t->NumHumanPlayers();
215
// make sure all teams have equal number
216
for ( i = teams.Len()-1; i>=0; --i )
220
if ( t->BalanceThisTeam() )
222
int wishAIs = maxP - t->NumHumanPlayers();
224
// don't add AI players to human team if it is not requested
225
if ( !balanceWithAIs && t->NumHumanPlayers() > 0 )
228
// don't genereate AI only teams if it is not requested by the min team count
229
if ( numTeamsWithPlayers >= minTeams && t->NumHumanPlayers() == 0 )
232
// add AI only teams to team count
233
if ( wishAIs > 0 && t->NumHumanPlayers() == 0 )
234
numTeamsWithPlayers++;
236
int AIs = t->NumAIPlayers();
238
while ( AIs > wishAIs )
241
gAIPlayer* throwOut = NULL;
242
for ( j=t->NumPlayers()-1; j>=0; --j )
244
gAIPlayer* player = dynamic_cast<gAIPlayer*>( t->Player( j ) );
245
if ( player && !player->IsHuman() && ( !throwOut || !throwOut->Character() || !player->Character() || throwOut->Character()->iq > player->Character()->iq ) )
253
throwOut->RemoveFromGame();
254
// throwOut->ReleaseOwnership();
257
if ( throwOut->GetRefcount() > 1 )
263
sg_AIReferences.Remove( throwOut );
269
while ( AIs < wishAIs )
271
// add the smartest AI player you can get
272
gAICharacter* best = BestIQ( 1000 );
276
gAIPlayer *ai = tNEW( gAIPlayer ) ();
277
ai->character = best;
278
ai->SetName( best->name );
282
sg_AIReferences.Add( ai );
290
// get rid of deleted netobjects (teams, mostly)
291
nNetObject::ClearAllDeleted();
294
// may player join this team?
295
bool gAITeam::PlayerMayJoin(const ePlayerNetID* player) const
297
return !player->IsHuman();
301
// this class describes a single wall close event
302
class gCycleTouchEvent{
304
REAL dist; // the position on this cycle's wall the touching happened
305
REAL otherDist; // the position on the other cylce's wall
306
int otherSide; // the side of the other cylce this wall touches
307
int winding; // winding number to add if we cross here
317
class gCycleMemoryEntry{
319
gCycleMemory *memory;
321
nObserverPtr< gCycle > cycle;
323
gCycleMemoryEntry(gCycleMemory* m, const gCycle* c)
324
:memory(m),id(-1), cycle(c)
326
memory->memory.Add(this, id);
328
max[0].dist = -1E+30;
329
max[1].dist = -1E+30;
336
memory->memory.Remove(this, id);
340
gCycleTouchEvent max[2]; // latest touch event (with the given cylce)
341
gCycleTouchEvent min[2]; // earliest touch event
346
// look for a closed loop in the walls if cycle a hits cycle b's wall at
347
// distance bDist and on side bSide.
348
// Look for the loop in driving direction of b if dir is 1 or to the other side of dir is 0.
349
// the end of the loop is reached when the wall of cycle a is driven along in
350
// direction aEndDir, passing the distance aEndDist and the side's bit is set
352
// Cycles that will be closed in the loop are stored in the array
354
// return value: the number of open points this loop contains
355
// (if this is >0, that usually means the space is wide open)
356
// or -1 if there is no loop.
357
static bool CheckLoop(const gCycle *a, const gCycle *b,
358
REAL bDist, int bSide, int dir,
359
tArray<const gCycle*>& closedIn, int& winding,
360
REAL aEndDist = 0, int aEndSides = 3, int aEndDir = 1 )
362
tASSERT(0<= bSide && 1 >= bSide);
363
tASSERT(0<= dir && 1 >= dir);
365
int tries = 10; // so long until we give up
368
bool bClosedIn = false;
370
const gCycle *run = b; // we run along this cycle's wall
371
int end = dir; // and move towards this end its wall wall
372
int side = bSide; // we are on this side of the cycle
373
REAL dist = bDist; // and are at this distance.
374
winding = 0; // the winding number we collected
376
int turn = a->Grid()->WindingNumber();
377
int halfTurn = turn >> 1;
384
while(tries-- > 0 && run &&
387
aEndSides & (1 << side) &&
388
(end > 0 ? dist >= aEndDist : dist <= aEndDist ) ) )
391
// con << "end = " << end << ", side = " << side << ", dist = " << dist
392
// << ", winding = " << winding << "\n";
397
// find the last connection
398
gCycleMemoryEntry* last = run->memory.Latest(side);
399
if (!last || last->max[side].dist <= dist + TOL)
401
// no interference. We can move directly around the cylce
404
// con << "Turning around...\n";
407
winding += halfTurn *
408
( side > 0 ? -1 : 1);
412
closedIn[closedIn.Len()] = run;
413
dist = run->GetDistance();
428
// con << "Crossing...\n";
431
// find the first connection
432
gCycleMemoryEntry* first = run->memory.Earliest(side);
433
if (first && first->min[side].dist >= dist + TOL)
435
// we cross the connection:
436
winding += first->min[side].winding;
438
end = (side == first->min[side].otherSide) ? 1 : 0;
441
// we need to turn around to follow
442
winding += halfTurn * (side > 0 ? 1 : -1);
444
dist = first->min[side].otherDist;
445
side = first->min[side].otherSide;
454
else // dir = -1, we move towards the end
456
// find the first connection
457
gCycleMemoryEntry* first = run->memory.Earliest(side);
458
if (!first || first->min[side].dist >= dist - TOL)
461
// con << "Turning around...\n";
465
// no interference. We can move directly around the cylce's end.
466
winding += halfTurn * ( side > 0 ? 1 : -1);
476
// con << "Crossing...\n";
480
// find the latest connection
481
gCycleMemoryEntry* last = run->memory.Latest(side);
482
if (last && last->max[side].dist <= dist - TOL)
484
// we cross the connection:
485
winding += last->max[side].winding;
487
// we need to turn around to start:
488
winding += halfTurn * (side > 0 ? -1 : 1);
491
end = (side == last->max[side].otherSide) ? 0 : 1;
494
// we need to turn around to follow
495
// winding -= halfTurn * (side > 0 ? 1 : -1);
497
dist = last->max[side].otherDist;
498
side = last->max[side].otherSide;
501
// uh oh. we are already closed in. No chance...
511
// con << "end = " << end << ", side = " << side << ", dist = " << dist
512
// << ", winding = " << winding << "\n\n";
527
// see if the given Cycle is trapped currently
528
static bool IsTrapped(const gCycle *trapped, const gCycle *other)
530
tArray<const gCycle*> closedIn;
532
if (CheckLoop(trapped, trapped, trapped->GetDistance(), 1, 0, closedIn, winding, trapped->GetDistance() - 1))
536
// see if the other cylce is trapped with him
537
for (int i = closedIn.Len()-1; i>=0; i--)
538
if (other == closedIn(i))
539
return false; // we can get him!
541
// no. trapped is trapped allone.
555
bool loop; // is there a loop?
556
int winding; // in what direction does it go?
557
tArray<const gCycle*>closedIn; // which cycles are closed in?
559
gLoopData():loop(false), winding(0){}
560
void AddCycle(const gCycle* c){closedIn[closedIn.Len()] = c;}
566
const eHalfEdge* edge; // edge we hit
567
gSensorWallType wallType; // type of the wall hit
568
int lr; // does the wall go left or right?
569
REAL distance; // distance to the wall
571
// additional info if the wall that got hit is a cycle wall
572
gCycle *otherCycle; // the cylce that the hit wall belongs to
573
REAL driveDistance; // the distance it had travelled when it was at the place we hit
574
int windingNumber; // the winding number at the place hit
576
gHitData():edge(NULL), wallType(gSENSOR_NONE), otherCycle(NULL){}
578
bool Hit() const {return edge;}
580
void AddHit(const eCoord& origin, const eCoord& dir, const gSensor& sensor, int winding)
583
return; // no hit, nothing to do
586
REAL otherDist = eCoord::F(*sensor.ehit->Point(), dir)
587
- eCoord::F(origin, dir);
590
REAL otherDist2 = eCoord::F(*sensor.ehit->Other()->Point(), dir)
591
- eCoord::F(origin, dir);
592
if (otherDist2 < otherDist)
593
otherDist = otherDist2;
596
REAL otherDist = eCoord::F( dir, sensor.before_hit - origin );
599
return; // the new hit is a wall we apparently drive along; nothing to do
601
if (Hit() && distance < otherDist)
602
return; // the hit that is already stored is more relevant. Ignore the new hit.
604
// copy the relevant data
606
wallType = sensor.type;
608
distance = otherDist;
610
REAL alpha = edge->Ratio(sensor.before_hit);
612
// get the extra information from the wall we hit:
613
gPlayerWall *w = dynamic_cast<gPlayerWall*>(edge->GetWall());
617
w = dynamic_cast<gPlayerWall*>(edge->Other()->GetWall());
622
otherCycle = w->Cycle();
623
driveDistance = w->Pos(alpha);
624
windingNumber = w->WindingNumber() - winding;
629
// special sensor that scans a broader area (not just a raycast)
632
// the raw data we collect:
634
const gCycle* cycle; // the cycle that sent out this sensor
636
bool hit; // whether a dangerous spot is hit
638
gHitData front; // what happens in our front
639
gHitData sides[2]; // and on our sides
641
// what we make of it:
642
gLoopData frontLoop[2]; // does the front wall we hit cause us to be trapped if we turn left or right?
643
gLoopData sideLoop[2][2]; // do the two side walls cause us to be trapped if we turn/drive straight on?
645
REAL distance; // distance to the closest wall
652
void DetectLoop(const gHitData& hit, gLoopData loopData[2])
655
gCycle *other = hit.otherCycle;
659
// if (other!= Object())
661
REAL dist = hit.driveDistance;
662
int otherSide = hit.lr < 0 ? 0 : 1;
663
for (int i=1; i>=0; i--)
665
loopData[i].closedIn.SetLen(0);
666
loopData[i].loop = false;
669
int dir = hit.lr * lr > 0 ? 1 : 0;
671
bool loop = CheckLoop(cycle, other,
672
dist, otherSide, dir,
673
loopData[i].closedIn, winding);
677
// complete the winding calculation: the target winding
678
// is the one of this cycle:
679
winding += cycle->WindingNumber();
680
// and the source is the winding of the wall we hit
681
winding -= hit.windingNumber;
684
// winding -= lr * (Object()->Grid()->WindingNumber() >> 1);
689
if (winding != 4 && winding != -4)
691
gRandomController noRandom;
696
CheckLoop(cycle, other,
697
dist, otherSide, dir,
698
loopData[i].closedIn, winding);
700
winding += cycle->WindingNumber();
701
winding -= hit.windingNumber;
707
// con << "winding = " << winding << " ,direction = " << lr << "\n";
709
// if the winding continues the direction we would turn in,
711
if (winding * lr > 0)
712
loopData[i].loop = true;
714
loopData[i].winding = winding;
721
gAISensor(const gCycle* c,
722
const eCoord& start, const eCoord& dir,
723
REAL sideScan, // the amout of space we should scan left and right
724
REAL frontScan, // the total front scanning range
725
REAL corridorScan, // the corridor scanning range
726
int winding // direction relative to the cycle's driving direction
728
:cycle(c), distance(frontScan*2)
730
gAIPlayer* ai = dynamic_cast<gAIPlayer*>(c->Player());
732
gAICharacter* character = ai->Character();
737
eDebugLine::SetTimeout(.5);
739
gCycle* cycle = const_cast<gCycle*>( this->cycle );
741
// detect straight ahead
742
gSensor ahead(cycle, start, dir);
747
ahead.detect(frontScan);
748
front.AddHit(start, dir, ahead, winding);
752
distance = front.distance;
753
if (character->properties[AI_LOOP] > 3 + fabsf(winding) * 3)
754
DetectLoop(front, frontLoop);
756
} while (!front.Hit() && count++ < character->properties[AI_RANGE]);
758
// adapt the corridor distance so the corridor is not looked for too far away
759
if (distance*.99f < corridorScan)
760
corridorScan = distance * .99f;
761
corridorScan -= sideScan * .02;
762
if (corridorScan < 0.1f)
765
if (Random() * 10 < character->properties[AI_TUNNEL])
768
eCoord lookTunnel = start + dir * corridorScan;
772
for (i = 1; i>=0; i--)
775
eCoord lrDir = dir.Turn(eCoord(.01f, -lr));
777
gSensor side(cycle, start, lrDir);
778
side.detect(sideScan*1.01f);
779
REAL thisSideScan = side.hit*.99f;
781
gSensor tunnel(cycle, lookTunnel, lrDir);
782
tunnel.detect(thisSideScan);
783
sides[i].AddHit(start, dir, tunnel, winding + lr);
785
gSensor parallel(cycle, start + lrDir * thisSideScan, dir);
786
parallel.detect(corridorScan);
787
sides[i].AddHit(start, dir, parallel, winding);
791
if (character->properties[AI_LOOP] > 6 + fabsf(winding) * 3)
792
DetectLoop(sides[i], sideLoop[i]);
794
if (sideLoop[i][1-i].loop ||
795
(character->properties[AI_TUNNEL] >= 10 &&
796
sides[i].otherCycle &&
797
sides[i].otherCycle->Team() != cycle->Team() &&
798
sides[i].lr * (i+i-1) < 0 &&
799
sides[i].otherCycle->GetDistance() < sides[i].driveDistance + sides[i].otherCycle->Speed() * 20)
802
if (sides[i].distance < distance)
803
distance = sides[i].distance;
811
gRandomController noRandom;
812
gSensor ahead(cycle, start, dir);
813
ahead.detect(frontScan);
815
gSensor side(cycle, start, lrDir);
816
side.detect(sideScan*1.01f);
817
REAL thisSideScan = side.hit*.99f;
819
gSensor tunnel(cycle, lookTunnel, lrDir);
820
tunnel.detect(thisSideScan);
821
// sides[i].AddHit(start, dir, tunnel, winding + lr);
823
gSensor parallel(cycle, start + lrDir * thisSideScan, dir);
824
parallel.detect(corridorScan);
825
// sides[i].AddHit(start, dir, parallel, winding);
831
if (sides[1].otherCycle == sides[0].otherCycle && sides[0].otherCycle)
837
eDebugLine::SetColor (1, 1, 1);
838
eDebugLine::SetTimeout(1);
839
eDebugLine::Draw(start, .5, start + dir*distance, .5);
841
eDebugLine::SetColor (1, .5, 1);
842
eDebugLine::Draw(start + dir*distance, .5, start + dir*distance, 1.5);
846
eDebugLine::SetTimeout(0);
859
gCycleMemoryEntry* gCycleMemory::Latest (int side) const
861
side = (side > 0 ? 1 : 0);
862
gCycleMemoryEntry* ret = NULL;
863
for (int i=memory.Len()-1; i>=0; i--)
865
gCycleMemoryEntry* m = memory(i);
866
if ((!ret || (m->max[side].dist > ret->max[side].dist)
867
&& bool( m->cycle ) && m->cycle->Alive() ))
874
gCycleMemoryEntry* gCycleMemory::Earliest (int side) const
876
side = (side > 0 ? 1 : 0);
877
gCycleMemoryEntry* ret = NULL;
878
for (int i=memory.Len()-1; i>=0; i--)
880
gCycleMemoryEntry* m = memory(i);
881
if ((!ret || (m->min[side].dist < ret->min[side].dist)
882
&& bool( m->cycle ) && m->cycle->Alive()))
889
gCycleMemoryEntry* gCycleMemory::Remember(const gCycle *cycle)
891
for (int i=memory.Len()-1; i>=0; i--)
892
if (memory(i)->cycle == cycle)
895
return tNEW(gCycleMemoryEntry)(this, cycle);
898
gCycleMemory::gCycleMemory()
903
gCycleMemory::~gCycleMemory()
908
void gCycleMemory::Clear()
910
for (int i = memory.Len()-1; i>=0; i--)
914
gCycleMemoryEntry* gCycleMemory::operator()(int i) const
916
tASSERT(0 <= i && i < Len());
921
// deeper analysis functions:
923
// helper function for gAIPlayer::CycleBlocksWay()
924
static void CycleBlocksWayHelper(const gCycle *a, const gCycle *b,
925
int aDir, int bDir, REAL aDist, REAL bDist, int winding)
927
gCycleMemoryEntry* aEntry = (const_cast<gCycleMemory&>(a->memory)).Remember(b);
929
if (aDist > aEntry->max[aDir].dist)
931
aEntry->max[aDir].dist = aDist;
932
aEntry->max[aDir].otherSide = bDir;
933
aEntry->max[aDir].otherDist = bDist;
934
aEntry->max[aDir].winding = winding;
937
if (aDist < aEntry->min[aDir].dist)
939
aEntry->min[aDir].dist = aDist;
940
aEntry->min[aDir].otherSide = bDir;
941
aEntry->min[aDir].otherDist = bDist;
942
aEntry->min[aDir].winding = winding;
949
// called whenever cylce a drives close to the wall of cylce b.
950
// directions: aDir tells whether the wall is to the left (-1) or right(1)
952
// bDir tells the direction the wall of b is going (-1: to the left, 1:...)
953
// bDist is the distance of b's wall to its start.
954
void gAIPlayer::CycleBlocksWay(const gCycle *a, const gCycle *b,
955
int aDir, int bDir, REAL bDist, int winding)
959
REAL aDist = a->GetDistance();
960
aDir = (aDir > 0 ? 1 : 0);
961
bDir = (bDir > 0 ? 1 : 0);
963
int w = winding + aDir * 2 + bDir * 2;
969
CycleBlocksWayHelper(a,b,aDir,bDir,aDist,bDist, winding);
970
CycleBlocksWayHelper(b,a,bDir,aDir,bDist,aDist, -winding);
972
// what to do if cylce a tries to trace cylce b?
973
if (a->Team() != b->Team())
975
gAIPlayer* ai = dynamic_cast<gAIPlayer*>(b->Player());
976
if (ai && ai->Character() && ai->Character()->properties[AI_DETECTTRACE] > 5)
977
if(aDir != bDir && ai->nextStateChange < se_GameTime() + 5 &&
978
ai->lastChangeAttempt < se_GameTime() - 5 )
980
REAL behind = b->GetDistance() - bDist;
981
if (a->Speed() > b->Speed() * 1.2f && behind < (a->Speed() - b->Speed()) * 10)
982
{ // a is faster. Try to escape.
983
ai->SetTraceSide(aDir > 0 ? 1 : -1);
984
ai->SwitchToState(AI_TRACE, 10);
985
ai->target = const_cast< gCycle * >( a );
987
else// if (a->Speed() < b->Speed() * 1.1f)
988
{ // b is faster. Attack.
989
ai->SetTraceSide(aDir > 0 ? -1 : 1);
990
ai->SwitchToState(AI_TRACE, 10 + behind / ( a->Speed() + b->Speed() ) );
991
ai->target = const_cast< gCycle * >( a );
995
// what to do if the AI player traces his opponent by accident? Trace On!
996
ai = dynamic_cast<gAIPlayer*>(a->Player());
997
if (ai && ai->Character() && ai->Character()->properties[AI_DETECTTRACE] > 0)
998
if(aDir != bDir && ai->nextStateChange < se_GameTime() + 5 &&
999
ai->lastChangeAttempt < se_GameTime() - 5 )
1001
REAL behind = b->GetDistance() - bDist;
1002
ai->SetTraceSide(aDir > 0 ? 1 : -1);
1003
ai->SwitchToState(AI_TRACE, 10 + 4 * behind / a->Speed());
1004
ai->target = const_cast< gCycle * >( b );
1009
// called whenever a cylce blocks the rim wall.
1010
void gAIPlayer::CycleBlocksRim(const gCycle *a, int aDir)
1014
// called whenever a hole is ripped in a's wall at distance aDist.
1015
void gAIPlayer::BreakWall(const gCycle *a, REAL aDist)
1027
// which eWall is detected?
1029
#define MAXAI_COLOR 13
1031
static int current_ai;
1032
static REAL rgb_ai[MAXAI_COLOR][3]={
1048
static nNOInitialisator<gAIPlayer> gAIPlayer_init(330,"gAIPlayer");
1050
nDescriptor &gAIPlayer::CreatorDescriptor() const{
1051
return gAIPlayer_init;
1054
gAIPlayer::gAIPlayer(nMessage &m) :
1058
lastPath(se_GameTime()-100),
1059
lastTime(se_GameTime()),
1067
gAIPlayer::gAIPlayer():
1070
lastPath(se_GameTime()-100),
1071
lastTime(se_GameTime()),
1082
// find a good color
1084
current_ai=(current_ai+1) % MAXAI_COLOR;
1085
int take_ai=current_ai;
1086
int try_ai=current_ai;
1088
REAL maxmindist=-10000;
1090
for(int i=MAXAI_COLOR-1;i>=0;i--){
1093
for (int j=se_PlayerNetIDs.Len()-1;j>=-1;j--){
1094
REAL R, G, B; // the color we want to avoid
1098
ePlayerNetID *p=se_PlayerNetIDs(j);
1108
else // last case: j = -1. Test against floor color
1110
se_FloorColor(R, G, B);
1113
fabs(R - rgb_ai[try_ai][0])+
1114
fabs(G - rgb_ai[try_ai][1])+
1115
fabs(B - rgb_ai[try_ai][2]);
1117
score+=exp(-dist*dist*4);
1121
/* con << c->r << ":" << rgb_ai[try_ai][0] << '\t'
1122
<< c->g << ":" << rgb_ai[try_ai][1] << '\t'
1123
<< c->b << ":" << rgb_ai[try_ai][2] << '\t' << dist << '\n'; */
1126
//con << "md=" << mindist << "\n\n";
1132
if (mindist>maxmindist){
1139
try_ai = ((try_ai+1) % MAXAI_COLOR);
1143
r = static_cast<int>(rgb_ai[take_ai][0] * 15);
1144
g = static_cast<int>(rgb_ai[take_ai][1] * 15);
1145
b = static_cast<int>(rgb_ai[take_ai][2] * 15);
1153
// AI players don't need to log in
1157
void gAIPlayer::ConfigureAIs() // ai configuration menu
1163
// make sure this many AI players are in the game
1164
void gAIPlayer::SetNumberOfAIs(int num, int minPlayers, int iq, int tries)
1166
// balance the human teams with AI players
1167
gAITeam::BalanceWithAIs();
1169
// remove AI players that got kicked out of their team
1170
for (int i = se_PlayerNetIDs.Len()-1; i>=0; i--)
1172
// count the active AIs
1173
ePlayerNetID *p = se_PlayerNetIDs(i);
1174
gAIPlayer *ai = dynamic_cast<gAIPlayer*>(p);
1175
if ( ai && !bool( ai->NextTeam() ) )
1177
ai->RemoveFromGame();
1180
if ( ai->GetRefcount() > 1 )
1186
sg_AIReferences.Remove( ai );
1192
bool iqperfect = false;
1194
// repeat until we run out of tries or the total amount of AIs is correct
1200
gAIPlayer* worstIQ = NULL; // worst fitting AI player that is in the game
1202
// static tArray<bool> inGame(gAICharacter::s_Characters.Len());
1203
// for (i = gAICharacter::s_Characters.Len()-1; i>=0; i--)
1204
// inGame(i) = false;
1206
for (i = se_PlayerNetIDs.Len()-1; i>=0; i--)
1208
// count the active AIs
1209
ePlayerNetID *p = se_PlayerNetIDs(i);
1210
gAIPlayer *ai = dynamic_cast<gAIPlayer*>(p);
1211
if (ai && ai->NextTeam() == sg_AITeam )
1213
// int index = ((int)ai->character - (int)&gAICharacter::s_Characters(0))/sizeof(gAICharacter);
1214
// inGame(index) = true;
1216
if (!worstIQ || !worstIQ->character || fabs(worstIQ->character->iq - iq) < fabs(ai->character->iq - iq) )
1223
gAICharacter* bestIQ = BestIQ( iq );
1227
// count the active players
1228
int pcount = - minPlayers;
1229
for (i = se_PlayerNetIDs.Len()-1; i>=0; i--)
1231
ePlayerNetID *p = se_PlayerNetIDs(i);
1232
if ( !p->IsSpectating() )
1240
if (bestIQ && worstIQ && worstIQ->character )
1241
iqperfect = (fabs(bestIQ->iq - iq) > fabs(worstIQ->character->iq - iq) * .9f);
1244
// count complete. Do something!
1245
if (worstIQ && (count > 0 || (count >= 0 && !iqperfect)))
1247
// too many AIs. Delete the one with the least fitting intelligence.
1248
worstIQ->RemoveFromGame();
1249
// worstIQ->ReleaseOwnership();
1252
if ( worstIQ->GetRefcount() > 1 )
1258
sg_AIReferences.Remove( worstIQ );
1261
// remove empty AI team
1262
if ( 0 == AITeam()->NumPlayers() )
1267
if (bestIQ && count < 0)
1269
// too litte AIs. Create one.
1270
gAIPlayer *ai = tNEW(gAIPlayer)();
1271
ai->SetName( bestIQ->name );
1272
ai->character = bestIQ;
1274
sg_AIReferences.Add( ai );
1276
ai->SetTeam ( AITeam() );
1282
tColoredString printname;
1283
printname << *(ePlayerNetID*)ai << tColoredString::ColorString(.5,1,.5);
1285
mess.SetTemplateParameter(1, printname);
1286
mess << "$player_entered_game";
1288
sn_ConsoleOut( mess );
1296
while ((count != 0 ||
1304
// Possible state changes:
1305
// Every state -> Survive for 20 seconds if the victim is dead or can be assumed dead soon, or if the situation gets too dangerous
1307
// Survive -> CloseCombat if survival gets too boring
1309
// Trace -> Closecombat
1310
// Path -> Closecombat if the victim gets in view
1312
// Survive -> Trace if an enemy wall is hit
1315
// CloseCombat -> Path if the vicim gets out of view
1318
void gAIPlayer::SetTraceSide(int side)
1320
REAL time = se_GameTime();
1321
REAL ts = time - lastChangeAttempt + 1;
1322
lastChangeAttempt = time;
1324
lazySideChange += ts * side;
1325
if (lazySideChange * traceSide <= 0)
1328
traceSide = lazySideChange > 0 ? 1 : -1;
1329
lazySideChange = 10 * traceSide;
1332
if (lazySideChange > 10)
1333
lazySideChange = 10;
1334
if (lazySideChange < -10)
1335
lazySideChange = -10;
1339
void gAIPlayer::SwitchToState(gAI_STATE nextState, REAL minTime)
1341
int thisAbility = 10 - character->properties[AI_STATE_TRACE];
1345
thisAbility = character->properties[AI_STATE_TRACE];
1347
case AI_CLOSECOMBAT:
1348
thisAbility = character->properties[AI_STATE_CLOSECOMBAT];
1351
thisAbility = character->properties[AI_STATE_PATH];
1357
int nextAbility = 10;
1361
nextAbility = character->properties[AI_STATE_TRACE];
1363
case AI_CLOSECOMBAT:
1364
nextAbility = character->properties[AI_STATE_CLOSECOMBAT];
1367
nextAbility = character->properties[AI_STATE_PATH];
1374
if (nextAbility > thisAbility && Random() * 10 > nextAbility)
1378
if (state != nextState)
1379
con << "Switching to state " << nextState << "\n";
1383
nextStateChange = se_GameTime() + minTime;
1386
// state update functions:
1387
void gAIPlayer::ThinkSurvive( ThinkData & data )
1390
// do nothing much. Rely on the emergency program.
1392
random=10*(Random()/float(1));
1394
EmergencySurvive(front, left, right, -1, 1);
1395
else if (random > 9.8)
1396
EmergencySurvive(front, left, right, -1, -1);
1398
if (front.front.wallType == gSENSOR_RIM && front.distance < 10)
1404
if (data.left.front.wallType == gSENSOR_RIM)
1405
EmergencySurvive( data, 1);
1406
else if (data.right.front.wallType == gSENSOR_RIM)
1407
EmergencySurvive( data, -1);
1409
EmergencySurvive( data );
1413
if (nextStateChange > se_GameTime())
1415
data.thinkAgain = .5f;
1419
// switch from Survival to close combat if surviving is too boring
1423
// find a new victim:
1424
eCoord enemypos=eCoord(1000,100);
1426
const tList<eGameObject>& gameObjects = Object()->Grid()->GameObjects();
1427
gCycle *secondbest = NULL;
1429
// find the closest enemy
1430
for (int i=gameObjects.Len()-1;i>=0;i--){
1431
gCycle *other=dynamic_cast<gCycle *>(gameObjects(i));
1433
if (other && other->Team()!=Object()->Team() &&
1434
!IsTrapped(other, Object())){
1435
// then, enemy is realy an enemy
1436
eCoord otherpos=other->Position()-Object()->Position();
1437
if (otherpos.NormSquared()<enemypos.NormSquared()){
1438
// check if the path is clear
1439
gSensor p(Object(),Object()->Position(),otherpos);
1440
p.detect(REAL(.98));
1441
secondbest = dynamic_cast<gCycle *>(other);
1443
enemypos = otherpos;
1444
target = secondbest;
1451
target = secondbest;
1454
SwitchToState(AI_CLOSECOMBAT, 1);
1457
data.thinkAgain = 1;
1460
void gAIPlayer::ThinkTrace( ThinkData & data )
1462
gAISensor const & front = data.front;
1463
gAISensor const & left = data.left;
1464
gAISensor const & right = data.right;
1466
bool inverse = front.Hit() && front.distance < Object()->Speed() * Delay();
1468
if (left.front.wallType == gSENSOR_RIM)
1471
if (right.front.wallType == gSENSOR_RIM)
1474
bool success = EmergencySurvive(data, 0, traceSide * ( inverse ? -1 : 1));
1476
REAL & nextTurn = data.thinkAgain;
1478
if (left.front.edge)
1480
REAL a = eCoord::F(Object()->Direction(), *left.front.edge->Point() - Object()->Position());
1481
REAL b = eCoord::F(Object()->Direction(), *left.front.edge->Other()->Point() - Object()->Position());
1489
if (right.front.edge)
1491
REAL a = eCoord::F(Object()->Direction(), *right.front.edge->Point() - Object()->Position());
1492
REAL b = eCoord::F(Object()->Direction(), *right.front.edge->Other()->Point() - Object()->Position());
1496
if ( a > 0 && a < nextTurn || !left.front.edge)
1500
nextTurn/= Object()->Speed() * .98f;
1502
REAL delay = Delay() * 1.5f;
1503
if ((!Object()->CanMakeTurn() || success) && nextTurn > delay)
1509
if (nextStateChange > se_GameTime())
1512
// find a new victim:
1513
eCoord enemypos=eCoord(1000,100);
1515
const tList<eGameObject>& gameObjects = Object()->Grid()->GameObjects();
1516
gCycle *secondbest = NULL;
1518
// find the closest enemy
1519
for (int i=gameObjects.Len()-1;i>=0;i--){
1520
gCycle *other=dynamic_cast<gCycle *>(gameObjects(i));
1522
if (other && other->Team()!=Object()->Team() &&
1523
!IsTrapped(other, Object())){
1524
// then, enemy is realy an enemy
1525
eCoord otherpos=other->Position()-Object()->Position();
1526
if (otherpos.NormSquared()<enemypos.NormSquared()){
1527
// check if the path is clear
1528
gSensor p(Object(),Object()->Position(),otherpos);
1529
p.detect(REAL(.98));
1530
secondbest = dynamic_cast<gCycle *>(other);
1533
enemypos = otherpos;
1536
enemypos = otherpos;
1537
target = secondbest;
1543
eCoord relpos=enemypos.Turn(Object()->Direction().Conj()).Turn(0,1);
1547
target = secondbest;
1549
SwitchToState(AI_CLOSECOMBAT, 1);
1552
SetTraceSide((relpos.x > 0 ? 10 : -10) *
1553
(target->Speed() > Object()->Speed() ? -1 : 1));
1555
nextStateChange = se_GameTime() + 10;
1557
// SwitchToState(AI_SURVIVE, 1);
1562
void gAIPlayer::ThinkPath( ThinkData & data )
1567
eCoord dir = Object()->Direction();
1568
// REAL fs=front.distance;
1569
REAL ls=data.left.distance;
1570
REAL rs=data.right.distance;
1573
if (!target->CurrentFace() || IsTrapped(target, Object()))
1575
SwitchToState(AI_SURVIVE, 1);
1576
EmergencySurvive( data );
1578
data.thinkAgain = 4;
1582
eCoord tDir = target->Position() - Object()->Position();
1584
if ( nextStateChange < se_GameTime() )
1586
gSensor p(Object(),Object()->Position(), tDir);
1587
p.detect(REAL(.9999999));
1588
if (p.hit >= .9999999) // free line of sight to victim. Switch to close combat.
1590
SwitchToState(AI_CLOSECOMBAT, 5);
1591
EmergencySurvive( data );
1599
// find a new path if the one we got is outdated:
1600
if (lastPath < se_GameTime() - 10)
1601
if (target->CurrentFace())
1603
Object()->FindCurrentFace();
1604
eHalfEdge::FindPath(Object()->Position(), Object()->CurrentFace(),
1605
target->Position(), target->CurrentFace(),
1608
lastPath = se_GameTime();
1613
data.thinkAgain = 1;
1617
// find the most advanced path point that is in our viewing range:
1619
for (int z = 10; z>=0; z--)
1622
bool goon = path.Proceed();
1623
bool nogood = false;
1628
goon = path.GoBack();
1632
eCoord pos = path.CurrentPosition() + path.CurrentOffset() * 0.1f;
1633
eCoord opos = Object()->Position();
1634
eCoord odir = pos - opos;
1636
eCoord intermediate = opos + dir * eCoord::F(odir, dir);
1638
gSensor p(Object(), opos, intermediate - opos);
1640
nogood = (p.hit <= .999999999 || eCoord::F(path.CurrentOffset(), odir) < 0);
1644
gSensor p(Object(), intermediate, pos - intermediate);
1646
nogood = (p.hit <= .99999999 || eCoord::F(path.CurrentOffset(), odir) < 0);
1650
while (goon && nogood);
1654
// now we have found our next goal. Try to get there.
1655
eCoord pos = Object()->Position();
1656
eCoord target = path.CurrentPosition();
1658
// look how far ahead the target is:
1659
REAL ahead = eCoord::F(target - pos, dir)
1660
+ eCoord::F(path.CurrentOffset(), dir);
1663
{ // it is still before us. just wait a while.
1667
{ // we have passed it. Make a turn towards it.
1668
REAL side = (target - pos) * dir;
1670
if ( !((side > 0 && ls < 3) || (side < 0 && rs < 3))
1671
&& (fabs(side) > 3 || ahead < -10) )
1674
con << "Following path...\n";
1676
lr += (side > 0 ? 1 : -1);
1683
SwitchToState(AI_SURVIVE);
1686
EmergencySurvive( data, 1, -lr );
1688
REAL d = sqrt(tDir.NormSquared()) * .2f;
1692
data.thinkAgain = mindist / Object()->Speed();
1693
if (data.thinkAgain > .4)
1694
data.thinkAgain *= .7;
1698
void gAIPlayer::ThinkCloseCombat( ThinkData & data )
1702
REAL nextThought = 0;
1704
const gAISensor* sides[2];
1705
sides[0] = &data.left;
1706
sides[1] = &data.right;
1708
eCoord dir = Object()->Direction();
1709
REAL fs=data.front.distance;
1710
// REAL ls=left.hit;
1711
// REAL rs=right.hit;
1713
if ( bool( target ) && !IsTrapped(target, Object()) && nextStateChange < se_GameTime() )
1715
gSensor p(Object(),Object()->Position(),target->Position() - Object()->Position());
1717
if (p.hit <= .999999) // no free line of sight to victim. Switch to path mode.
1719
SwitchToState(AI_PATH, 5);
1720
EmergencySurvive( data );
1727
const REAL fear=REAL(.01);
1728
const REAL caution=.001;
1729
const REAL evasive=100;
1730
const REAL attack=100;
1731
const REAL seek=REAL(1);
1732
const REAL trap=REAL(.01);
1734
// const REAL close=1000;
1736
REAL random=10*Random()*Random();
1738
if ( bool( target ) && target->Alive()){
1740
eCoord enemypos=target->Position()-Object()->Position();
1741
eCoord enemydir=target->Direction();
1742
REAL enemyspeed=target->Speed();
1744
ed=REAL(fabs(enemypos.x)+fabs(enemypos.y));
1747
// transform coordinates relative to us:
1748
enemypos=enemypos.Turn(dir.Conj()).Turn(0,1);
1749
enemydir=enemydir.Turn(dir.Conj()).Turn(0,1);
1751
// now we are at the center of the coordinate system facing
1752
// in direction (0,1).
1754
// rules are symmetrical: exploit that.
1761
sides[1] = &data.left;
1762
sides[0] = &data.right;
1765
// now we can even assume the enemy is on our right side.
1767
// consider his ping and our reaction time
1771
//REAL enemyspeed=target->speed;
1772
REAL ourspeed=Object()->Speed();
1774
REAL enemydist=target->Lag()*enemyspeed;
1776
// redo the prediction
1778
if (sn_GetNetState()==nCLIENT && !sr_predictObjects)
1780
enemypos=enemypos-enemydir*enemydist;
1781
enemydist+=2*REACTION *enemyspeed;
1783
REAL ourdist=REACTION*ourspeed;;
1786
// now we consider the worst case: we drive straight on,
1787
enemypos.y-=ourdist;
1788
// while the enemy cuts us: he goes in front of us
1789
REAL forward=-enemypos.y+.01;
1790
if (forward<0) // no need to go to much ahead
1792
if (forward>enemydist)
1795
enemypos.y+=forward;
1798
// and then he turns left.
1799
enemypos.x-=enemydist;
1801
if (enemypos.y*enemyspeed>enemypos.x*ourspeed){ // he is right ahead of us.
1802
if (random<fear){ // evade him
1810
if (enemypos.y<=ffar &&
1811
((enemydir.x<0 && random<evasive) ||
1812
(enemydir.y>0 && random<caution) ||
1813
(enemydir.y<0 && random<attack))){
1815
con << "caution!\n";
1821
if (enemyspeed > ourspeed)
1823
SetTraceSide(-side);
1824
SwitchToState(AI_TRACE, 10);
1828
else if (enemypos.y*ourspeed<-enemypos.x*enemyspeed){
1830
// good attack position
1831
if (enemypos.x<rs && rs < range*.99){
1841
REAL canCutIfDriveOn = enemypos.x*ourspeed - fs * (enemyspeed - ourspeed);
1842
canCutIfDriveOn -= enemypos.y*enemyspeed;
1844
REAL canCutIfAttack = - sides[1]->distance * enemyspeed
1845
- (sides[1]->distance - enemypos.x -enemypos.y*ourspeed) * ourspeed;
1847
if (random<attack && (!(data.front.Hit() && data.front.distance < 20) || canCutIfAttack > canCutIfDriveOn)){
1856
else if(enemypos.x>ffar*4){
1866
else if (enemypos.x<ffar*2 && fabs(enemypos.y)<ffar){
1878
if (!EmergencySurvive(data, 1, -lr))
1881
data.thinkAgain = ed/2 + nextThought;
1886
static void PretendFrontHit(const gAISensor& f, const gAISensor &corridor,
1887
const eCoord& origin, const eCoord& direction)
1889
gAISensor& front = (gAISensor&)(f);
1891
// transfer the easy data
1892
front.ehit = corridor.ehit;
1893
front.lr = corridor.lr;
1894
front.type = corridor.type;
1896
REAL a = eCoord::F(*corridor.ehit->Point() - origin, direction);
1897
REAL b = eCoord::F(*corridor.ehit->Other()->Point() - origin, direction);
1904
front.before_hit = origin + direction * front.hit * .99f;
1909
#define DANGERLEVELS 4
1911
#define SPACELEVEL 1
1913
#define COLIDELEVEL 2
1919
int sideDanger[DANGERLEVELS][2];
1920
int frontDanger[DANGERLEVELS];
1930
gAILogEntry entries[ENTRIES+1];
1934
gAILog():current(0), del(0){}
1943
gAILogEntry& NextEntry()
1947
if (current >= ENTRIES)
1949
for (int i=1; i<ENTRIES; i++)
1950
entries[i-1] = entries[i];
1955
gAILogEntry& ret = entries[current-1];
1956
ret.time = se_GameTime();
1964
for (int i = current + del - 1; i>=0; i--)
1966
for (int j=0; j < DANGERLEVELS; j++)
1968
con << entries[i].sideDanger[j][0] << ' ';
1969
con << entries[i].frontDanger[j] << ' ';
1970
con << entries[i].sideDanger[j][1] << " ";
1972
con << entries[i].turn << ", " << entries[i].tries << "\n";
1975
// se_PauseGameTimer(true);
1981
// emergency functions:
1982
bool gAIPlayer::EmergencySurvive( ThinkData & data, int enemyevade, int preferedSide)
1984
gAISensor const & front = data.front;
1985
gAISensor const & left = data.left;
1986
gAISensor const & right = data.right;
1992
static int last = 0;
1993
if (log->current >= 4
1994
&& log->entries[log->current-2].time > se_GameTime() - .2
1995
&& log->entries[log->current-1].turn * last <= 0
1996
&& log->entries[log->current-1].turn * log->entries[log->current-2].turn < 0
1997
// && log->entries[log->current-3].turn * log->entries[log->current-2].turn < 0
1998
// && log->entries[log->current-3].turn * log->entries[log->current-4].turn < 0
2004
last = log->entries[log->current-1].turn;
2007
triesLeft = (triesLeft * character->properties[AI_EMERGENCY])/10;
2013
// don't do a thing if there may be a better way out of we drive on:
2014
if (triesLeft > 0 &&
2015
front.front.otherCycle &&
2016
front.front.otherCycle != Object() &&
2017
((front.frontLoop[1].loop && front.front.otherCycle != left .front.otherCycle && left .front.otherCycle)||
2018
(front.frontLoop[0].loop && front.front.otherCycle != right.front.otherCycle && right.front.otherCycle ) )
2022
// get the delay between two turns
2023
REAL delay = Delay();
2024
REAL range = Object()->Speed() * delay;
2026
// nothing we can do if we cannot make a turn immediately
2027
if (!Object()->CanMakeTurn())
2030
// bool dontCheckForLoop[2] = { false, false };
2033
// look out if there is anything bad going on in one of the directions:
2034
// [signifficance: danger level of n: You'll be (as good as) dead in [10/n delay times] if you drive that way
2035
int sideDanger[DANGERLEVELS][2];
2036
int frontDanger[DANGERLEVELS];
2037
for(i = DANGERLEVELS-1; i>=0; i--)
2039
sideDanger[i][0] = 0;
2040
sideDanger[i][1] = 0;
2044
bool canTrapEnemy = false;
2048
frontDanger[SPACELEVEL] += 40;
2051
const gAISensor* sides[2];
2057
bool isTrapped = IsTrapped(Object(), NULL);
2060
if (front.front.wallType == gSENSOR_ENEMY)
2061
sideDanger[LOOPLEVEL][(1-front.front.lr*enemyevade) >> 1] += 5;
2065
for (i = 1; i>=0; i--)
2067
if (front.frontLoop[i].loop && front.distance < 5*sides[i]->distance)
2069
// if we would close ourself in, make the danger bigger
2070
if (front.front.otherCycle == Object() && i+i-1 == front.front.lr)
2071
sideDanger[LOOPLEVEL][i]+=40;
2073
sideDanger[LOOPLEVEL][i]+=40;
2074
for (j = front.frontLoop[i].closedIn.Len()-1; j>=0; j--)
2075
if (front.frontLoop[i].closedIn(j) == target)
2076
canTrapEnemy = true;
2079
for (j = 1; j>=0; j--)
2080
if (front.sideLoop[i][j].loop)
2081
sideDanger[LOOPLEVEL][j]++;
2083
// if we would close ourselfs in by a zigzag in direction i,
2084
// but not by a u-turn and there is enough space for a u-turn,
2086
if (sides[i]->frontLoop[1-i].loop &&
2087
!sides[i]->frontLoop[i].loop)
2089
if (sides[i]->distance > range)
2091
frontDanger[LOOPLEVEL] += 20;
2092
sideDanger[LOOPLEVEL][1-i] += 10;
2094
else // try to make some room so we can evade:
2096
frontDanger[LOOPLEVEL] += 20;
2098
sideDanger[LOOPLEVEL][i] += 10;
2102
// if we would close ourselves in by a U-Turn, don't do it.
2103
// if (sides[i]->frontLoop[i].loop && sides[i].distance < range * 2)
2104
// sideDanger[LOOPLEVEL][i] += 40;
2107
// try to trap the enemy
2108
if (character->properties[AI_LOOP] >= 10 && canTrapEnemy && !emergency)
2113
// avoid closing yourself or a teammate in.
2114
if (front.type == gSENSOR_SELF || front.type == gSENSOR_TEAMMATE)
2117
sideDanger[][1] +=2;
2119
sideDanger[][0] +=2;
2127
( front.distance + range < sides[0]->distance ||
2128
front.distance + range < sides[1]->distance) )
2130
if ( front.front.wallType == gSENSOR_RIM)
2131
frontDanger[SPACELEVEL] += static_cast<int>(100 * range * gArena::SizeMultiplier() / (front.distance + range * .1));
2133
frontDanger[SPACELEVEL] += static_cast<int>(5 * range / (front.distance + range *.2));
2135
if (front.distance < range)
2136
frontDanger[SPACELEVEL] += static_cast<int>(20 * range / (front.distance + range *.2)) + 1;
2140
// avoid close corners:
2141
for (i = 1; i>=0; i--)
2143
if (sides[i]->Hit() && //sides[i]->distance < range * 3 &&
2144
sides[i]->distance < front.distance + range)
2146
if ( sides[i]->front.wallType == gSENSOR_RIM)
2147
sideDanger[SPACELEVEL][i] += static_cast<int>(150 * range * gArena::SizeMultiplier() / (sides[i]->distance + range * .1));
2149
sideDanger[SPACELEVEL][i] += static_cast<int>
2150
(range * 5 / (sides[i]->distance + range * .1));
2152
if (sides[i]->distance < range)
2153
sideDanger[SPACELEVEL][i] += static_cast<int>
2154
(range * 20 / (sides[i]->distance + range * .1));
2157
// give us a chance to turn around:
2158
if (frontDanger[SPACELEVEL] * 2 < sideDanger[SPACELEVEL][i])
2159
sideDanger[LOOPLEVEL][i-i] -= sideDanger[SPACELEVEL][i] * 2;
2163
// avoid close proximity to other cycles
2164
const gCycle* target = NULL;
2165
const tList<eGameObject>& gameObjects = Object()->Grid()->GameObjects();
2166
gCycle *secondbest = NULL;
2167
REAL closest = 1000000;
2168
eCoord dir = Object()->Direction();
2170
// find the closest enemy
2171
for (i=gameObjects.Len()-1;i>=0;i--){
2172
gCycle *other=dynamic_cast<gCycle *>(gameObjects(i));
2174
if (other && other->Alive() && other != Object())
2176
eCoord otherpos=other->Position()-Object()->Position();
2177
REAL otherNorm = otherpos.NormSquared();
2179
bool nothit = false;
2180
if (otherNorm < closest * 4)
2182
gSensor p(other, other->Position(), -otherpos);
2183
p.detect(REAL(.9999));
2184
gSensor q(Object(), Object()->Position(), otherpos);
2185
q.detect(REAL(.9999));
2187
nothit = p.hit>=.999 && q.hit >=.999;
2190
if (other->Team() != Object()->Team())
2192
// then, enemy is realy an enemy
2193
// REAL s = Object()->Speed() * 50;
2194
if (/* otherNorm < s*s && */ otherNorm < closest)
2196
// check if the path is clear
2197
secondbest = dynamic_cast<gCycle *>(other);
2199
closest = otherNorm;
2200
target = secondbest;
2206
// he is a teammate. Avoid him.
2208
eCoord friendpos=other->Position() - Object()->Position();
2210
// transform coordinates relative to us:
2211
friendpos=friendpos.Turn(dir.Conj()).Turn(0,1);
2213
if (friendpos.y > fabs(friendpos.x) * 1.5f)
2214
frontDanger[TEAMLEVEL] += 10;
2215
if (friendpos.x * 2 > -friendpos.y)
2216
sideDanger[TEAMLEVEL][1] += 10;
2217
else if (-friendpos.x * 2 > -friendpos.y)
2218
sideDanger[TEAMLEVEL][0] += 10;
2224
//target = secondbest;
2226
if (target && character->properties[AI_ENEMY] > 0)
2228
bool sdanger = false;
2229
for (i = DANGERLEVELS-1; i>=0; i--)
2230
sdanger |= sideDanger[i][0] > 4 || sideDanger[i][1] > 4;
2232
eCoord enemypos=target->Position() - Object()->Position();
2233
eCoord enemydir=target->Direction();
2234
REAL enemyspeed=target->Speed();
2237
// transform coordinates relative to us:
2238
enemypos=enemypos.Turn(dir.Conj()).Turn(0,1);
2239
enemydir=enemydir.Turn(dir.Conj()).Turn(0,1);
2241
if (character->properties[AI_ENEMY] > 7)
2243
// would he be able to trap us if we drive straight on?
2244
bool trap[2] = {false, false};
2247
for (i = 1; i>=0; i--)
2249
// if the enemy comes racing towards us, check if he could
2250
// close us in by touching our own line ON THE OPPOSITE side of i
2251
tArray<const gCycle*> closedIn;
2254
bool loop = CheckLoop(target, Object(),
2255
Object()->GetDistance() + 4 * TOL, i, 0,
2258
winding -= Object()->WindingNumber();
2259
winding += target->WindingNumber();
2262
// yes! we shoult turn in direction 1-i to get the target
2263
// to the other side.
2265
if (winding * (i+i-1) < 0)
2268
REAL x = enemypos.x * (i+i-1);
2269
REAL y = enemypos.y;
2271
bool canAccelerateByTurning =
2272
( sides[1-i]->Hit() &&
2273
sides[1-i]->distance < Object()->Speed() * delay * 5 &&
2274
sides[i-i]->distance > Object()->Speed() * delay &&
2275
!sides[i-i]->frontLoop[i].loop) ;
2277
bool ohShit = target->Speed() > Object()->Speed() + sqrt(closest);
2281
SetTraceSide(-(i+i-1));
2282
SwitchToState(AI_TRACE, 10);
2285
bool turningIsFutile =
2286
front.front.otherCycle == Object() &&
2287
sides[1-i]->front.otherCycle == Object() &&
2288
front.distance < sides[1-1]->distance * 10 ;
2293
x * Object()->Speed() < -y * target->Speed() + 1000 ||
2294
canAccelerateByTurning || ohShit
2300
if (enemydir.y < -.2f && y < 0)
2301
SetTraceSide(-(i+i-1));
2303
frontDanger[TRAPLEVEL] += 10;
2306
if ( y > 0 || x < 0 || ohShit
2307
// ( y * Object()->Speed() > x * target->Speed()*.9 - 200 || enemyspeed.x * (i+i-1)
2309
sideDanger[TRAPLEVEL][i] += 20;
2310
// sideDanger[TRAPLEVEL][i] ++;
2315
if (character->properties[AI_ENEMY] > 0)
2317
// imminent collision check
2318
REAL totalspeed = enemyspeed + Object()->Speed();
2320
if ((fabs(enemypos.y) < totalspeed * .3f && fabs(enemypos.x) < totalspeed * .3f))
2322
REAL diffSpeed = -enemydir.y * enemyspeed + Object()->Speed();
2323
if (diffSpeed > 0 && enemydir.y <= .2)
2325
REAL enemyFront = enemypos.y / diffSpeed;
2326
REAL enemySide = fabs(enemypos.x) / diffSpeed;
2327
if (enemyFront > 0 && enemyFront < .4 + enemySide && fabs(enemypos.y) > fabs(enemypos.x))
2329
frontDanger[COLIDELEVEL] += 1 + int(4 / (enemyFront + .01));
2330
// SwitchToState( AI_SURVIVE, enemyFront * 4 + 2 );
2334
int side = enemypos.x > 0 ? 1 : 0;
2336
// can we cut him instead of evade him?
2337
if (Object()->Team() != target->Team() &&
2338
( ( enemydir.y <= -.2 &&
2339
enemypos.y*target->Speed()*1.1 > fabs(enemypos.x) * Object()->Speed() ) ||
2340
sideDanger[COLIDELEVEL][side] > 0))
2341
sideDanger[COLIDELEVEL][1-side]+=5;
2342
else if ( -(enemypos.y + .3f) * Object()->Speed() < fabs(enemypos.x) * target->Speed()*1.2)
2343
sideDanger[COLIDELEVEL][side]+=10;
2348
eDebugLine::SetTimeout(.5);
2349
eDebugLine::SetColor (1, 0, 1);
2350
eCoord p = Object()->Position();
2351
eDebugLine::Draw(p, .5, p, 8.5);
2352
eDebugLine::SetTimeout(0);
2356
// determine the total danger levels by taking the max of the individual experts:
2358
int sDanger[2] = { 0, 0 };
2359
for (i = 0; i<DANGERLEVELS; i++)
2360
// for (i = 1; i< 2; i++)
2362
if (!fDanger || frontDanger[i] > fDanger + 2)
2363
fDanger = frontDanger[i];
2365
for (int j=1; j>=0; j--)
2366
if (!sDanger[j] || sideDanger[i][j] > sDanger[j] + 2)
2367
sDanger[j] = sideDanger[i][j];
2370
// nothing to do if we are not in immediate danger.
2371
if (!fDanger && !preferedSide)
2375
// decide about your direction:
2382
turn = (int) freeSide;
2384
if (!turn && front.front.wallType != gSENSOR_RIM)
2385
turn = front.front.lr * enemyevade;
2388
turn = (sides[0]->distance > sides[1]->distance ? -1 : 1);
2390
if (!turn && log->current)
2391
turn = log->entries[log->current-1].turn;
2395
// switch to survival mode if we just trapped an enemy
2399
if ( !tRecorder::IsRunning() )
2400
Chat(tString( "Hehe! Got you!" ) );
2402
SwitchToState(AI_TRACE, 10);
2405
this->SetTraceSide(-turn);
2408
gAILogEntry&e = log->NextEntry();
2410
e.tries = triesLeft;
2411
for (i = DANGERLEVELS-1; i>=0; i--)
2413
e.frontDanger[i] = frontDanger[i];
2414
e.sideDanger[i][0] = sideDanger[i][0];
2415
e.sideDanger[i][1] = sideDanger[i][1];
2420
if (preferedSide < 0)
2422
for (i = DANGERLEVELS-1; i>=0; i--)
2424
int dSwap = sideDanger[i][0];
2425
sideDanger[i][0] = sideDanger[i][1];
2426
sideDanger[i][1] = dSwap;
2429
int dSwap = sDanger[0];
2430
sDanger[0] = sDanger[1];
2440
// no problem in the preferred direction. Just take it.
2443
if( fDanger * 3 >= sDanger[1] * 2 - 5 &&
2444
sDanger[0] * 3 >= sDanger[1] * 2 - 5)
2446
freeSide -= side*100;
2452
if (fDanger * 2 <= sDanger[0] * 3 + 3)
2459
// it is safer driving straight on
2460
if (fDanger <= sDanger[0] + 3 && fDanger <= sDanger[1] + 3 && fDanger < 20)
2469
freeSide -= side*100;
2478
eDebugLine::SetTimeout(0);
2482
void gAIPlayer::EmergencyTrace( ThinkData & data )
2484
EmergencySurvive( data, -1, -traceSide );
2488
void gAIPlayer::EmergencyPath( ThinkData & data )
2490
EmergencySurvive( data );
2493
void gAIPlayer::EmergencyCloseCombat( ThinkData & data )
2495
EmergencySurvive( data );
2502
eCoord enemyPos = target->Position() - Object()->Position();
2503
eCoord dirRel = Object()->Direction();
2504
if (enemyPos * dirRel < 0)
2511
EmergencySurvive(front, left, right, 1, dir);
2518
void gAIPlayer::RightBeforeDeath(int triesLeft) // is called right before the vehicle gets destroyed.
2520
if ( nCLIENT == sn_GetNetState() )
2523
gRandomController random( randomizer_ );
2525
// think again immediately after this
2526
nextTime = lastTime;
2529
if (log && !Object()->Alive())
2538
if (!Object()->Alive() || ( character && Random() * 10 > character->properties[AI_EMERGENCY] ) )
2542
// get the delay between two turns
2543
REAL delay = Delay();
2545
this->triesLeft = triesLeft;
2546
this->emergency = (triesLeft < 2);
2548
REAL speed=Object()->Speed();
2550
eCoord dir=Object()->Direction();
2551
REAL side = speed*delay;
2553
gAISensor front(Object(),Object()->Position(),dir, side, range, range*.3, 0);
2554
gAISensor left(Object(),Object()->Position(),dir.Turn(eCoord(0,1)), side, range, range*.3, -1);
2555
gAISensor right(Object(),Object()->Position(),dir.Turn(eCoord(0,-1)), side, range, range*.3, 1);
2562
nextStateChange = se_GameTime() + 100;
2564
// switch to survival state if our victim died:
2565
if ((!target || !target->Alive()) && state != AI_TRACE)
2566
SwitchToState(AI_SURVIVE, 1);
2569
ThinkData data( front, left, right);
2573
EmergencySurvive(data);
2576
EmergencyPath(data);
2579
EmergencyTrace(data);
2581
case AI_CLOSECOMBAT:
2582
EmergencyCloseCombat(data);
2589
gAISensor front(Object(),Object()->Position(),dir, side, range, range*.3, 0);
2590
gAISensor left(Object(),Object()->Position(),dir.Turn(eCoord(0,1)), side, range, range*.3, -1);
2591
gAISensor right(Object(),Object()->Position(),dir.Turn(eCoord(0,-1)), side, range, range*.3, 1);
2596
void gAIPlayer::NewObject() // called when we control a new object
2600
lastChangeAttempt = 0;
2606
nextTime = character->properties[AI_STARTSTRAIGHT] * gArena::SizeMultiplier()/gCycleMovement::SpeedMultiplier();
2607
nextStateChange = character->properties[AI_STATECHANGE];
2608
state = (gAI_STATE)character->properties[AI_STARTSTATE];
2613
nextStateChange = 30;
2624
static gAISensor * sg_GetSensor( int currentDirectionNumber, gCycle const & object, int turn, REAL side, REAL range, REAL corridor, REAL & mindist )
2626
// determine the current direction
2627
eGrid & grid = *object.Grid();
2628
eCoord origDir = grid.GetDirection( currentDirectionNumber );
2630
// determine sensors
2631
gAISensor * ret = 0;
2634
int direction = currentDirectionNumber;
2636
grid.Turn( direction, turn );
2637
eCoord dir = grid.GetDirection( direction );
2638
while ( turn * ( origDir * dir ) > .01 && currentDirectionNumber != direction )
2641
gAISensor * sensor = tNEW(gAISensor)(&object,object.Position(),dir, side, range, corridor*.5f, turn );
2642
if ( !ret || sensor->distance > ret->distance )
2646
// calculate effective distance required to turn around in time
2647
REAL dist = ret->distance - 1.2 * object.Speed() * Delay() * turns;
2648
if ( mindist > dist )
2661
grid.Turn( direction, turn );
2662
dir = grid.GetDirection( direction );
2669
REAL gAIPlayer::Think(){
2670
// get the delay between two turns
2671
REAL delay = Delay();
2674
if (log && !Object()->Alive())
2683
if (!Object()->Alive())
2689
// first, find close eWalls and evade them.
2690
REAL speed=Object()->Speed();
2692
eCoord dir=Object()->Direction();
2693
REAL side=speed*delay;
2695
REAL corridor = range;
2696
if (corridor < side * 2)
2697
corridor = side * 2;
2699
gAISensor front(Object(),Object()->Position(),dir, side * 2, range, corridor, 0);
2704
gRandomController noRandom;
2706
if (front.distance < 1)
2711
gAISensor front(Object(),Object()->Position(),dir, side, range, corridor, 0);
2715
// get the sensors to the left and right with the most free space
2716
int currentDirectionNumber = Object()->Grid()->DirectionWinding( dir );
2717
REAL mindistLeft = 1E+30, mindistRight = 1E+30;
2718
std::auto_ptr< gAISensor > left ( sg_GetSensor( currentDirectionNumber, *Object(), -1, side, range, corridor, mindistLeft ) );
2719
std::auto_ptr< gAISensor > right ( sg_GetSensor( currentDirectionNumber, *Object(), 1, side, range, corridor, mindistRight ) );
2721
// count intermediate walls to the left and right as if they were in front
2723
REAL mindistFront = mindistLeft > mindistRight ? mindistLeft : mindistRight;
2724
if ( mindistFront < front.distance )
2726
front.distance = mindistFront;
2732
nextStateChange = se_GameTime() + 100;
2734
// switch to survival state if our victim died:
2735
if (state != AI_SURVIVE && state != AI_TRACE && (!target || !target->Alive()))
2736
SwitchToState(AI_SURVIVE, 1);
2740
eDebugLine::SetTimeout(.5);
2741
eDebugLine::SetColor (0, 1, 0);
2742
eCoord p = Object()->Position();
2743
eDebugLine::Draw(p, .5, p, 5.5);
2744
eDebugLine::SetTimeout(0);
2751
ThinkData data( front, *left, *right);
2763
case AI_CLOSECOMBAT:
2764
ThinkCloseCombat(data);
2768
ret = data.thinkAgain;
2772
gAISensor front(Object(),Object()->Position(),dir, side, range, range*.3, 0);
2773
gAISensor left(Object(),Object()->Position(),dir.Turn(eCoord(0,1)), side, range, range*.3, -1);
2774
gAISensor right(Object(),Object()->Position(),dir.Turn(eCoord(0,-1)), side, range, range*.3, 1);
2778
REAL mindist = front.distance * front.distance * 8;
2780
const tList<eGameObject>& gameObjects = Object()->Grid()->GameObjects();
2782
// find the closest enemy
2783
for (int i=gameObjects.Len()-1;i>=0;i--){
2784
gCycle *other=dynamic_cast<gCycle *>(gameObjects(i));
2786
if (other && other != Object()){
2787
// then, enemy is realy an enemy
2788
eCoord otherpos=other->Position()-Object()->Position();
2789
REAL dist = otherpos.NormSquared();
2796
mindist = sqrt(mindist) / (3 * Object()->Speed());
2804
std::ostream & operator << ( std::ostream & s, gAIPlayer::ThinkDataBase const & data )
2806
s << data.turn << " " << data.thinkAgain;
2811
std::istream & operator >> ( std::istream & s, gAIPlayer::ThinkDataBase & data )
2813
s >> data.turn >> data.thinkAgain;
2818
static char const * section = "AI";
2820
void gAIPlayer::ActOnData( ThinkData & data )
2823
ThinkDataBase & base = data;
2826
// sanitize next think time so it will be before we hit the next wall
2827
if ( Object()->Speed() > 0 && data.thinkAgain > 0 )
2829
gSensor front( Object(), Object()->Position(), Object()->Direction() );
2830
front.detect( Object()->Speed() * data.thinkAgain * 1.5 );
2833
REAL thinkAgain = ( front.hit / Object()->Speed() ) * .8;
2834
if ( data.thinkAgain > thinkAgain )
2835
data.thinkAgain = thinkAgain;
2840
void gAIPlayer::ActOnData( ThinkDataBase & data )
2843
ThinkDataBase copy = data;
2844
if ( tRecorder::Playback( section, data ) )
2846
if ( copy.turn != data.turn )
2848
// AI made a different decision than recorded, better let a programmer have a look at it
2849
std::cout << "AI turn decision changed!\n";
2853
REAL difference = fabs( copy.thinkAgain - data.thinkAgain );
2854
static REAL minReport = EPS;
2855
if ( difference > minReport )
2857
minReport = difference * 2;
2858
std::cout << "AI timing decision changed by " << difference
2859
<< " from " << data.thinkAgain << " to " << copy.thinkAgain <<"!\n";
2863
tRecorder::Record( section, data );
2867
Object()->Turn( data.turn );
2870
const REAL relax=25;
2872
void gAIPlayer::Timestep(REAL time){
2879
REAL ts=time-lastTime;
2882
if (concentration < 0)
2885
concentration += 4*(character->properties[AI_REACTION]+1) * ts/relax;
2886
concentration=concentration/(1+ts/relax);
2888
if (bool(Object()) && Object()->Alive() && nextTime<time){
2889
gRandomController random( randomizer_ );
2891
REAL nextthought=Think();
2892
// if (nextthought>.9) nextthought=REAL(.9);
2894
if (nextthought<REAL(.6-concentration)) nextthought=REAL(.6-concentration);
2896
nextTime=nextTime+nextthought;
2898
//con << concentration << "\t" << nextthought << '\t' << ts << '\n';
2900
if(.1+4*nextthought<1)
2901
concentration*=REAL(.1+4*nextthought);
2906
void gAIPlayer::Color( REAL&a_r, REAL&a_g, REAL&a_b ) const
2908
ePlayerNetID::Color( a_r, a_g, a_b );
2911
gAIPlayer::~gAIPlayer()
2921
void gAIPlayer::ClearAll()
2923
sg_AIReferences.ReleaseAll();
2925
// remove empty AI team
2926
if ( 0 == AITeam()->NumPlayers() )
2932
void gAIPlayer::AddRef()
2934
ePlayerNetID::AddRef();
2937
void gAIPlayer::Release()
2939
ePlayerNetID::Release();