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 "Player.hxx" |
19 | #include "DrawCounterDecodes.hxx" |
20 | #include "TIA.hxx" |
21 | |
22 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
23 | Player::Player(uInt32 collisionMask) |
24 | : myCollisionMaskDisabled(collisionMask), |
25 | myCollisionMaskEnabled(0xFFFF), |
26 | myIsSuppressed(false), |
27 | myDecodesOffset(0), |
28 | myTIA(nullptr) |
29 | { |
30 | reset(); |
31 | } |
32 | |
33 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
34 | void Player::reset() |
35 | { |
36 | myDecodes = DrawCounterDecodes::get().playerDecodes()[myDecodesOffset]; |
37 | myHmmClocks = 0; |
38 | myCounter = 0; |
39 | isMoving = false; |
40 | myIsRendering = false; |
41 | myRenderCounter = 0; |
42 | myPatternOld = 0; |
43 | myPatternNew = 0; |
44 | myIsReflected = 0; |
45 | myIsDelaying = false; |
46 | myColor = myObjectColor = myDebugColor = 0; |
47 | myDebugEnabled = false; |
48 | collision = myCollisionMaskDisabled; |
49 | mySampleCounter = 0; |
50 | myDividerPending = 0; |
51 | myDividerChangeCounter = -1; |
52 | myInvertedPhaseClock = false; |
53 | myUseInvertedPhaseClock = false; |
54 | myPattern = 0; |
55 | |
56 | setDivider(1); |
57 | } |
58 | |
59 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
60 | void Player::grp(uInt8 pattern) |
61 | { |
62 | const uInt8 oldPatternNew = myPatternNew; |
63 | |
64 | myPatternNew = pattern; |
65 | |
66 | if (!myIsDelaying && myPatternNew != oldPatternNew) { |
67 | myTIA->flushLineCache(); |
68 | updatePattern(); |
69 | } |
70 | } |
71 | |
72 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
73 | void Player::hmp(uInt8 value) |
74 | { |
75 | myHmmClocks = (value >> 4) ^ 0x08; |
76 | } |
77 | |
78 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
79 | void Player::nusiz(uInt8 value, bool hblank) |
80 | { |
81 | myDecodesOffset = value & 0x07; |
82 | |
83 | switch (myDecodesOffset) { |
84 | case 5: |
85 | myDividerPending = 2; |
86 | break; |
87 | |
88 | case 7: |
89 | myDividerPending = 4; |
90 | break; |
91 | |
92 | default: |
93 | myDividerPending = 1; |
94 | break; |
95 | } |
96 | |
97 | const uInt8* oldDecodes = myDecodes; |
98 | |
99 | myDecodes = DrawCounterDecodes::get().playerDecodes()[myDecodesOffset]; |
100 | |
101 | if ( |
102 | myDecodes != oldDecodes && |
103 | myIsRendering && |
104 | (myRenderCounter - Count::renderCounterOffset) < 2 && |
105 | !myDecodes[(myCounter - myRenderCounter + Count::renderCounterOffset + TIAConstants::H_PIXEL - 1) % TIAConstants::H_PIXEL] |
106 | ) { |
107 | myIsRendering = false; |
108 | } |
109 | |
110 | if (myDividerPending == myDivider) return; |
111 | |
112 | // The following is an effective description of the effects of NUSIZ during |
113 | // decode and rendering. |
114 | |
115 | if (myIsRendering) { |
116 | Int8 delta = myRenderCounter - Count::renderCounterOffset; |
117 | |
118 | switch ((myDivider << 4) | myDividerPending) { |
119 | case 0x12: |
120 | case 0x14: |
121 | if (hblank) { |
122 | if (delta < 4) |
123 | setDivider(myDividerPending); |
124 | else |
125 | myDividerChangeCounter = (delta < 5 ? 1 : 0); |
126 | } else { |
127 | if (delta < 3) |
128 | setDivider(myDividerPending); |
129 | else |
130 | myDividerChangeCounter = 1; |
131 | } |
132 | |
133 | break; |
134 | |
135 | case 0x21: |
136 | case 0x41: |
137 | if (delta < (hblank ? 4 : 3)) { |
138 | setDivider(myDividerPending); |
139 | } else if (delta < (hblank ? 6 : 5)) { |
140 | setDivider(myDividerPending); |
141 | --myRenderCounter; |
142 | } else { |
143 | myDividerChangeCounter = (hblank ? 0 : 1); |
144 | } |
145 | |
146 | break; |
147 | |
148 | case 0x42: |
149 | case 0x24: |
150 | if (myRenderCounter < 1 || (hblank && (myRenderCounter % myDivider == 1))) |
151 | setDivider(myDividerPending); |
152 | else |
153 | myDividerChangeCounter = (myDivider - (myRenderCounter - 1) % myDivider); |
154 | break; |
155 | |
156 | default: |
157 | // should never happen |
158 | setDivider(myDividerPending); |
159 | break; |
160 | } |
161 | |
162 | } else { |
163 | setDivider(myDividerPending); |
164 | } |
165 | } |
166 | |
167 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
168 | void Player::resp(uInt8 counter) |
169 | { |
170 | myCounter = counter; |
171 | |
172 | // This tries to account for the effects of RESP during draw counter decode as |
173 | // described in Andrew Towers' notes. Still room for tuning.' |
174 | if (myIsRendering && (myRenderCounter - Count::renderCounterOffset) < 4) |
175 | myRenderCounter = Count::renderCounterOffset + (counter - 157); |
176 | } |
177 | |
178 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
179 | void Player::refp(uInt8 value) |
180 | { |
181 | const bool oldIsReflected = myIsReflected; |
182 | |
183 | myIsReflected = (value & 0x08) > 0; |
184 | |
185 | if (oldIsReflected != myIsReflected) { |
186 | myTIA->flushLineCache(); |
187 | updatePattern(); |
188 | } |
189 | } |
190 | |
191 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
192 | void Player::vdelp(uInt8 value) |
193 | { |
194 | const bool oldIsDelaying = myIsDelaying; |
195 | |
196 | myIsDelaying = (value & 0x01) > 0; |
197 | |
198 | if (oldIsDelaying != myIsDelaying) { |
199 | myTIA->flushLineCache(); |
200 | updatePattern(); |
201 | } |
202 | } |
203 | |
204 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
205 | void Player::toggleEnabled(bool enabled) |
206 | { |
207 | const bool oldIsSuppressed = myIsSuppressed; |
208 | |
209 | myIsSuppressed = !enabled; |
210 | |
211 | if (oldIsSuppressed != myIsSuppressed) updatePattern(); |
212 | } |
213 | |
214 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
215 | void Player::toggleCollisions(bool enabled) |
216 | { |
217 | myCollisionMaskEnabled = enabled ? 0xFFFF : (0x8000 | myCollisionMaskDisabled); |
218 | } |
219 | |
220 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
221 | void Player::setColor(uInt8 color) |
222 | { |
223 | if (color != myObjectColor && myPattern) myTIA->flushLineCache(); |
224 | |
225 | myObjectColor = color; |
226 | applyColors(); |
227 | } |
228 | |
229 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
230 | void Player::setDebugColor(uInt8 color) |
231 | { |
232 | myTIA->flushLineCache(); |
233 | myDebugColor = color; |
234 | applyColors(); |
235 | } |
236 | |
237 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
238 | void Player::enableDebugColors(bool enabled) |
239 | { |
240 | myTIA->flushLineCache(); |
241 | myDebugEnabled = enabled; |
242 | applyColors(); |
243 | } |
244 | |
245 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
246 | void Player::applyColorLoss() |
247 | { |
248 | myTIA->flushLineCache(); |
249 | applyColors(); |
250 | } |
251 | |
252 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
253 | void Player::setInvertedPhaseClock(bool enable) |
254 | { |
255 | myUseInvertedPhaseClock = enable; |
256 | } |
257 | |
258 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
259 | void Player::startMovement() |
260 | { |
261 | isMoving = true; |
262 | } |
263 | |
264 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
265 | void Player::nextLine() |
266 | { |
267 | if (!myIsRendering || myRenderCounter < myRenderCounterTripPoint) |
268 | collision = myCollisionMaskDisabled; |
269 | else |
270 | collision = (myPattern & (1 << mySampleCounter)) ? myCollisionMaskEnabled : myCollisionMaskDisabled; |
271 | } |
272 | |
273 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
274 | void Player::shufflePatterns() |
275 | { |
276 | const uInt8 oldPatternOld = myPatternOld; |
277 | |
278 | myPatternOld = myPatternNew; |
279 | |
280 | if (myIsDelaying && myPatternOld != oldPatternOld) { |
281 | myTIA->flushLineCache(); |
282 | updatePattern(); |
283 | } |
284 | } |
285 | |
286 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
287 | uInt8 Player::getRespClock() const |
288 | { |
289 | switch (myDivider) |
290 | { |
291 | case 1: |
292 | return (myCounter + TIAConstants::H_PIXEL - 5) % TIAConstants::H_PIXEL; |
293 | |
294 | case 2: |
295 | return (myCounter + TIAConstants::H_PIXEL - 9) % TIAConstants::H_PIXEL; |
296 | |
297 | case 4: |
298 | return (myCounter + TIAConstants::H_PIXEL - 12) % TIAConstants::H_PIXEL; |
299 | |
300 | default: |
301 | throw runtime_error("invalid width"); |
302 | } |
303 | } |
304 | |
305 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
306 | void Player::setGRPOld(uInt8 pattern) |
307 | { |
308 | myTIA->flushLineCache(); |
309 | |
310 | myPatternOld = pattern; |
311 | updatePattern(); |
312 | } |
313 | |
314 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
315 | void Player::updatePattern() |
316 | { |
317 | if (myIsSuppressed) { |
318 | myPattern = 0; |
319 | return; |
320 | } |
321 | |
322 | myPattern = myIsDelaying ? myPatternOld : myPatternNew; |
323 | |
324 | if (!myIsReflected) { |
325 | myPattern = ( |
326 | ((myPattern & 0x01) << 7) | |
327 | ((myPattern & 0x02) << 5) | |
328 | ((myPattern & 0x04) << 3) | |
329 | ((myPattern & 0x08) << 1) | |
330 | ((myPattern & 0x10) >> 1) | |
331 | ((myPattern & 0x20) >> 3) | |
332 | ((myPattern & 0x40) >> 5) | |
333 | ((myPattern & 0x80) >> 7) |
334 | ); |
335 | } |
336 | |
337 | if (myIsRendering && myRenderCounter >= myRenderCounterTripPoint) { |
338 | collision = (myPattern & (1 << mySampleCounter)) ? myCollisionMaskEnabled : myCollisionMaskDisabled; |
339 | myTIA->scheduleCollisionUpdate(); |
340 | } |
341 | } |
342 | |
343 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
344 | void Player::setDivider(uInt8 divider) |
345 | { |
346 | myDivider = divider; |
347 | myRenderCounterTripPoint = divider == 1 ? 0 : 1; |
348 | } |
349 | |
350 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
351 | void Player::applyColors() |
352 | { |
353 | if (!myDebugEnabled) |
354 | { |
355 | if (myTIA->colorLossActive()) myObjectColor |= 0x01; |
356 | else myObjectColor &= 0xfe; |
357 | myColor = myObjectColor; |
358 | } |
359 | else |
360 | myColor = myDebugColor; |
361 | } |
362 | |
363 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
364 | uInt8 Player::getPosition() const |
365 | { |
366 | // Wide players are shifted by one pixel to the right |
367 | const uInt8 shift = myDivider == 1 ? 0 : 1; |
368 | |
369 | // position = |
370 | // current playfield x + |
371 | // (current counter - 156 (the decode clock of copy 0)) + |
372 | // clock count after decode until first pixel + |
373 | // shift (accounts for wide player shift) + |
374 | // 1 (it'll take another cycle after the decode for the render counter to start ticking) |
375 | // |
376 | // The result may be negative, so we add TIA::H_PIXEL and do the modulus -> 317 = 156 + TIA::H_PIXEL + 1 |
377 | // |
378 | // Mind the sign of renderCounterOffset: it's defined negative above |
379 | return (317 - myCounter - Count::renderCounterOffset + shift + myTIA->getPosition()) % TIAConstants::H_PIXEL; |
380 | } |
381 | |
382 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
383 | void Player::setPosition(uInt8 newPosition) |
384 | { |
385 | myTIA->flushLineCache(); |
386 | |
387 | const uInt8 shift = myDivider == 1 ? 0 : 1; |
388 | |
389 | // See getPosition for an explanation |
390 | myCounter = (317 - newPosition - Count::renderCounterOffset + shift + myTIA->getPosition()) % TIAConstants::H_PIXEL; |
391 | } |
392 | |
393 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
394 | bool Player::save(Serializer& out) const |
395 | { |
396 | try |
397 | { |
398 | out.putInt(collision); |
399 | out.putInt(myCollisionMaskDisabled); |
400 | out.putInt(myCollisionMaskEnabled); |
401 | |
402 | out.putByte(myColor); |
403 | out.putByte(myObjectColor); out.putByte(myDebugColor); |
404 | out.putBool(myDebugEnabled); |
405 | |
406 | out.putBool(myIsSuppressed); |
407 | |
408 | out.putByte(myHmmClocks); |
409 | out.putByte(myCounter); |
410 | out.putBool(isMoving); |
411 | |
412 | out.putBool(myIsRendering); |
413 | out.putByte(myRenderCounter); |
414 | out.putByte(myRenderCounterTripPoint); |
415 | out.putByte(myDivider); |
416 | out.putByte(myDividerPending); |
417 | out.putByte(mySampleCounter); |
418 | out.putByte(myDividerChangeCounter); |
419 | |
420 | out.putByte(myDecodesOffset); |
421 | |
422 | out.putByte(myPatternOld); |
423 | out.putByte(myPatternNew); |
424 | out.putByte(myPattern); |
425 | |
426 | out.putBool(myIsReflected); |
427 | out.putBool(myIsDelaying); |
428 | out.putBool(myInvertedPhaseClock); |
429 | } |
430 | catch(...) |
431 | { |
432 | cerr << "ERROR: TIA_Player::save"<< endl; |
433 | return false; |
434 | } |
435 | |
436 | return true; |
437 | } |
438 | |
439 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
440 | bool Player::load(Serializer& in) |
441 | { |
442 | try |
443 | { |
444 | collision = in.getInt(); |
445 | myCollisionMaskDisabled = in.getInt(); |
446 | myCollisionMaskEnabled = in.getInt(); |
447 | |
448 | myColor = in.getByte(); |
449 | myObjectColor = in.getByte(); myDebugColor = in.getByte(); |
450 | myDebugEnabled = in.getBool(); |
451 | |
452 | myIsSuppressed = in.getBool(); |
453 | |
454 | myHmmClocks = in.getByte(); |
455 | myCounter = in.getByte(); |
456 | isMoving = in.getBool(); |
457 | |
458 | myIsRendering = in.getBool(); |
459 | myRenderCounter = in.getByte(); |
460 | myRenderCounterTripPoint = in.getByte(); |
461 | myDivider = in.getByte(); |
462 | myDividerPending = in.getByte(); |
463 | mySampleCounter = in.getByte(); |
464 | myDividerChangeCounter = in.getByte(); |
465 | |
466 | myDecodesOffset = in.getByte(); |
467 | myDecodes = DrawCounterDecodes::get().playerDecodes()[myDecodesOffset]; |
468 | |
469 | myPatternOld = in.getByte(); |
470 | myPatternNew = in.getByte(); |
471 | myPattern = in.getByte(); |
472 | |
473 | myIsReflected = in.getBool(); |
474 | myIsDelaying = in.getBool(); |
475 | myInvertedPhaseClock = in.getBool(); |
476 | |
477 | applyColors(); |
478 | } |
479 | catch(...) |
480 | { |
481 | cerr << "ERROR: TIA_Player::load"<< endl; |
482 | return false; |
483 | } |
484 | |
485 | return true; |
486 | } |
487 |