Multiball part 1: control and component changes from FT.

The result is 3DPB/FT hybrid, with control closer to 3DPB and components closer to FT.
This commit is contained in:
Muzychenko Andrey 2022-08-25 17:09:17 +03:00
parent 14a8d64b67
commit c1c74878df
15 changed files with 354 additions and 154 deletions

View File

@ -141,3 +141,9 @@ vector2 TBall::get_coordinates()
{ {
return TTableLayer::edge_manager->NormalizeBox(Position); return TTableLayer::edge_manager->NormalizeBox(Position);
} }
void TBall::Disable()
{
ActiveFlag = false;
render::sprite_set_bitmap(RenderSprite, nullptr);
}

View File

@ -14,6 +14,7 @@ public :
bool already_hit(TEdgeSegment* edge); bool already_hit(TEdgeSegment* edge);
int Message(int code, float value) override; int Message(int code, float value) override;
vector2 get_coordinates() override; vector2 get_coordinates() override;
void Disable();
static void throw_ball(TBall* ball, vector3* direction, float angleMult, float speedMult1, static void throw_ball(TBall* ball, vector3* direction, float angleMult, float speedMult1,
float speedMult2); float speedMult2);

View File

@ -23,16 +23,21 @@ int TDrain::Message(int code, float value)
timer::kill(Timer); timer::kill(Timer);
Timer = 0; Timer = 0;
} }
PinballTable->BallInSink = 0; PinballTable->BallInDrainFlag = 0;
} }
return 0; return 0;
} }
void TDrain::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge) void TDrain::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
ball->Message(1024, 0.0); ball->Disable();
PinballTable->BallInSink = 1; --PinballTable->MultiballCount;
Timer = timer::set(TimerTime, this, TimerCallback); if (PinballTable->MultiballCount <= 0)
{
PinballTable->MultiballCount = 0;
PinballTable->BallInDrainFlag = 1;
Timer = timer::set(TimerTime, this, TimerCallback);
}
control::handler(63, this); control::handler(63, this);
} }

View File

@ -49,20 +49,19 @@ TPinballTable::TPinballTable(): TPinballComponent(nullptr, -1, false)
CurScoreStruct = nullptr; CurScoreStruct = nullptr;
ScoreBallcount = nullptr; ScoreBallcount = nullptr;
ScorePlayerNumber1 = nullptr; ScorePlayerNumber1 = nullptr;
BallInSink = 0; BallInDrainFlag = 0;
ActiveFlag = 1; ActiveFlag = 1;
TiltLockFlag = 0; TiltLockFlag = 0;
EndGameTimeoutTimer = 0; EndGameTimeoutTimer = 0;
LightShowTimer = 0; LightShowTimer = 0;
ReplayTimer = 0; ReplayTimer = 0;
TiltTimeoutTimer = 0; TiltTimeoutTimer = 0;
MultiballFlag = 0; MultiballFlag = false;
PlayerCount = 0; PlayerCount = 0;
auto ballObj = new TBall(this); auto ball = AddBall(0.0f, 0.0f);
BallList.push_back(ballObj); ball->Disable();
if (ballObj)
ballObj->ActiveFlag = 0;
new TTableLayer(this); new TTableLayer(this);
LightGroup = new TLightGroup(this, 0); LightGroup = new TLightGroup(this, 0);
@ -287,7 +286,7 @@ void TPinballTable::ChangeBallCount(int count)
void TPinballTable::tilt(float time) void TPinballTable::tilt(float time)
{ {
if (!TiltLockFlag && !BallInSink) if (!TiltLockFlag && !BallInDrainFlag)
{ {
pinball::InfoTextBox->Clear(); pinball::InfoTextBox->Clear();
pinball::MissTextBox->Clear(); pinball::MissTextBox->Clear();
@ -452,6 +451,9 @@ int TPinballTable::Message(int code, float value)
LightShowTimer = timer::set(time, this, LightShow_timeout); LightShowTimer = timer::set(time, this, LightShow_timeout);
} }
// Multi-ball is FT exclusive feature, at least for now.
if (pb::FullTiltMode)
MultiballFlag = true;
midi::play_track(MidiTracks::Track1, true); midi::play_track(MidiTracks::Track1, true);
break; break;
case 1018: case 1018:
@ -573,9 +575,9 @@ int TPinballTable::Message(int code, float value)
ScoreSpecial3Flag = 0; ScoreSpecial3Flag = 0;
UnknownP71 = 0; UnknownP71 = 0;
ExtraBalls = 0; ExtraBalls = 0;
UnknownP75 = 0; MultiballCount = 0;
BallLockedCounter = 0; BallLockedCounter = 0;
MultiballFlag = 0; MultiballFlag = false;
UnknownP78 = 0; UnknownP78 = 0;
ReplayActiveFlag = 0; ReplayActiveFlag = 0;
ReplayTimer = 0; ReplayTimer = 0;
@ -589,6 +591,63 @@ int TPinballTable::Message(int code, float value)
return 0; return 0;
} }
TBall* TPinballTable::AddBall(float x, float y)
{
TBall* ball = nullptr;
for (auto curBall : BallList)
{
if (!curBall->ActiveFlag)
{
ball = curBall;
break;
}
}
if (ball != nullptr)
{
ball->ActiveFlag = 1;
ball->Position.Z = ball->Offset;
ball->Direction = {};
ball->Speed = 0;
ball->TimeDelta = 0;
ball->TimeNow = 0;
ball->EdgeCollisionCount = 0;
ball->CollisionFlag = 0;
ball->CollisionMask = 1;
ball->CollisionComp = nullptr;
}
else
{
if (BallList.size() >= 20)
return nullptr;
ball = new TBall(this);
BallList.push_back(ball);
}
ball->Position.X = x;
ball->Position.Y = y;
return ball;
}
int TPinballTable::BallCountInRect(const RectF& rect)
{
int count = 0;
for (const auto ball : BallList)
{
if (ball->ActiveFlag &&
ball->Position.X >= rect.XMin &&
ball->Position.Y >= rect.YMin &&
ball->Position.X <= rect.XMax &&
ball->Position.Y <= rect.YMax)
{
count++;
}
}
return count;
}
void TPinballTable::EndGame_timeout(int timerId, void* caller) void TPinballTable::EndGame_timeout(int timerId, void* caller)
{ {
auto table = static_cast<TPinballTable*>(caller); auto table = static_cast<TPinballTable*>(caller);

View File

@ -9,6 +9,7 @@ class TPlunger;
class TDrain; class TDrain;
class TDemo; class TDemo;
class TLightGroup; class TLightGroup;
struct RectF;
struct score_struct_super struct score_struct_super
{ {
@ -34,6 +35,8 @@ public:
void tilt(float time); void tilt(float time);
void port_draw() override; void port_draw() override;
int Message(int code, float value) override; int Message(int code, float value) override;
TBall* AddBall(float x, float y);
int BallCountInRect(const RectF& rect);
static void EndGame_timeout(int timerId, void* caller); static void EndGame_timeout(int timerId, void* caller);
static void LightShow_timeout(int timerId, void* caller); static void LightShow_timeout(int timerId, void* caller);
@ -49,7 +52,7 @@ public:
int SoundIndex1{}; int SoundIndex1{};
int SoundIndex2{}; int SoundIndex2{};
int SoundIndex3{}; int SoundIndex3{};
int BallInSink; int BallInDrainFlag;
int CurScore{}; int CurScore{};
int CurScoreE9{}; int CurScoreE9{};
int LightShowTimer; int LightShowTimer;
@ -86,9 +89,9 @@ public:
int BallCount{}; int BallCount{};
int MaxBallCount; int MaxBallCount;
int ExtraBalls{}; int ExtraBalls{};
int UnknownP75{}; int MultiballCount{};
int BallLockedCounter{}; int BallLockedCounter{};
int MultiballFlag; bool MultiballFlag;
int UnknownP78{}; int UnknownP78{};
int ReplayActiveFlag{}; int ReplayActiveFlag{};
int ReplayTimer; int ReplayTimer;

View File

@ -23,11 +23,17 @@ TPlunger::TPlunger(TPinballTable* table, int groupIndex) : TCollisionComponent(t
SoundIndexP2 = visual.SoundIndex3; SoundIndexP2 = visual.SoundIndex3;
HardHitSoundId = visual.Kicker.HardHitSoundId; HardHitSoundId = visual.Kicker.HardHitSoundId;
Threshold = 1000000000.0; Threshold = 1000000000.0;
MaxPullback = 100;
// In FT, default max pullback is 50.
if (pb::FullTiltMode)
MaxPullback = 50;
else
MaxPullback = 100;
Elasticity = 0.5f; Elasticity = 0.5f;
Smoothness = 0.5f; Smoothness = 0.5f;
PullbackIncrement = static_cast<int>(100.0 / (ListBitmap->size() * 8.0)); PullbackIncrement = MaxPullback / (ListBitmap->size() * 8.0f);
Unknown4F = 0.025f; PullbackDelay = 0.025f;
float* floatArr = loader::query_float_attribute(groupIndex, 0, 601); float* floatArr = loader::query_float_attribute(groupIndex, 0, 601);
table->PlungerPositionX = floatArr[0]; table->PlungerPositionX = floatArr[0];
table->PlungerPositionY = floatArr[1]; table->PlungerPositionY = floatArr[1];
@ -35,10 +41,19 @@ TPlunger::TPlunger(TPinballTable* table, int groupIndex) : TCollisionComponent(t
void TPlunger::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge) void TPlunger::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
if (PinballTable->TiltLockFlag) if (PinballTable->TiltLockFlag || SomeCounter > 0)
Message(1017, 0.0); {
auto boost = RandFloat() * Boost * 0.1f + Boost; auto boost = RandFloat() * MaxPullback * 0.1f + MaxPullback;
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, Threshold, boost); maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 0, boost);
if (SomeCounter)
SomeCounter--;
Message(1005, 0.0);
}
else
{
auto boost = RandFloat() * Boost * 0.1f + Boost;
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, Threshold, boost);
}
} }
int TPlunger::Message(int code, float value) int TPlunger::Message(int code, float value)
@ -46,56 +61,76 @@ int TPlunger::Message(int code, float value)
switch (code) switch (code)
{ {
case 1004: case 1004:
if (!PullbackTimer_) if (!PullbackStartedFlag && PinballTable->MultiballCount > 0 && !PinballTable->TiltLockFlag)
{ {
PullbackStartedFlag = true;
Boost = 0.0; Boost = 0.0;
Threshold = 1000000000.0; Threshold = 1000000000.0;
loader::play_sound(HardHitSoundId, this, "TPlunger1"); loader::play_sound(HardHitSoundId, this, "TPlunger1");
PullbackTimer(0, this); PullbackTimer(0, this);
} }
return 0; break;
case 1015: case 1015:
{ {
auto ball = PinballTable->BallList.at(0); RectF rect{};
ball->Message(1024, 0.0); rect.XMin = PinballTable->CollisionCompOffset * -1.2f + PinballTable->PlungerPositionX;
ball->Position.X = PinballTable->PlungerPositionX; rect.XMax = PinballTable->CollisionCompOffset * 1.2f + PinballTable->PlungerPositionX;
ball->Position.Y = PinballTable->PlungerPositionY; rect.YMin = PinballTable->CollisionCompOffset * -1.2f + PinballTable->PlungerPositionY;
ball->ActiveFlag = 1; rect.YMax = PinballTable->CollisionCompOffset * 1.2f + PinballTable->PlungerPositionY;
PinballTable->BallInSink = 0; if(PinballTable->BallCountInRect(rect))
pb::tilt_no_more(); {
control::handler(code, this); timer::set(1.0f, this, BallFeedTimer);
return 0; }
else
{
auto ball = PinballTable->AddBall(PinballTable->PlungerPositionX, PinballTable->PlungerPositionY);
assertm(ball, "Failure to create ball in plunger");
PinballTable->MultiballCount++;
PinballTable->BallInDrainFlag = 0;
pb::tilt_no_more();
}
break;
} }
case 1016: case 1016:
if (BallFeedTimer_) timer::set(0.95999998f, this, BallFeedTimer);
timer::kill(BallFeedTimer_);
BallFeedTimer_ = timer::set(0.95999998f, this, BallFeedTimer);
loader::play_sound(SoundIndexP1, this, "TPlunger2"); loader::play_sound(SoundIndexP1, this, "TPlunger2");
control::handler(code, this); break;
return 0;
case 1017: case 1017:
Threshold = 0.0; PullbackStartedFlag = true;
Boost = static_cast<float>(MaxPullback); Boost = MaxPullback;
timer::set(0.2f, this, PlungerReleasedTimer); Message(1005, 0.0f);
break;
case 1018:
SomeCounter++;
timer::set(value, this, BallFeedTimer);
loader::play_sound(SoundIndexP1, this, "TPlunger2_1");
PullbackStartedFlag = true;
PullbackTimer(0, this);
break;
case 1020:
PullbackStartedFlag = false;
Boost = 0.0f;
Threshold = 1000000000.0f;
SomeCounter = 0;
timer::kill(BallFeedTimer);
timer::kill(PullbackTimer);
timer::kill(ReleasedTimer);
break;
case 1011:
SomeCounter = 0;
timer::kill(BallFeedTimer);
break; break;
case 1005: case 1005:
case 1009: case 1009:
case 1010: case 1010:
case 1024: if (PullbackStartedFlag && !SomeCounter)
{ {
if (code == 1024) PullbackStartedFlag = false;
{
if (BallFeedTimer_)
timer::kill(BallFeedTimer_);
BallFeedTimer_ = 0;
}
Threshold = 0.0; Threshold = 0.0;
if (PullbackTimer_) if (PullbackTimer_)
timer::kill(PullbackTimer_); timer::kill(PullbackTimer_);
PullbackTimer_ = 0; PullbackTimer_ = 0;
if (code == 1005) loader::play_sound(SoundIndexP2, this, "TPlunger3");
loader::play_sound(SoundIndexP2, this, "TPlunger3");
auto bmp = ListBitmap->at(0); auto bmp = ListBitmap->at(0);
auto zMap = ListZMap->at(0); auto zMap = ListZMap->at(0);
render::sprite_set( render::sprite_set(
@ -105,38 +140,68 @@ int TPlunger::Message(int code, float value)
bmp->XPosition - PinballTable->XOffset, bmp->XPosition - PinballTable->XOffset,
bmp->YPosition - PinballTable->YOffset); bmp->YPosition - PinballTable->YOffset);
timer::set(Unknown4F, this, PlungerReleasedTimer); timer::set(PullbackDelay, this, ReleasedTimer);
break;
} }
break;
case 1024:
{
PullbackStartedFlag = false;
Boost = 0.0f;
Threshold = 1000000000.0f;
SomeCounter = 0;
timer::kill(BallFeedTimer);
timer::kill(PullbackTimer);
timer::kill(ReleasedTimer);
auto bmp = ListBitmap->at(0);
auto zMap = ListZMap->at(0);
render::sprite_set(
RenderSprite,
bmp,
zMap,
bmp->XPosition - PinballTable->XOffset,
bmp->YPosition - PinballTable->YOffset);
break;
}
default: default:
break; break;
} }
control::handler(code, this);
return 0; return 0;
} }
void TPlunger::BallFeedTimer(int timerId, void* caller) void TPlunger::BallFeedTimer(int timerId, void* caller)
{ {
auto plunger = static_cast<TPlunger*>(caller); auto plunger = static_cast<TPlunger*>(caller);
plunger->PullbackTimer_ = 0;
plunger->Message(1015, 0.0); plunger->Message(1015, 0.0);
} }
void TPlunger::PullbackTimer(int timerId, void* caller) void TPlunger::PullbackTimer(int timerId, void* caller)
{ {
auto plunger = static_cast<TPlunger*>(caller); auto plunger = static_cast<TPlunger*>(caller);
plunger->Boost += static_cast<float>(plunger->PullbackIncrement); plunger->Boost += plunger->PullbackIncrement;
if (plunger->Boost <= static_cast<float>(plunger->MaxPullback)) if (plunger->Boost <= plunger->MaxPullback)
{ {
plunger->PullbackTimer_ = timer::set(plunger->Unknown4F, plunger, PullbackTimer); if (plunger->SomeCounter)
{
plunger->PullbackTimer_ = timer::set(plunger->PullbackDelay / 4.0f, plunger, PullbackTimer);
}
else
{
plunger->PullbackTimer_ = timer::set(plunger->PullbackDelay, plunger, PullbackTimer);
}
} }
else else
{ {
plunger->PullbackTimer_ = 0; plunger->PullbackTimer_ = 0;
plunger->Boost = static_cast<float>(plunger->MaxPullback); plunger->Boost = plunger->MaxPullback;
} }
int index = static_cast<int>(floor( int index = static_cast<int>(floor(
static_cast<float>(plunger->ListBitmap->size() - 1) * static_cast<float>(plunger->ListBitmap->size() - 1) *
(plunger->Boost / static_cast<float>(plunger->MaxPullback)))); (plunger->Boost / plunger->MaxPullback)));
auto bmp = plunger->ListBitmap->at(index); auto bmp = plunger->ListBitmap->at(index);
auto zMap = plunger->ListZMap->at(index); auto zMap = plunger->ListZMap->at(index);
render::sprite_set( render::sprite_set(
@ -147,7 +212,7 @@ void TPlunger::PullbackTimer(int timerId, void* caller)
bmp->YPosition - plunger->PinballTable->YOffset); bmp->YPosition - plunger->PinballTable->YOffset);
} }
void TPlunger::PlungerReleasedTimer(int timerId, void* caller) void TPlunger::ReleasedTimer(int timerId, void* caller)
{ {
auto plunger = static_cast<TPlunger*>(caller); auto plunger = static_cast<TPlunger*>(caller);
plunger->Threshold = 1000000000.0; plunger->Threshold = 1000000000.0;

View File

@ -13,13 +13,15 @@ public:
static void BallFeedTimer(int timerId, void* caller); static void BallFeedTimer(int timerId, void* caller);
static void PullbackTimer(int timerId, void* caller); static void PullbackTimer(int timerId, void* caller);
static void PlungerReleasedTimer(int timerId, void* caller); static void ReleasedTimer(int timerId, void* caller);
int PullbackTimer_; int PullbackTimer_;
int BallFeedTimer_; int BallFeedTimer_;
int MaxPullback; float MaxPullback;
int PullbackIncrement; float PullbackIncrement;
float Unknown4F; float PullbackDelay;
int SoundIndexP1; int SoundIndexP1;
int SoundIndexP2; int SoundIndexP2;
bool PullbackStartedFlag{};
int SomeCounter{};
}; };

View File

@ -14,7 +14,6 @@ TSink::TSink(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
visualStruct visual{}; visualStruct visual{};
MessageField = 0; MessageField = 0;
Timer = 0;
loader::query_visual(groupIndex, 0, &visual); loader::query_visual(groupIndex, 0, &visual);
BallThrowDirection = visual.Kicker.ThrowBallDirection; BallThrowDirection = visual.Kicker.ThrowBallDirection;
ThrowAngleMult = visual.Kicker.ThrowBallAngleMult; ThrowAngleMult = visual.Kicker.ThrowBallAngleMult;
@ -35,27 +34,19 @@ int TSink::Message(int code, float value)
case 56: case 56:
if (value < 0.0f) if (value < 0.0f)
value = TimerTime; value = TimerTime;
Timer = timer::set(value, this, TimerExpired); timer::set(value, this, TimerExpired);
break; break;
case 1020: case 1020:
timer::kill(TimerExpired);
PlayerMessagefieldBackup[PinballTable->CurrentPlayer] = MessageField; PlayerMessagefieldBackup[PinballTable->CurrentPlayer] = MessageField;
MessageField = PlayerMessagefieldBackup[static_cast<int>(floor(value))]; MessageField = PlayerMessagefieldBackup[static_cast<int>(floor(value))];
break; break;
case 1024: case 1024:
{ {
if (Timer) timer::kill(TimerExpired);
timer::kill(Timer);
Timer = 0;
MessageField = 0; MessageField = 0;
for (auto &msgBackup : PlayerMessagefieldBackup)
auto playerPtr = PlayerMessagefieldBackup; msgBackup = 0;
for (auto index = 0; index < PinballTable->PlayerCount; ++index)
{
*playerPtr = 0;
++playerPtr;
}
break; break;
} }
default: default:
@ -66,15 +57,13 @@ int TSink::Message(int code, float value)
void TSink::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge) void TSink::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
Timer = 0;
if (PinballTable->TiltLockFlag) if (PinballTable->TiltLockFlag)
{ {
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0); maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0);
} }
else else
{ {
ball->ActiveFlag = 0; ball->Disable();
render::sprite_set_bitmap(ball->RenderSprite, nullptr);
loader::play_sound(SoundIndex4, ball, "TSink1"); loader::play_sound(SoundIndex4, ball, "TSink1");
control::handler(63, this); control::handler(63, this);
} }
@ -82,15 +71,26 @@ void TSink::Collision(TBall* ball, vector2* nextPosition, vector2* direction, fl
void TSink::TimerExpired(int timerId, void* caller) void TSink::TimerExpired(int timerId, void* caller)
{ {
RectF rect{};
auto sink = static_cast<TSink*>(caller); auto sink = static_cast<TSink*>(caller);
auto ball = sink->PinballTable->BallList.at(0); auto table = sink->PinballTable;
ball->CollisionComp = nullptr;
ball->ActiveFlag = 1; rect.XMin = table->CollisionCompOffset * -2.0f + sink->BallPosition.X;
ball->Position.X = sink->BallPosition.X; rect.XMax = table->CollisionCompOffset * 2.0f + sink->BallPosition.X;
ball->Position.Y = sink->BallPosition.Y; rect.YMin = table->CollisionCompOffset * -2.0f + sink->BallPosition.Y;
TBall::throw_ball(ball, &sink->BallThrowDirection, sink->ThrowAngleMult, sink->ThrowSpeedMult1, rect.YMax = table->CollisionCompOffset * 2.0f + sink->BallPosition.Y;
sink->ThrowSpeedMult2); if (table->BallCountInRect(rect))
if (sink->SoundIndex3) {
loader::play_sound(sink->SoundIndex3, ball, "TSink2"); timer::set(0.5f, sink, TimerExpired);
sink->Timer = 0; }
else
{
auto ball = table->AddBall(sink->BallPosition.X, sink->BallPosition.Y);
assertm(ball, "Failure to create ball in sink");
TBall::throw_ball(ball, &sink->BallThrowDirection, sink->ThrowAngleMult, sink->ThrowSpeedMult1,
sink->ThrowSpeedMult2);
if (sink->SoundIndex3)
loader::play_sound(sink->SoundIndex3, ball, "TSink2");
}
} }

View File

@ -13,7 +13,6 @@ public:
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
int Timer;
float TimerTime; float TimerTime;
vector2 BallPosition{}; vector2 BallPosition{};
vector3 BallThrowDirection{}; vector3 BallThrowDirection{};

View File

@ -108,6 +108,9 @@ component_tag<TLight> control_lite27_tag = {"lite27"};
component_tag<TLight> control_lite28_tag = {"lite28"}; component_tag<TLight> control_lite28_tag = {"lite28"};
component_tag<TLight> control_lite29_tag = {"lite29"}; component_tag<TLight> control_lite29_tag = {"lite29"};
component_tag<TLight> control_lite30_tag = {"lite30"}; component_tag<TLight> control_lite30_tag = {"lite30"};
component_tag<TLight> control_lite38_tag = {"lite38"};
component_tag<TLight> control_lite39_tag = {"lite39"};
component_tag<TLight> control_lite40_tag = {"lite40"};
component_tag<TLight> control_lite54_tag = {"lite54"}; component_tag<TLight> control_lite54_tag = {"lite54"};
component_tag<TLight> control_lite55_tag = {"lite55"}; component_tag<TLight> control_lite55_tag = {"lite55"};
component_tag<TLight> control_lite56_tag = {"lite56"}; component_tag<TLight> control_lite56_tag = {"lite56"};
@ -341,6 +344,9 @@ TLight*& lite27 = control_lite27_tag.Component;
TLight*& lite28 = control_lite28_tag.Component; TLight*& lite28 = control_lite28_tag.Component;
TLight*& lite29 = control_lite29_tag.Component; TLight*& lite29 = control_lite29_tag.Component;
TLight*& lite30 = control_lite30_tag.Component; TLight*& lite30 = control_lite30_tag.Component;
TLight*& lite38 = control_lite38_tag.Component;
TLight*& lite39 = control_lite39_tag.Component;
TLight*& lite40 = control_lite40_tag.Component;
TLight*& lite54 = control_lite54_tag.Component; TLight*& lite54 = control_lite54_tag.Component;
TLight*& lite55 = control_lite55_tag.Component; TLight*& lite55 = control_lite55_tag.Component;
TLight*& lite56 = control_lite56_tag.Component; TLight*& lite56 = control_lite56_tag.Component;
@ -615,7 +621,7 @@ component_info control::score_components[88]
}; };
component_tag_base* control::simple_components[142] component_tag_base* control::simple_components[145]
{ {
&control_lite8_tag, &control_lite8_tag,
&control_lite9_tag, &control_lite9_tag,
@ -758,7 +764,10 @@ component_tag_base* control::simple_components[142]
&control_lite322_tag, &control_lite322_tag,
&control_goal_lights_tag, &control_goal_lights_tag,
&control_soundwave25_tag, &control_soundwave25_tag,
&control_soundwave7_tag &control_soundwave7_tag,
&control_lite38_tag,
&control_lite39_tag,
&control_lite40_tag,
}; };
int control::waiting_deployment_flag; int control::waiting_deployment_flag;
@ -953,25 +962,40 @@ void control::table_set_flag_lights()
info_text_box->Display(pinball::get_rc_string(51, 0), 2.0); info_text_box->Display(pinball::get_rc_string(51, 0), 2.0);
} }
void control::table_set_multiball() void control::table_set_multiball(float time)
{ {
info_text_box->Display(pinball::get_rc_string(16, 0), 2.0); if (TableG->MultiballCount <= 1)
midi::play_track(MidiTracks::Track3, true); {
TableG->MultiballCount += 3;
sink1->Message(56, time);
sink2->Message(56, time);
sink3->Message(56, time);
lite38->Message(7, -1.0f);
lite39->Message(7, -1.0f);
lite40->Message(7, -1.0f);
info_text_box->Display(pinball::get_rc_string(16, 0), 2.0);
midi::play_track(MidiTracks::Track3, true);
}
} }
void control::table_bump_ball_sink_lock() void control::table_bump_ball_sink_lock()
{ {
if (TableG->BallLockedCounter == 2) if (TableG->MultiballCount <= 1)
{ {
table_set_multiball(); TableG->MultiballCount--;
TableG->BallLockedCounter = 0; if (TableG->BallLockedCounter == 2)
} {
else soundwave41->Play(nullptr, "table_bump_ball_sink_lock_set_multiball");
{ table_set_multiball(2.0);
TableG->BallLockedCounter = TableG->BallLockedCounter + 1; TableG->BallLockedCounter = 0;
soundwave44->Play(nullptr, "table_bump_ball_sink_lock"); }
info_text_box->Display(pinball::get_rc_string(1, 0), 2.0); else
TableG->Plunger->Message(1016, 0.0); {
TableG->BallLockedCounter = TableG->BallLockedCounter + 1;
soundwave44->Play(nullptr, "table_bump_ball_sink_lock");
info_text_box->Display(pinball::get_rc_string(1, 0), 2.0);
TableG->Plunger->Message(1018, 2.0f);
}
} }
} }
@ -1630,19 +1654,30 @@ void control::WormHoleControl(int code, TPinballComponent* caller)
{ {
if (TableG->MultiballFlag) if (TableG->MultiballFlag)
{ {
table_bump_ball_sink_lock(); if (TableG->MultiballCount == 1)
TableG->AddScore(10000); {
table_bump_ball_sink_lock();
TableG->AddScore(10000);
return;
}
else
{
table_set_replay(4.0);
TableG->AddScore(50000);
}
} }
else else
{ {
info_text_box->Display(pinball::get_rc_string(49, 0), 2.0);
table_set_replay(4.0); table_set_replay(4.0);
TableG->AddScore(sink->get_scoring(1)); TableG->AddScore(sink->get_scoring(1));
wormhole_tag_array2[sinkFlag]->GetComponent()->Message(16, sink->TimerTime);
wormhole_tag_array3[sinkFlag]->GetComponent()->Message(11, static_cast<float>(2 - sinkFlag));
wormhole_tag_array3[sinkFlag]->GetComponent()->Message(16, sink->TimerTime);
wormhole_tag_array1[sinkFlag]->GetComponent()->Message(56, sink->TimerTime);
} }
info_text_box->Display(pinball::get_rc_string(49, 0), 2.0);
wormhole_tag_array2[sinkFlag]->GetComponent()->Message(16, sink->TimerTime);
wormhole_tag_array3[sinkFlag]->GetComponent()->Message(11, static_cast<float>(2 - sinkFlag));
wormhole_tag_array3[sinkFlag]->GetComponent()->Message(16, sink->TimerTime);
wormhole_tag_array1[sinkFlag]->GetComponent()->Message(56, sink->TimerTime);
return; return;
} }
TableG->AddScore(sink->get_scoring(2)); TableG->AddScore(sink->get_scoring(2));
@ -2352,7 +2387,8 @@ void control::HyperspaceKickOutControl(int code, TPinballComponent* caller)
if (TableG->MultiballFlag) if (TableG->MultiballFlag)
{ {
table_set_multiball(); auto duration = soundwave41->Play(nullptr, "HyperspaceKickOutControl_setMultiball");
table_set_multiball(duration);
} }
if (TableG->ScoreSpecial3 < 100000) if (TableG->ScoreSpecial3 < 100000)
TableG->ScoreSpecial3 = 100000; TableG->ScoreSpecial3 = 100000;
@ -2580,10 +2616,18 @@ void control::BallDrainControl(int code, TPinballComponent* caller)
soundwave59->Play(nullptr, "BallDrainControl5"); soundwave59->Play(nullptr, "BallDrainControl5");
--TableG->UnknownP78; --TableG->UnknownP78;
} }
else if (TableG->UnknownP75) else if (TableG->MultiballCount)
{ {
soundwave27->Play(nullptr, "BallDrainControl6"); if (TableG->MultiballCount == 1)
--TableG->UnknownP75; {
lite38->Message(20, 0.0f);
lite39->Message(20, 0.0f);
midi::play_track(MidiTracks::Track1, false);
}
else if (TableG->MultiballCount == 2)
{
lite40->Message(20, 0.0f);
}
} }
else else
{ {

View File

@ -63,7 +63,7 @@ class control
public: public:
static TPinballTable* TableG; static TPinballTable* TableG;
static component_info score_components[88]; static component_info score_components[88];
static component_tag_base* simple_components[142]; static component_tag_base* simple_components[145];
static int waiting_deployment_flag; static int waiting_deployment_flag;
static bool table_unlimited_balls; static bool table_unlimited_balls;
static int RankRcArray[9], MissionRcArray[17], mission_select_scores[17]; static int RankRcArray[9], MissionRcArray[17], mission_select_scores[17];
@ -79,7 +79,7 @@ public:
static void table_set_bonus(); static void table_set_bonus();
static void table_set_jackpot(); static void table_set_jackpot();
static void table_set_flag_lights(); static void table_set_flag_lights();
static void table_set_multiball(); static void table_set_multiball(float time);
static void table_bump_ball_sink_lock(); static void table_bump_ball_sink_lock();
static void table_set_replay(float value); static void table_set_replay(float value);
static void cheat_bump_rank(); static void cheat_bump_rank();

View File

@ -85,6 +85,11 @@ struct ramp_plane_type
vector2 FieldForce; vector2 FieldForce;
}; };
struct RectF
{
float XMax, YMax, XMin, YMin;
};
enum class FlipperIntersect enum class FlipperIntersect
{ {
none = -1, none = -1,

View File

@ -241,10 +241,16 @@ void pb::ballset(float dx, float dy)
{ {
// dx and dy are normalized to window, ideally in [-1, 1] // dx and dy are normalized to window, ideally in [-1, 1]
static constexpr float sensitivity = 7000; static constexpr float sensitivity = 7000;
TBall* ball = MainTable->BallList.at(0);
ball->Direction.X = dx * sensitivity; for (auto ball : MainTable->BallList)
ball->Direction.Y = dy * sensitivity; {
ball->Speed = maths::normalize_2d(ball->Direction); if (ball->ActiveFlag)
{
ball->Direction.X = dx * sensitivity;
ball->Direction.Y = dy * sensitivity;
ball->Speed = maths::normalize_2d(ball->Direction);
}
}
} }
void pb::frame(float dtMilliSec) void pb::frame(float dtMilliSec)
@ -469,33 +475,8 @@ void pb::InputDown(GameInput input)
switch (input.Value) switch (input.Value)
{ {
case 'b': case 'b':
TBall* ball; if (MainTable->AddBall(6.0f, 7.0f))
if (MainTable->BallList.empty()) MainTable->MultiballCount++;
{
ball = new TBall(MainTable);
}
else
{
for (auto index = 0u; ;)
{
ball = MainTable->BallList.at(index);
if (!ball->ActiveFlag)
break;
++index;
if (index >= MainTable->BallList.size())
{
ball = new TBall(MainTable);
break;
}
}
}
ball->ActiveFlag = 1;
ball->Position.X = 1.0;
ball->Position.Z = ball->Offset;
ball->Position.Y = 1.0;
ball->Direction.Z = 0.0;
ball->Direction.Y = 0.0;
ball->Direction.X = 0.0;
break; break;
case 'h': case 'h':
{ {

View File

@ -61,6 +61,35 @@ int timer::kill(int timerId)
return timerId; return timerId;
} }
int timer::kill(void(* callback)(int, void*))
{
auto count = 0;
timer_struct* current = ActiveList, * prev = nullptr;
for (auto index = 0; index < Count; index++)
{
if (current->Callback == callback)
{
count++;
if (prev)
prev->NextTimer = current->NextTimer;
else
ActiveList = current->NextTimer;
current->NextTimer = FreeList;
FreeList = current;
if (--Count == index)
break;
current = current->NextTimer;
}
else
{
prev = current;
current = current->NextTimer;
}
}
return count;
}
int timer::set(float time, void* caller, void (* callback)(int, void*)) int timer::set(float time, void* caller, void (* callback)(int, void*))
{ {
if (Count >= MaxCount) if (Count >= MaxCount)

View File

@ -15,6 +15,7 @@ public:
static int init(int count); static int init(int count);
static void uninit(); static void uninit();
static int kill(int timerId); static int kill(int timerId);
static int kill(void (*callback)(int, void*));
static int set(float time, void* caller, void (* callback)(int, void*)); static int set(float time, void* caller, void (* callback)(int, void*));
static int check(); static int check();