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 }