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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
22 | Ball::Ball(uInt32 collisionMask) |
23 | : myCollisionMaskDisabled(collisionMask), |
24 | myCollisionMaskEnabled(0xFFFF), |
25 | myIsSuppressed(false), |
26 | myTIA(nullptr) |
27 | { |
28 | reset(); |
29 | } |
30 | |
31 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
32 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
56 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
70 | void Ball::hmbl(uInt8 value) |
71 | { |
72 | myHmmClocks = (value >> 4) ^ 0x08; |
73 | } |
74 | |
75 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
76 | void Ball::resbl(uInt8 counter) |
77 | { |
78 | myCounter = counter; |
79 | |
80 | myIsRendering = true; |
81 | myRenderCounter = Count::renderCounterOffset + (counter - 157); |
82 | } |
83 | |
84 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
85 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
98 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
111 | void Ball::toggleCollisions(bool enabled) |
112 | { |
113 | myCollisionMaskEnabled = enabled ? 0xFFFF : (0x8000 | myCollisionMaskDisabled); |
114 | } |
115 | |
116 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
117 | void Ball::toggleEnabled(bool enabled) |
118 | { |
119 | myIsSuppressed = !enabled; |
120 | updateEnabled(); |
121 | } |
122 | |
123 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
124 | void Ball::setColor(uInt8 color) |
125 | { |
126 | if (color != myObjectColor && myIsEnabled) myTIA->flushLineCache(); |
127 | |
128 | myObjectColor = color; |
129 | applyColors(); |
130 | } |
131 | |
132 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
133 | void Ball::setDebugColor(uInt8 color) |
134 | { |
135 | myTIA->flushLineCache(); |
136 | myDebugColor = color; |
137 | applyColors(); |
138 | } |
139 | |
140 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
141 | void Ball::enableDebugColors(bool enabled) |
142 | { |
143 | myTIA->flushLineCache(); |
144 | myDebugEnabled = enabled; |
145 | applyColors(); |
146 | } |
147 | |
148 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
149 | void Ball::applyColorLoss() |
150 | { |
151 | myTIA->flushLineCache(); |
152 | applyColors(); |
153 | } |
154 | |
155 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
156 | void Ball::setInvertedPhaseClock(bool enable) |
157 | { |
158 | myUseInvertedPhaseClock = enable; |
159 | } |
160 | |
161 | |
162 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
163 | void Ball::startMovement() |
164 | { |
165 | isMoving = true; |
166 | } |
167 | |
168 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
169 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
179 | void Ball::setENABLOld(bool enabled) |
180 | { |
181 | myTIA->flushLineCache(); |
182 | |
183 | myIsEnabledOld = enabled; |
184 | updateEnabled(); |
185 | } |
186 | |
187 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
188 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
201 | void Ball::updateEnabled() |
202 | { |
203 | myIsEnabled = !myIsSuppressed && (myIsDelaying ? myIsEnabledOld : myIsEnabledNew); |
204 | |
205 | collision = (mySignalActive && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled; |
206 | myTIA->scheduleCollisionUpdate(); |
207 | } |
208 | |
209 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
210 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
223 | uInt8 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
238 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
247 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
288 | bool 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 | |