1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include "Ball.hxx"
19#include "TIA.hxx"
20
21// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
22Ball::Ball(uInt32 collisionMask)
23 : myCollisionMaskDisabled(collisionMask),
24 myCollisionMaskEnabled(0xFFFF),
25 myIsSuppressed(false),
26 myTIA(nullptr)
27{
28 reset();
29}
30
31// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
32void Ball::reset()
33{
34 myColor = myObjectColor = myDebugColor = 0;
35 collision = myCollisionMaskDisabled;
36 myIsEnabledOld = false;
37 myIsEnabledNew = false;
38 myIsEnabled = false;
39 myIsDelaying = false;
40 mySignalActive = false;
41 myHmmClocks = 0;
42 myCounter = 0;
43 isMoving = false;
44 myEffectiveWidth = 1;
45 myLastMovementTick = 0;
46 myWidth = 1;
47 myIsRendering = false;
48 myDebugEnabled = false;
49 myRenderCounter = 0;
50 myIsEnabled = false;
51 myInvertedPhaseClock = false;
52 myUseInvertedPhaseClock = false;
53}
54
55// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
56void Ball::enabl(uInt8 value)
57{
58 const auto enabledNewOldValue = myIsEnabledNew;
59
60 myIsEnabledNew = (value & 0x02) > 0;
61
62 if (myIsEnabledNew != enabledNewOldValue && !myIsDelaying) {
63 myTIA->flushLineCache();
64
65 updateEnabled();
66 }
67}
68
69// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
70void Ball::hmbl(uInt8 value)
71{
72 myHmmClocks = (value >> 4) ^ 0x08;
73}
74
75// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
76void Ball::resbl(uInt8 counter)
77{
78 myCounter = counter;
79
80 myIsRendering = true;
81 myRenderCounter = Count::renderCounterOffset + (counter - 157);
82}
83
84// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
85void Ball::ctrlpf(uInt8 value)
86{
87 static constexpr uInt8 ourWidths[] = {1, 2, 4, 8};
88
89 const uInt8 newWidth = ourWidths[(value & 0x30) >> 4];
90
91 if (newWidth != myWidth) {
92 myTIA->flushLineCache();
93 myWidth = newWidth;
94 }
95}
96
97// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
98void Ball::vdelbl(uInt8 value)
99{
100 const auto oldIsDelaying = myIsDelaying;
101
102 myIsDelaying = (value & 0x01) > 0;
103
104 if (oldIsDelaying != myIsDelaying) {
105 myTIA->flushLineCache();
106 updateEnabled();
107 }
108}
109
110// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
111void Ball::toggleCollisions(bool enabled)
112{
113 myCollisionMaskEnabled = enabled ? 0xFFFF : (0x8000 | myCollisionMaskDisabled);
114}
115
116// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
117void Ball::toggleEnabled(bool enabled)
118{
119 myIsSuppressed = !enabled;
120 updateEnabled();
121}
122
123// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
124void Ball::setColor(uInt8 color)
125{
126 if (color != myObjectColor && myIsEnabled) myTIA->flushLineCache();
127
128 myObjectColor = color;
129 applyColors();
130}
131
132// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
133void Ball::setDebugColor(uInt8 color)
134{
135 myTIA->flushLineCache();
136 myDebugColor = color;
137 applyColors();
138}
139
140// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
141void Ball::enableDebugColors(bool enabled)
142{
143 myTIA->flushLineCache();
144 myDebugEnabled = enabled;
145 applyColors();
146}
147
148// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
149void Ball::applyColorLoss()
150{
151 myTIA->flushLineCache();
152 applyColors();
153}
154
155// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
156void Ball::setInvertedPhaseClock(bool enable)
157{
158 myUseInvertedPhaseClock = enable;
159}
160
161
162// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
163void Ball::startMovement()
164{
165 isMoving = true;
166}
167
168// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
169void Ball::nextLine()
170{
171 // Reevalute the collision mask in order to properly account for collisions during
172 // hblank. Usually, this will be taken care off in the next tick, but there is no
173 // next tick before hblank ends.
174 mySignalActive = myIsRendering && myRenderCounter >= 0;
175 collision = (mySignalActive && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled;
176}
177
178// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
179void Ball::setENABLOld(bool enabled)
180{
181 myTIA->flushLineCache();
182
183 myIsEnabledOld = enabled;
184 updateEnabled();
185}
186
187// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
188void Ball::shuffleStatus()
189{
190 const auto oldIsEnabledOld = myIsEnabledOld;
191
192 myIsEnabledOld = myIsEnabledNew;
193
194 if (myIsEnabledOld != oldIsEnabledOld && myIsDelaying) {
195 myTIA->flushLineCache();
196 updateEnabled();
197 }
198}
199
200// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
201void Ball::updateEnabled()
202{
203 myIsEnabled = !myIsSuppressed && (myIsDelaying ? myIsEnabledOld : myIsEnabledNew);
204
205 collision = (mySignalActive && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled;
206 myTIA->scheduleCollisionUpdate();
207}
208
209// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
210void Ball::applyColors()
211{
212 if (!myDebugEnabled)
213 {
214 if (myTIA->colorLossActive()) myObjectColor |= 0x01;
215 else myObjectColor &= 0xfe;
216 myColor = myObjectColor;
217 }
218 else
219 myColor = myDebugColor;
220}
221
222// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
223uInt8 Ball::getPosition() const
224{
225 // position =
226 // current playfield x +
227 // (current counter - 156 (the decode clock of copy 0)) +
228 // clock count after decode until first pixel +
229 // 1 (it'll take another cycle after the decode for the rendter counter to start ticking)
230 //
231 // The result may be negative, so we add 160 and do the modulus -> 317 = 156 + TIA::H_PIXEL + 1
232 //
233 // Mind the sign of renderCounterOffset: it's defined negative above
234 return (317 - myCounter - Count::renderCounterOffset + myTIA->getPosition()) % TIAConstants::H_PIXEL;
235}
236
237// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
238void Ball::setPosition(uInt8 newPosition)
239{
240 myTIA->flushLineCache();
241
242 // See getPosition for an explanation
243 myCounter = (317 - newPosition - Count::renderCounterOffset + myTIA->getPosition()) % TIAConstants::H_PIXEL;
244}
245
246// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
247bool Ball::save(Serializer& out) const
248{
249 try
250 {
251 out.putInt(collision);
252 out.putInt(myCollisionMaskDisabled);
253 out.putInt(myCollisionMaskEnabled);
254
255 out.putByte(myColor);
256 out.putByte(myObjectColor);
257 out.putByte(myDebugColor);
258 out.putBool(myDebugEnabled);
259
260 out.putBool(myIsEnabledOld);
261 out.putBool(myIsEnabledNew);
262 out.putBool(myIsEnabled);
263 out.putBool(myIsSuppressed);
264 out.putBool(myIsDelaying);
265 out.putBool(mySignalActive);
266
267 out.putByte(myHmmClocks);
268 out.putByte(myCounter);
269 out.putBool(isMoving);
270 out.putByte(myWidth);
271 out.putByte(myEffectiveWidth);
272 out.putByte(myLastMovementTick);
273
274 out.putBool(myIsRendering);
275 out.putByte(myRenderCounter);
276 out.putBool(myInvertedPhaseClock);
277 }
278 catch(...)
279 {
280 cerr << "ERROR: TIA_Ball::save" << endl;
281 return false;
282 }
283
284 return true;
285}
286
287// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
288bool Ball::load(Serializer& in)
289{
290 try
291 {
292 collision = in.getInt();
293 myCollisionMaskDisabled = in.getInt();
294 myCollisionMaskEnabled = in.getInt();
295
296 myColor = in.getByte();
297 myObjectColor = in.getByte();
298 myDebugColor = in.getByte();
299 myDebugEnabled = in.getBool();
300
301 myIsEnabledOld = in.getBool();
302 myIsEnabledNew = in.getBool();
303 myIsEnabled = in.getBool();
304 myIsSuppressed = in.getBool();
305 myIsDelaying = in.getBool();
306 mySignalActive = in.getBool();
307
308 myHmmClocks = in.getByte();
309 myCounter = in.getByte();
310 isMoving = in.getBool();
311 myWidth = in.getByte();
312 myEffectiveWidth = in.getByte();
313 myLastMovementTick = in.getByte();
314
315 myIsRendering = in.getBool();
316 myRenderCounter = in.getByte();
317 myInvertedPhaseClock = in.getBool();
318
319 applyColors();
320 }
321 catch(...)
322 {
323 cerr << "ERROR: TIA_Ball::load" << endl;
324 return false;
325 }
326
327 return true;
328}
329