Blame |
Last modification |
View Log
| RSS feed
unit app_entity_ball;
interface
uses sux_constant, sux_object, app_constant, app_entity_main,
app_entity_trail, app_entity_paddle,
app_particle_ballsparks, app_particle_flowers;
type
TBall=record
rot:SXFloat;
speed:SXFloat;
speeduptimer:SXFloat;
angularspeed:SXFloat;
lastpos:SXVertex2D;
lasthitplayer:SXInt;
sparktimer: SXFloat;
end;
SAEBall=class(SAEntity)
ball:TBall;
trail:SAETrail;
sparks: SAPBallSparks;
flowers: SAPFlowers;
procedure setRotation(rot:SXFloat);
procedure setSpeed(speed:SXFloat);
procedure setAngularVelocity(angularspeed:SXFloat);
function getHitZoneAngleDeviation:SXFloat;
function getTopSpinIntensity:SXFloat;
function getBallStartSpeed:SXFloat;
function getBallSpeedUpTimer:SXFloat;
function getBallSpeedUpIncrease:SXFloat;
function getBallSteepestAngleReflectFromBoard:SXFloat;
function getBallSteepestAngleHitPaddle:SXFloat;
function getBallMaximumAngularSpeed:SXFloat;
function getBallAngularSpeedDecrease:SXFloat;
procedure playerGainsScore(player:SXInt);
procedure checkPlayerGainsScore;
procedure generateTrail;
procedure updateTrail;
procedure generateImpact(pos:SXVertex2D;direction:SXFloat;mode:SXState);
procedure reflectFromBoard;
procedure spawnCrystal;
procedure generateFlowers;
procedure updateFlowers;
procedure generateBallSparks;
procedure updateBallSparks;
procedure deleteBallSparks;
procedure collectPowerUps;
procedure reflectFromCrystals;
procedure reflectAlongNormal(rot:SXFloat);
procedure ballHitsPaddle(paddle:SAEPaddle);
procedure reflectFromPaddle(paddle:SAEPaddle);
procedure reflectFromEntities;
procedure clampToAngle(const steepestangle: SXFloat);
procedure speedUp;
procedure moveForward;
procedure processAngularVelocity;
procedure onTimer; override;
procedure initialize; override;
constructor Create(parent:TObject);
destructor Destroy; override;
end;
implementation
uses main, app_entity_impact, Math, app_entity_crystal, app_entity_powerup;
// --- SAEBall
procedure SAEBall.setRotation(rot:SXFloat);
begin
ball.rot:=rot;
end;
procedure SAEBall.setSpeed(speed:SXFloat);
begin
ball.speed:=speed;
end;
procedure SAEBall.setAngularVelocity(angularspeed:SXFloat);
begin
sx.math.clampToRange(angularspeed,-getBallMaximumAngularSpeed,getBallMaximumAngularSpeed);
ball.angularspeed:=angularspeed;
end;
function SAEBall.getHitZoneAngleDeviation:SXFloat;
begin
result:=app.ini.settings.gameplay.hitzoneangledeviation;
end;
function SAEBall.getTopSpinIntensity:SXFloat;
begin
result:=app.ini.settings.gameplay.topspinintensity;
end;
function SAEBall.getBallStartSpeed:SXFloat;
begin
result:=app.ini.settings.gameplay.ballstartspeed;
end;
function SAEBall.getBallSpeedUpTimer:SXFloat;
begin
result:=app.ini.settings.gameplay.ballspeeduptimer;
end;
function SAEBall.getBallSpeedUpIncrease:SXFloat;
begin
result:=app.ini.settings.gameplay.ballspeedupincrease;
end;
function SAEBall.getBallSteepestAngleReflectFromBoard:SXFloat;
begin
result:=app.ini.settings.gameplay.ballsteepestanglereflectfromboard;
end;
function SAEBall.getBallSteepestAngleHitPaddle:SXFloat;
begin
result:=app.ini.settings.gameplay.ballsteepestanglehitpaddle;
end;
function SAEBall.getBallMaximumAngularSpeed:SXFloat;
begin
result:=app.ini.settings.gameplay.ballmaximumangularspeed;
end;
function SAEBall.getBallAngularSpeedDecrease:SXFloat;
begin
result:=app.ini.settings.gameplay.ballangularspeeddecrease;
end;
procedure SAEBall.playerGainsScore(player:SXInt);
var typeofscore:SXState;
begin
typeofscore:=SX_SCORE_TYPE_NORMAL;
if (ball.lasthitplayer<>player) then typeofscore:=SX_SCORE_TYPE_OWNGOAL;
app.game.playerGainsScore(player,typeofscore);
delete;
end;
procedure SAEBall.checkPlayerGainsScore;
begin
if (settings.pos.x<-270) then
begin
playerGainsScore(2);
exit;
end;
if (settings.pos.x>270) then
begin
playerGainsScore(1);
exit;
end;
end;
procedure SAEBall.generateTrail;
begin
if (trail=nil) then trail:=app.game.createEntity(SX_ENTITY_TRAIL) as SAETrail;
end;
procedure SAEBall.updateTrail;
begin
if (trail=nil) then generateTrail;
// Update the ball's trail
trail.addElement(settings.pos);
end;
procedure SAEBall.generateImpact(pos:SXVertex2D;direction:SXFloat;mode:SXState);
var impact:SAEImpact;
begin
if (app.game.settings.effects.concave > 0) and (mode = SX_EIMPACT_MODE_PADDLE) then
begin
direction := 180 - direction;
if (direction = 180) then pos.x := pos.x + 25 else pos.x := pos.x - 25;
end;
impact:=app.game.createEntity(SX_ENTITY_IMPACT) as SAEImpact;
impact.setPosition(pos);
impact.setRotation(direction);
impact.setMode(mode);
impact.setInverseMovement(app.game.settings.effects.concave > 0);
case mode of
SX_EIMPACT_MODE_WALL : app.sound.playSample2D('sounds\wallimpact');
SX_EIMPACT_MODE_PADDLE : app.sound.playSample2D('sounds\paddleimpact');
end;
if (mode = SX_EIMPACT_MODE_WALL) then
if (app.game.settings.effects.createcrystals > 0) then
if (not isOutOfBoard) then
begin
dec(app.game.settings.effects.createcrystals);
spawnCrystal;
end;
updateTrail;
end;
procedure SAEBall.reflectFromBoard;
var impactdirection:SXFloat;
begin
impactdirection := -1;
if (settings.pos.y < -185) then
begin
reflectAlongNormal(0);
impactdirection := 0;
clampToAngle(getBallSteepestAngleReflectFromBoard);
end;
if (settings.pos.y > 185) then
begin
reflectAlongNormal(180);
impactdirection := 180;
clampToAngle(getBallSteepestAngleReflectFromBoard);
end;
sx.math.clampToRange(settings.pos.y, -185, 185);
// Generate an impact on the board
if (impactdirection >- 1) then generateImpact(settings.pos,impactdirection,SX_EIMPACT_MODE_WALL);
end;
procedure SAEBall.spawnCrystal;
var crystal: SAECrystal;
begin
crystal := SAECrystal(app.game.createEntity(SX_ENTITY_CRYSTAL));
crystal.setPosition(getPos);
crystal.setRotation(sx.math.random.getRandomFloat(0, 360));
crystal.setSpawnParameters(4, 15, 5.0);
app.sound.playSample2D('sounds\crystal1');
end;
procedure SAEBall.generateFlowers;
begin
if (flowers = nil) then
begin
flowers := SAPFlowers(app.game.createParticleSystem(SX_PARTICLESYSTEM_FLOWERS));
flowers.addFlowers(getPos);
end;
end;
procedure SAEBall.updateFlowers;
begin
generateFlowers;
sx.timer.incTimer(ball.sparktimer, 1/SX_EBALL_ITERATIONS);
while (ball.sparktimer > 0.02) do
begin
ball.sparktimer := ball.sparktimer - 0.02;
flowers.addFlowers(getPos);
end;
end;
procedure SAEBall.generateBallSparks;
begin
if (sparks = nil) then
begin
sparks := SAPBallSparks(app.game.createParticleSystem(SX_PARTICLESYSTEM_BALLSPARKS));
end;
end;
procedure SAEBall.updateBallSparks;
begin
if (app.game.settings.effects.flowers <= 0) then
begin
generateBallSparks;
flowers := nil;
sx.timer.incTimer(ball.sparktimer, 1/SX_EBALL_ITERATIONS);
while (ball.sparktimer > 0.02) do
begin
ball.sparktimer := ball.sparktimer - 0.02;
sparks.addSparks(getPos, ball.angularspeed);
end;
end else
begin
updateFlowers;
end;
end;
procedure SAEBall.deleteBallSparks;
begin
if (sparks <> nil) then app.game.deleteParticleSystem(sparks);
sparks := nil;
end;
procedure SAEBall.collectPowerUps;
var e: SXInt;
other: SAEntity;
begin
for e := app.game.entities.getMax downto 0 do
begin
other := SAEntity(app.game.entities.getObject(e));
if (self <> other) and (other is SAEPowerUp) then
if (doBoundingCirclesIntersect(other)) then
begin
SAEPowerUp(other).collect;
app.game.deleteEntity(other);
exit;
end;
end;
end;
procedure SAEBall.reflectFromCrystals;
var e: SXInt;
other: SAEntity;
normal: SXVertex2D;
rot: SXFloat;
distance: SXVertex2D;
begin
for e := app.game.entities.getMax downto 0 do
begin
other := SAEntity(app.game.entities.getObject(e));
if (self <> other) and (other.settings.solid) then
if (other is SAECrystal) and (other.settings.size > 5) then
begin
distance.x := settings.pos.x - other.settings.pos.x;
distance.y := settings.pos.y - other.settings.pos.y;
if ((sqr(distance.x) + sqr(distance.y)) < sqr(other.settings.size)) then
begin
// Reflect from this entity.
normal := sx.math.getPointVector(other.getPos, ball.lastpos);
normal := sx.math.normalizeVector(normal);
rot := radtodeg(arccos(normal.x));
if normal.y > 0 then rot := 180 - rot + 90 else rot := rot - 90;
rot := 180+rot;
reflectAlongNormal(rot);
app.sound.playSample2D('sounds\crystal2');
SAECrystal(other).generateShards;
app.game.deleteEntity(other);
exit;
end;
end;
end;
end;
procedure SAEBall.reflectAlongNormal(rot:SXFloat);
var difference:SXFloat;
begin
ball.rot:=ball.rot+180;
sx.math.stayInDegrees0to360(ball.rot);
{angle:=radtodeg(arccos(vector.x));
if vector.z>0 then angle:=180-angle+90 else angle:=angle-90;}
difference:=rot-ball.rot;
ball.rot:=rot+difference;
sx.math.stayInDegrees0to360(ball.rot);
if (sx.math.turnDegrees(ball.rot,rot)>=90) then
begin
ball.rot:=360-ball.rot;
sx.math.stayInDegrees0to360(ball.rot);
end;
{normal:=sx.math.normalizeVector(normal);
ballvector:=sx.math.normalizeVector(ball.linear.velocity);
ball.linear.velocity:=sx.math.subtractVertices(ballvector,sx.math.multiplyVertex(normal,2*sx.math.getDotProduct(ballvector,normal)));}
end;
procedure SAEBall.ballHitsPaddle(paddle:SAEPaddle);
var newx:SXFloat;
direction,ydifference,angularspeed:SXFloat;
paddledirection:SXFloat;
invertplayer2paddle: SXInt;
begin
newx:=paddle.settings.pos.x;
ydifference:=(settings.pos.y-paddle.settings.pos.y)*getHitZoneAngleDeviation;
// Invert y-difference if the paddles are concave.
if (app.game.settings.effects.concave > 0) then ydifference := -ydifference;
if (paddle.paddle.belongtoplayer = 1) then
begin
paddledirection := 0;
direction:= 90 - ydifference;
newx := newx + 6;
invertplayer2paddle := 1;
end else
begin
paddledirection := 180;
direction:= 270 + ydifference;
newx := newx - 6;
invertplayer2paddle := -1;
end;
ball.lasthitplayer:=paddle.paddle.belongtoplayer;
settings.pos.x := newx;
reflectAlongNormal(direction);
generateImpact(paddle.settings.pos,paddledirection, SX_EIMPACT_MODE_PADDLE);
angularspeed := paddle.getMovementSpeed * getTopSpinIntensity * invertplayer2paddle;
setAngularVelocity(ball.angularspeed + angularspeed);
clampToAngle(getBallSteepestAngleHitPaddle);
end;
procedure SAEBall.reflectFromPaddle(paddle:SAEPaddle);
var paddlex:SXFloat;
begin
if (abs(settings.pos.y - paddle.settings.pos.y) < 35) then
begin
paddlex := paddle.settings.pos.x;
if (paddlex < 0) then
begin
paddlex := paddlex + 5;
if (settings.pos.x <= paddlex) and (ball.lastpos.x > paddlex) then ballHitsPaddle(paddle);
end else
begin
paddlex := paddlex - 5;
if (settings.pos.x >= paddlex) and (ball.lastpos.x < paddlex) then ballHitsPaddle(paddle);
end;
end;
end;
procedure SAEBall.reflectFromEntities;
var e:SXInt;
entity:SAEntity;
begin
for e:=0 to app.game.entities.getMax do
begin
entity:=SAEntity(app.game.entities.getObject(e));
if (entity is SAEPaddle) then reflectFromPaddle(SAEPaddle(entity));
end;
end;
procedure SAEBall.clampToAngle(const steepestangle: SXFloat);
begin
with ball do
begin
sx.math.stayInDegrees0to360(rot);
if (steepestangle <= 0) then exit;
if (rot < steepestangle) then rot := steepestangle;
if (rot > 360-steepestangle) then rot := 360 - steepestangle;
if (rot > 180-steepestangle) and (rot <= 180) then rot := 180 - steepestangle;
if (rot < 180+steepestangle) and (rot >= 180) then rot := 180 + steepestangle;
end;
end;
procedure SAEBall.speedUp;
begin
with ball do
begin
// Speed of linear velocity increases with time
sx.timer.incTimer(speeduptimer);
if (speeduptimer>=getBallSpeedUpTimer) then
begin
speeduptimer:=speeduptimer-getBallSpeedUpTimer;
speed:=speed+getBallSpeedUpIncrease;
//app.sound.playSample2D('sounds\speedup');
end;
// Decrease angular velocity
if (angularspeed>0) then
begin
sx.timer.decTimer(angularspeed,getBallAngularSpeedDecrease);
if (angularspeed<0) then angularspeed:=0;
end;
if (angularspeed<0) then
begin
sx.timer.incTimer(angularspeed,getBallAngularSpeedDecrease);
if (angularspeed>0) then angularspeed:=0;
end;
end;
end;
procedure SAEBall.moveForward;
var vector:SXVertex2D;
intensity:SXFloat;
begin
with ball do
begin
vector.x:=sx.math._sin(rot);
vector.y:=sx.math._cos(rot);
vector:=sx.math.setVectorLength(vector,sx.timer.getTimer(speed/SX_EBALL_ITERATIONS));
settings.pos:=sx.math.addVertices(settings.pos,vector);
intensity:=(speed-getBallStartSpeed)*0.002;
sx.math.clampToRange(intensity,0,1);
app.scene.blur.setBlurIntensity(intensity);
end;
end;
procedure SAEBall.processAngularVelocity;
begin
// Influence the velocity vector
with ball do
begin
sx.timer.incTimer(rot, angularspeed / SX_EBALL_ITERATIONS);
end;
end;
procedure SAEBall.onTimer;
var i:SXInt;
begin
speedUp;
for i:=0 to SX_EBALL_ITERATIONS-1 do
begin
ball.lastpos:=getPos;
processAngularVelocity;
moveForward;
collectPowerUps;
reflectFromCrystals;
reflectFromBoard;
reflectFromEntities;
updateBallSparks;
end;
updateTrail;
checkPlayerGainsScore;
app.scene.board.setMonkeyRotationSpeed(ball.angularspeed);
end;
procedure SAEBall.initialize;
begin
end;
constructor SAEBall.Create(parent:TObject);
begin
inherited Create(parent);
setupAppearance(app.scene.board.settings.ball,5);
setRotation(90);
setSpeed(300);
setAngularVelocity(0);
ball.speeduptimer:=0;
ball.lasthitplayer:=SX_NONE;
ball.sparktimer := 0;
end;
destructor SAEBall.Destroy;
begin
deleteBallSparks;
inherited;
end;
end.