KF2-Server-Extension/ServerExt/Classes/MX_PongGame.uc

449 lines
10 KiB
Ucode
Raw Permalink Normal View History

2017-10-20 02:00:49 +00:00
Class MX_PongGame extends MX_MiniGameBase;
var localized string PressToStartText;
var localized string ScoreText;
var localized string PlaysText;
2017-10-20 02:00:49 +00:00
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<SoundCue> 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();
2020-11-28 20:12:58 +00:00
if (Data!=0)
2017-10-20 02:00:49 +00:00
i = Data ^ InverseXOr;
Score = (i >> 16) & 32767;
2020-11-28 20:12:58 +00:00
if (Score>31767)
2017-10-20 02:00:49 +00:00
Score = Score-32768;
NumPlays = i & 65535;
SetAIRating();
}
final function UpdateScores()
{
Data = (((Max(Score,-1000) & 32767) << 16) | (NumPlays & 65535)) ^ InverseXOr;
SaveConfig();
}
2020-11-28 20:04:55 +00:00
function SetFXTrack(Object O)
2017-10-20 02:00:49 +00:00
{
local ObjectReferencer R;
local int i;
2020-11-28 20:12:58 +00:00
if (SoundCue(O)!=None)
2017-10-20 02:00:49 +00:00
{
HitSoundsA.AddItem(SoundCue(O));
HitSoundsB.AddItem(SoundCue(O));
}
2020-11-28 20:12:58 +00:00
else if (ObjectReferencer(O)!=None)
2017-10-20 02:00:49 +00:00
{
R = ObjectReferencer(O);
2020-11-28 20:12:58 +00:00
if (R.ReferencedObjects.Length<2)
2017-10-20 02:00:49 +00:00
return;
MissSound[0] = SoundCue(R.ReferencedObjects[0]);
MissSound[1] = SoundCue(R.ReferencedObjects[1]);
2020-11-28 20:12:58 +00:00
for (i=2; i<R.ReferencedObjects.Length; ++i)
2017-10-20 02:00:49 +00:00
{
2020-11-28 20:12:58 +00:00
if ((i & 1)==0)
2017-10-20 02:00:49 +00:00
HitSoundsA.AddItem(SoundCue(R.ReferencedObjects[i]));
else HitSoundsB.AddItem(SoundCue(R.ReferencedObjects[i]));
}
}
}
function StartGame()
{
Super.StartGame();
SetTimer(2,false,'RespawnBall',Self);
}
2020-11-28 20:04:55 +00:00
function Render(float XPos, float YPos, float XSize, float YSize)
2017-10-20 02:00:49 +00:00
{
local float H,W;
2023-05-14 02:49:12 +00:00
2017-10-20 02:00:49 +00:00
ScreenHeight = YSize;
// Score
H = WorldInfo.RealTimeSeconds * 0.6;
Canvas.Font = Font(DynamicLoadObject("UI_Canvas_Fonts.Font_Main",class'Font'));
2017-10-20 02:00:49 +00:00
W = FMin(YSize/200.f,3.f);
2020-11-28 20:12:58 +00:00
if (!bGameStarted)
2017-10-20 02:00:49 +00:00
{
Canvas.SetDrawColor(128,64,64,Abs(Sin(H))*96.f+128);
Canvas.SetPos(XPos+XSize*0.4,YPos+YSize*0.2);
Canvas.DrawText(PressToStartText,,W,W);
2017-10-20 02:00:49 +00:00
}
else
{
Canvas.SetDrawColor(255,255,128,Abs(Sin(H))*96.f);
Canvas.SetPos(XPos+XSize*0.2,YPos+YSize*0.22);
Canvas.DrawText(ScoreText@string(Score),,W,W);
2017-10-20 02:00:49 +00:00
Canvas.SetPos(XPos+XSize*0.2,YPos+YSize*0.68);
Canvas.DrawText(PlaysText@string(NumPlays),,W,W);
2017-10-20 02:00:49 +00:00
}
2023-05-14 02:49:12 +00:00
2017-10-20 02:00:49 +00:00
// Borders
Canvas.SetDrawColor(Abs(Sin(H))*255.f,Abs(Sin(H+1.25))*255.f,Abs(Sin(H+2.35))*255.f,255);
Canvas.SetPos(XPos,YPos);
Canvas.DrawTile(Canvas.DefaultTexture,XSize,YSize*LevelBoarderSize,0,0,1,1);
Canvas.SetPos(XPos,YPos+YSize*(1.f-LevelBoarderSize));
Canvas.DrawTile(Canvas.DefaultTexture,XSize,YSize*LevelBoarderSize,0,0,1,1);
2023-05-14 02:49:12 +00:00
2017-10-20 02:00:49 +00:00
// Player
H = PadHeight*YSize;
W = PadWidth*XSize;
Canvas.SetDrawColor(128,255,128,255);
Canvas.SetPos(XPos+PlayerPad.X*XSize,YPos+PlayerPad.Y*YSize-H*0.5);
Canvas.DrawTile(Canvas.DefaultTexture,W,H,0,0,1,1);
2023-05-14 02:49:12 +00:00
2017-10-20 02:00:49 +00:00
// Enemy
Canvas.SetDrawColor(255,68,68,255);
Canvas.SetPos(XPos+EnemyPad.X*XSize-W,YPos+EnemyPad.Y*YSize-H*0.5);
Canvas.DrawTile(Canvas.DefaultTexture,W,H,0,0,1,1);
2023-05-14 02:49:12 +00:00
2017-10-20 02:00:49 +00:00
// Pong ball
Canvas.SetDrawColor(255,255,86,255);
BallWidth = BallHeight*(YSize/XSize);
H = BallHeight*YSize;
W = H*0.5;
Canvas.SetPos(XPos+BallPos.X*XSize-W,YPos+BallPos.Y*YSize-W);
Canvas.DrawTile(Canvas.DefaultTexture,H,H,0,0,1,1);
2023-05-14 02:49:12 +00:00
2017-10-20 02:00:49 +00:00
// Trajectory preview ball
/*Canvas.SetDrawColor(255,255,86,64);
Canvas.SetPos(XPos+BallTrajectory.X*XSize-W,YPos+BallTrajectory.Y*YSize-W);
Canvas.DrawTile(Canvas.DefaultTexture,H,H,0,0,1,1);*/
}
2020-11-28 20:04:55 +00:00
function UpdateMouse(float X, float Y)
2017-10-20 02:00:49 +00:00
{
Y /= (ScreenHeight*8/Sensitivity);
PlayerPad.Y = FClamp(PlayerPad.Y-Y,PadMoveLimit,1.f-PadMoveLimit);
}
2020-11-28 20:04:55 +00:00
function SetMouse(float X, float Y)
2017-10-20 02:00:49 +00:00
{
PlayerPad.Y = FClamp(Y/ScreenHeight,PadMoveLimit,1.f-PadMoveLimit);
}
final function RespawnBall()
{
BallVel.X = -1;
BallVel.Y = 0.5-FRand();
BallVel = Normal2D(BallVel)*0.35;
BallPos = Default.BallPos;
BallHeading = BH_ToPlayer;
EnemyPadVel = 0.f;
}
final function NewRound()
{
BallVel = vect(0,0,0);
BallPos = Default.BallPos;
BallHeading = BH_None;
SetTimer(1,false,'RespawnBall',Self);
}
2020-11-28 20:04:55 +00:00
final function PlayHitSound(bool bPlayer)
2017-10-20 02:00:49 +00:00
{
2020-11-28 20:12:58 +00:00
if (bPlayer)
2017-10-20 02:00:49 +00:00
{
2020-11-28 20:12:58 +00:00
if (HitSoundsA.Length==0)
2017-10-20 02:00:49 +00:00
return;
2020-11-28 20:12:58 +00:00
if (HitSoundsA[HitSoundIndex[0]]!=None)
2017-10-20 02:00:49 +00:00
PlaySound(HitSoundsA[HitSoundIndex[0]],true);
2020-11-28 20:12:58 +00:00
if (++HitSoundIndex[0]==HitSoundsA.Length)
2017-10-20 02:00:49 +00:00
HitSoundIndex[0] = 0;
}
else
{
2020-11-28 20:12:58 +00:00
if (HitSoundsB.Length==0)
2017-10-20 02:00:49 +00:00
return;
2020-11-28 20:12:58 +00:00
if (HitSoundsB[HitSoundIndex[1]]!=None)
2017-10-20 02:00:49 +00:00
PlaySound(HitSoundsB[HitSoundIndex[1]],true);
2020-11-28 20:12:58 +00:00
if (++HitSoundIndex[1]==HitSoundsB.Length)
2017-10-20 02:00:49 +00:00
HitSoundIndex[1] = 0;
}
}
2020-11-28 21:54:57 +00:00
2020-11-28 20:04:55 +00:00
final function PlayerScored(bool bPlayer)
2017-10-20 02:00:49 +00:00
{
++NumPlays;
2020-11-28 20:12:58 +00:00
if (bPlayer)
2017-10-20 02:00:49 +00:00
{
++Score;
2020-11-28 20:12:58 +00:00
if (MissSound[1]!=None)
2017-10-20 02:00:49 +00:00
PlaySound(MissSound[1],true);
}
else
{
--Score;
2020-11-28 20:12:58 +00:00
if (MissSound[0]!=None)
2017-10-20 02:00:49 +00:00
PlaySound(MissSound[0],true);
}
HitSoundIndex[0] = 0;
HitSoundIndex[1] = 0;
UpdateScores();
SetAIRating();
BallHeading = BH_None;
SetTimer(2.5,false,'NewRound',Self);
}
// Calculate where the ball is going to hit on in enemy side.
final function CalcEndPosition()
{
local float T,DY;
local vector P,V;
2023-05-14 02:49:12 +00:00
2020-11-28 20:12:58 +00:00
if (BallVel.X<=0.f) // Never.
2017-10-20 02:00:49 +00:00
return;
2023-05-14 02:49:12 +00:00
2017-10-20 02:00:49 +00:00
V = BallVel;
P = BallPos;
// Get hit time.
T = (EnemyPad.X - PadWidth - (BallWidth*0.5) - P.X) / V.X;
// Now take bounces into account.
2020-11-28 20:12:58 +00:00
while (true)
2017-10-20 02:00:49 +00:00
{
2020-11-28 20:12:58 +00:00
if (V.Y<0.f) // Bottom.
2017-10-20 02:00:49 +00:00
{
DY = (LevelBoarderSize + (BallHeight*0.5) - P.Y) / V.Y; // Calc intersection time.
2020-11-28 20:12:58 +00:00
if (DY<T)
2017-10-20 02:00:49 +00:00
{
P+=(V*DY);
V.Y = -V.Y;
T-=DY;
}
else break; // No more wallhits.
}
2020-11-28 20:12:58 +00:00
else if (V.Y>0.f) // Top.
2017-10-20 02:00:49 +00:00
{
DY = (1.f - LevelBoarderSize - (BallHeight*0.5) - P.Y) / V.Y;
2020-11-28 20:12:58 +00:00
if (DY<T)
2017-10-20 02:00:49 +00:00
{
P+=(V*DY);
V.Y = -V.Y;
T-=DY;
}
else break; // No more wallhits.
}
else break; // No wallhits!
}
BallTrajectory = P+(V*T);
}
2020-11-28 20:04:55 +00:00
function Tick(float Delta)
2017-10-20 02:00:49 +00:00
{
local vector V,HN,ExtA,ExtB;
local float DY;
local bool bTraj,bRand;
// Check collision unless out of bounds already.
V = BallVel*Delta;
2020-11-28 20:12:58 +00:00
if (BallHeading!=BH_None)
2017-10-20 02:00:49 +00:00
{
// Check paddles
2020-11-28 20:12:58 +00:00
switch (BallHeading)
2017-10-20 02:00:49 +00:00
{
case BH_ToPlayer:
2020-11-28 20:12:58 +00:00
if (BallPos.X<0.f)
2017-10-20 02:00:49 +00:00
PlayerScored(false);
2020-11-28 20:12:58 +00:00
else if ((BallPos.X+V.X)<0.05)
2017-10-20 02:00:49 +00:00
{
ExtA.X = PadWidth*0.5;
ExtA.Y = PadHeight*0.5;
ExtB.X = BallWidth*0.5;
ExtB.Y = BallHeight*0.5;
2020-11-28 20:12:58 +00:00
if (Box8DirTrace(BallPos,V,PlayerPad+vect(0.5,0,0)*PadWidth,ExtB,ExtA,HN,DY))
2017-10-20 02:00:49 +00:00
{
BallPos+=(V*DY);
V = vect(0,0,0);
2020-11-28 20:12:58 +00:00
if (HN.X<0.25) // Hit edge of the paddle
2017-10-20 02:00:49 +00:00
{
PlayerScored(false);
BallVel = MirrorVectorByNormal(BallVel,HN);
}
else
{
AITrajOffset = 0.f;
2020-11-28 20:12:58 +00:00
if (AITactic>3)
2017-10-20 02:00:49 +00:00
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:
2020-11-28 20:12:58 +00:00
if (BallPos.X>1.f)
2017-10-20 02:00:49 +00:00
PlayerScored(true);
2020-11-28 20:12:58 +00:00
else if ((BallPos.X+V.X)>0.95)
2017-10-20 02:00:49 +00:00
{
ExtA.X = PadWidth*0.5;
ExtA.Y = PadHeight*0.5;
ExtB.X = BallWidth*0.5;
ExtB.Y = BallHeight*0.5;
2020-11-28 20:12:58 +00:00
if (Box8DirTrace(BallPos,V,EnemyPad-vect(0.5,0,0)*PadWidth,ExtB,ExtA,HN,DY))
2017-10-20 02:00:49 +00:00
{
BallPos+=(V*DY);
V = vect(0,0,0);
2020-11-28 20:12:58 +00:00
if (HN.X>-0.25) // Hit edge of the paddle
2017-10-20 02:00:49 +00:00
{
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.
2020-11-28 20:12:58 +00:00
if (V.Y<0.f)
2017-10-20 02:00:49 +00:00
{
DY = LevelBoarderSize + (BallHeight*0.5) - BallPos.Y;
2020-11-28 20:12:58 +00:00
if (DY>V.Y)
2017-10-20 02:00:49 +00:00
{
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
2020-11-28 20:12:58 +00:00
if (V.Y>0.f)
2017-10-20 02:00:49 +00:00
{
DY = 1.f - LevelBoarderSize - (BallHeight*0.5) - BallPos.Y;
2020-11-28 20:12:58 +00:00
if (DY<V.Y)
2017-10-20 02:00:49 +00:00
{
DY = DY / V.Y;
BallPos+=(V*DY);
V = vect(0,0,0);
BallVel.Y = -BallVel.Y;
BallPos.Y = FMin(BallPos.Y,1.f-LevelBoarderSize-(BallHeight*0.5));
CalcEndPosition();
}
}
bRand = true;
2020-11-28 20:12:58 +00:00
if (AITactic>0.f) // Directly follow ball
2017-10-20 02:00:49 +00:00
{
bTraj = false;
2020-11-28 20:12:58 +00:00
if (AITactic<1.f)
2017-10-20 02:00:49 +00:00
{
2020-11-28 20:12:58 +00:00
if (BallHeading==BH_ToEnemy)
2017-10-20 02:00:49 +00:00
bRand = BallPos.X>AITactic;
}
else
{
bRand = false;
2020-11-28 20:12:58 +00:00
if (AITactic>2.f && BallHeading==BH_ToEnemy)
2017-10-20 02:00:49 +00:00
bTraj = (AITactic>=4.f || BallPos.X>(2.f - AITactic*0.5));
}
2020-11-28 20:12:58 +00:00
if (!bRand)
2017-10-20 02:00:49 +00:00
{
2020-11-28 20:12:58 +00:00
if (bTraj)
2017-10-20 02:00:49 +00:00
{
2020-11-28 20:12:58 +00:00
if (BallPos.X>0.5)
2017-10-20 02:00:49 +00:00
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);
}
}
2023-05-14 02:49:12 +00:00
2017-10-20 02:00:49 +00:00
// Update AI
2020-11-28 20:12:58 +00:00
if (bRand) // Random motion.
2017-10-20 02:00:49 +00:00
{
2020-11-28 20:12:58 +00:00
if (AITacticTimer<WorldInfo.TimeSeconds)
2017-10-20 02:00:49 +00:00
{
bAIRandom = (Rand(2)==0);
AITacticTimer = WorldInfo.TimeSeconds+FRand();
}
DY = FMax(FMin(Delta,0.65f-Abs(EnemyPadVel)),0.f);
2020-11-28 20:12:58 +00:00
if (bAIRandom)
2017-10-20 02:00:49 +00:00
EnemyPadVel += DY;
else EnemyPadVel -= DY;
}
2023-05-14 02:49:12 +00:00
2017-10-20 02:00:49 +00:00
// Apply by velocity and limit movement.
EnemyPad.Y = EnemyPad.Y+(EnemyPadVel*Delta);
2020-11-28 20:12:58 +00:00
if (EnemyPad.Y<PadMoveLimit)
2017-10-20 02:00:49 +00:00
{
EnemyPad.Y = PadMoveLimit;
EnemyPadVel = FMax(EnemyPadVel,0.f);
}
2020-11-28 20:12:58 +00:00
else if (EnemyPad.Y>(1.f-PadMoveLimit))
2017-10-20 02:00:49 +00:00
{
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
2023-05-14 02:49:12 +00:00
}