KF2-Server-Extension/ServerExt/Classes/MX_PongGame.uc
GenZmeY 1d7c441b4c update localization
- more text supports localization;
- move hardcode to english localization file;
- update the Russian localization file.
2021-01-18 00:55:45 +03:00

448 lines
10 KiB
Ucode

Class MX_PongGame extends MX_MiniGameBase;
var localized string PressToStartText;
var localized string ScoreText;
var localized string PlaysText;
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();
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; i<R.ReferencedObjects.Length; ++i)
{
if ((i & 1)==0)
HitSoundsA.AddItem(SoundCue(R.ReferencedObjects[i]));
else HitSoundsB.AddItem(SoundCue(R.ReferencedObjects[i]));
}
}
}
function StartGame()
{
Super.StartGame();
SetTimer(2,false,'RespawnBall',Self);
}
function Render(float XPos, float YPos, float XSize, float YSize)
{
local float H,W;
ScreenHeight = YSize;
// Score
H = WorldInfo.RealTimeSeconds * 0.6;
Canvas.Font = Font(DynamicLoadObject("UI_Canvas_Fonts.Font_Main",class'Font'));
W = FMin(YSize/200.f,3.f);
if (!bGameStarted)
{
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);
}
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);
Canvas.SetPos(XPos+XSize*0.2,YPos+YSize*0.68);
Canvas.DrawText(PlaysText@string(NumPlays),,W,W);
}
// 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);
// 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);
// 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);
// 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);
// 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);*/
}
function UpdateMouse(float X, float Y)
{
Y /= (ScreenHeight*8/Sensitivity);
PlayerPad.Y = FClamp(PlayerPad.Y-Y,PadMoveLimit,1.f-PadMoveLimit);
}
function SetMouse(float X, float Y)
{
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);
}
final function PlayHitSound(bool bPlayer)
{
if (bPlayer)
{
if (HitSoundsA.Length==0)
return;
if (HitSoundsA[HitSoundIndex[0]]!=None)
PlaySound(HitSoundsA[HitSoundIndex[0]],true);
if (++HitSoundIndex[0]==HitSoundsA.Length)
HitSoundIndex[0] = 0;
}
else
{
if (HitSoundsB.Length==0)
return;
if (HitSoundsB[HitSoundIndex[1]]!=None)
PlaySound(HitSoundsB[HitSoundIndex[1]],true);
if (++HitSoundIndex[1]==HitSoundsB.Length)
HitSoundIndex[1] = 0;
}
}
final function PlayerScored(bool bPlayer)
{
++NumPlays;
if (bPlayer)
{
++Score;
if (MissSound[1]!=None)
PlaySound(MissSound[1],true);
}
else
{
--Score;
if (MissSound[0]!=None)
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;
if (BallVel.X<=0.f) // Never.
return;
V = BallVel;
P = BallPos;
// Get hit time.
T = (EnemyPad.X - PadWidth - (BallWidth*0.5) - P.X) / V.X;
// Now take bounces into account.
while (true)
{
if (V.Y<0.f) // Bottom.
{
DY = (LevelBoarderSize + (BallHeight*0.5) - P.Y) / V.Y; // Calc intersection time.
if (DY<T)
{
P+=(V*DY);
V.Y = -V.Y;
T-=DY;
}
else break; // No more wallhits.
}
else if (V.Y>0.f) // Top.
{
DY = (1.f - LevelBoarderSize - (BallHeight*0.5) - P.Y) / V.Y;
if (DY<T)
{
P+=(V*DY);
V.Y = -V.Y;
T-=DY;
}
else break; // No more wallhits.
}
else break; // No wallhits!
}
BallTrajectory = P+(V*T);
}
function Tick(float Delta)
{
local vector V,HN,ExtA,ExtB;
local float DY;
local bool bTraj,bRand;
// Check collision unless out of bounds already.
V = BallVel*Delta;
if (BallHeading!=BH_None)
{
// Check paddles
switch (BallHeading)
{
case BH_ToPlayer:
if (BallPos.X<0.f)
PlayerScored(false);
else if ((BallPos.X+V.X)<0.05)
{
ExtA.X = PadWidth*0.5;
ExtA.Y = PadHeight*0.5;
ExtB.X = BallWidth*0.5;
ExtB.Y = BallHeight*0.5;
if (Box8DirTrace(BallPos,V,PlayerPad+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(false);
BallVel = MirrorVectorByNormal(BallVel,HN);
}
else
{
AITrajOffset = 0.f;
if (AITactic>3)
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 (DY<V.Y)
{
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;
if (AITactic>0.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<WorldInfo.TimeSeconds)
{
bAIRandom = (Rand(2)==0);
AITacticTimer = WorldInfo.TimeSeconds+FRand();
}
DY = FMax(FMin(Delta,0.65f-Abs(EnemyPadVel)),0.f);
if (bAIRandom)
EnemyPadVel += DY;
else EnemyPadVel -= DY;
}
// Apply by velocity and limit movement.
EnemyPad.Y = EnemyPad.Y+(EnemyPadVel*Delta);
if (EnemyPad.Y<PadMoveLimit)
{
EnemyPad.Y = PadMoveLimit;
EnemyPadVel = FMax(EnemyPadVel,0.f);
}
else if (EnemyPad.Y>(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
}