Class MX_PongGame extends MX_MiniGameBase; var int Score,NumPlays; var vector PlayerPad,EnemyPad,BallPos,BallVel,BallTrajectory; var float ScreenHeight,BallWidth; var enum EBallHeading { BH_ToPlayer, BH_ToEnemy, BH_None, } BallHeading; var array HitSoundsA,HitSoundsB; var SoundCue MissSound[2]; var int HitSoundIndex[2]; var float EnemyPadVel,AITactic,AITacticTimer,AITrajOffset; var bool bAIRandom; const PadWidth=0.015; const PadHeight=0.15; const LevelBoarderSize=0.05; const PadMoveLimit=0.125; const BallHeight=0.03; const InverseXOr=879379227; function Init() { local int i; Super.Init(); if( Data!=0 ) i = Data ^ InverseXOr; Score = (i >> 16) & 32767; if( Score>31767 ) Score = Score-32768; NumPlays = i & 65535; SetAIRating(); } final function UpdateScores() { Data = (((Max(Score,-1000) & 32767) << 16) | (NumPlays & 65535)) ^ InverseXOr; SaveConfig(); } function SetFXTrack( Object O ) { local ObjectReferencer R; local int i; if( SoundCue(O)!=None ) { HitSoundsA.AddItem(SoundCue(O)); HitSoundsB.AddItem(SoundCue(O)); } else if( ObjectReferencer(O)!=None ) { R = ObjectReferencer(O); if( R.ReferencedObjects.Length<2 ) return; MissSound[0] = SoundCue(R.ReferencedObjects[0]); MissSound[1] = SoundCue(R.ReferencedObjects[1]); for( i=2; i0.f ) // Top. { DY = (1.f - LevelBoarderSize - (BallHeight*0.5) - P.Y) / V.Y; if( DY3 ) AITrajOffset = FMin((AITactic-3)*0.35,0.97)*(0.5-FRand())*(PadHeight+BallWidth); // Randomly chose to throw ball in the corners to give angular momentum. BallHeading = BH_ToEnemy; BallVel.X *= -1.05; BallVel.Y = (BallPos.Y-PlayerPad.Y) / PadHeight * Abs(BallVel.X) * 4.5; CalcEndPosition(); PlayHitSound(true); } } } break; case BH_ToEnemy: if( BallPos.X>1.f ) PlayerScored(true); else if( (BallPos.X+V.X)>0.95 ) { ExtA.X = PadWidth*0.5; ExtA.Y = PadHeight*0.5; ExtB.X = BallWidth*0.5; ExtB.Y = BallHeight*0.5; if( Box8DirTrace(BallPos,V,EnemyPad-vect(0.5,0,0)*PadWidth,ExtB,ExtA,HN,DY) ) { BallPos+=(V*DY); V = vect(0,0,0); if( HN.X>-0.25 ) // Hit edge of the paddle { PlayerScored(true); BallVel = MirrorVectorByNormal(BallVel,HN); } else { BallHeading = BH_ToPlayer; BallVel.X = -BallVel.X; BallVel.Y = (BallPos.Y-EnemyPad.Y) / PadHeight * Abs(BallVel.X) * 4.5; PlayHitSound(false); } } } break; } // Check edges // Top. if( V.Y<0.f ) { DY = LevelBoarderSize + (BallHeight*0.5) - BallPos.Y; if( DY>V.Y ) { DY = DY / V.Y; // Calc intersection time. BallPos+=(V*DY); V = vect(0,0,0); BallVel.Y = -BallVel.Y; BallPos.Y = FMax(BallPos.Y,LevelBoarderSize+(BallHeight*0.5)); CalcEndPosition(); } } // Bottom if( V.Y>0.f ) { DY = 1.f - LevelBoarderSize - (BallHeight*0.5) - BallPos.Y; if( DY0.f ) // Directly follow ball { bTraj = false; if( AITactic<1.f ) { if( BallHeading==BH_ToEnemy ) bRand = BallPos.X>AITactic; } else { bRand = false; if( AITactic>2.f && BallHeading==BH_ToEnemy ) bTraj = (AITactic>=4.f || BallPos.X>(2.f - AITactic*0.5)); } if( !bRand ) { if( bTraj ) { if( BallPos.X>0.5 ) HN.Y = BallTrajectory.Y+AITrajOffset-EnemyPad.Y; else HN.Y = BallTrajectory.Y-EnemyPad.Y; HN.X = FMin(2.f + ((AITactic-1.f)*3.f),13.f); // Calc paddle changespeed rate DY = FMin(0.15 + (AITactic*0.02f),2.f); // Calc paddle max speed } else { HN.Y = BallPos.Y-EnemyPad.Y; HN.X = FMin(3.f + (AITactic*6.f),15.f); // Calc paddle changespeed rate DY = FMin(0.25 + (AITactic*0.025f),2.f); // Calc paddle max speed } EnemyPadVel *= (1.f-Delta*HN.X); // Deaccel all the time. EnemyPadVel = FClamp(EnemyPadVel+(HN.Y*Delta*HN.X*6.f),-DY,DY); } } // Update AI if( bRand ) // Random motion. { if( AITacticTimer(1.f-PadMoveLimit) ) { EnemyPad.Y = 1.f-PadMoveLimit; EnemyPadVel = FMin(EnemyPadVel,0.f); } } BallPos+=V; } final function SetAIRating() { AITactic = float(Score)*0.1+0.5; } defaultproperties { PlayerPad=(X=0.005,Y=0.5) EnemyPad=(X=0.995,Y=0.5) ScreenHeight=800 BallPos=(X=0.75,Y=0.5) BallTrajectory=(X=1,Y=0.5) BallHeading=BH_None }