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 "Missile.hxx" |
19 | #include "DrawCounterDecodes.hxx" |
20 | #include "TIA.hxx" |
21 | |
22 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
23 | Missile::Missile(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 Missile::reset() |
35 | { |
36 | myDecodes = DrawCounterDecodes::get().missileDecodes()[myDecodesOffset]; |
37 | myIsEnabled = false; |
38 | myEnam = false; |
39 | myResmp = 0; |
40 | myHmmClocks = 0; |
41 | myCounter = 0; |
42 | isMoving = false; |
43 | myWidth = 1; |
44 | myEffectiveWidth = 1; |
45 | myIsRendering = false; |
46 | myIsVisible = false; |
47 | myRenderCounter = 0; |
48 | myColor = myObjectColor = myDebugColor = 0; |
49 | myDebugEnabled = false; |
50 | collision = myCollisionMaskDisabled; |
51 | myIsEnabled = false; |
52 | myInvertedPhaseClock = false; |
53 | myUseInvertedPhaseClock = false; |
54 | } |
55 | |
56 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
57 | void Missile::enam(uInt8 value) |
58 | { |
59 | const auto oldEnam = myEnam; |
60 | |
61 | myEnam = (value & 0x02) > 0; |
62 | |
63 | if (oldEnam != myEnam) { |
64 | myTIA->flushLineCache(); |
65 | |
66 | updateEnabled(); |
67 | } |
68 | } |
69 | |
70 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
71 | void Missile::hmm(uInt8 value) |
72 | { |
73 | myHmmClocks = (value >> 4) ^ 0x08; |
74 | } |
75 | |
76 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
77 | void Missile::resm(uInt8 counter, bool hblank) |
78 | { |
79 | myCounter = counter; |
80 | |
81 | if (myIsRendering) { |
82 | if (myRenderCounter < 0) { |
83 | myRenderCounter = Count::renderCounterOffset + (counter - 157); |
84 | |
85 | } else { |
86 | // The following is an effective description of the behavior of missile width after a |
87 | // RESMx during draw. It would be much simpler without the HBLANK cases :) |
88 | |
89 | switch (myWidth) { |
90 | case 8: |
91 | myRenderCounter = (counter - 157) + ((myRenderCounter >= 4) ? 4 : 0); |
92 | break; |
93 | |
94 | case 4: |
95 | myRenderCounter = (counter - 157); |
96 | break; |
97 | |
98 | case 2: |
99 | if (hblank) myIsRendering = myRenderCounter > 1; |
100 | else if (myRenderCounter == 0) ++myRenderCounter; |
101 | |
102 | break; |
103 | |
104 | default: |
105 | if (hblank) myIsRendering = myRenderCounter > 0; |
106 | |
107 | break; |
108 | } |
109 | } |
110 | } |
111 | } |
112 | |
113 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
114 | void Missile::resmp(uInt8 value, const Player& player) |
115 | { |
116 | const uInt8 resmp = value & 0x02; |
117 | |
118 | if (resmp == myResmp) return; |
119 | |
120 | myTIA->flushLineCache(); |
121 | |
122 | myResmp = resmp; |
123 | |
124 | if (!myResmp) |
125 | myCounter = player.getRespClock(); |
126 | |
127 | updateEnabled(); |
128 | } |
129 | |
130 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
131 | void Missile::toggleCollisions(bool enabled) |
132 | { |
133 | myCollisionMaskEnabled = enabled ? 0xFFFF : (0x8000 | myCollisionMaskDisabled); |
134 | } |
135 | |
136 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
137 | void Missile::toggleEnabled(bool enabled) |
138 | { |
139 | myIsSuppressed = !enabled; |
140 | updateEnabled(); |
141 | } |
142 | |
143 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
144 | void Missile::nusiz(uInt8 value) |
145 | { |
146 | static constexpr uInt8 ourWidths[] = { 1, 2, 4, 8 }; |
147 | |
148 | myDecodesOffset = value & 0x07; |
149 | myWidth = ourWidths[(value & 0x30) >> 4]; |
150 | myDecodes = DrawCounterDecodes::get().missileDecodes()[myDecodesOffset]; |
151 | |
152 | if (myIsRendering && myRenderCounter >= myWidth) |
153 | myIsRendering = false; |
154 | } |
155 | |
156 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
157 | void Missile::startMovement() |
158 | { |
159 | isMoving = true; |
160 | } |
161 | |
162 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
163 | void Missile::nextLine() |
164 | { |
165 | myIsVisible = myIsRendering && (myRenderCounter >= 0); |
166 | collision = (myIsVisible && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled; |
167 | } |
168 | |
169 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
170 | void Missile::setColor(uInt8 color) |
171 | { |
172 | if (color != myObjectColor && myIsEnabled) myTIA->flushLineCache(); |
173 | |
174 | myObjectColor = color; |
175 | applyColors(); |
176 | } |
177 | |
178 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
179 | void Missile::setDebugColor(uInt8 color) |
180 | { |
181 | myTIA->flushLineCache(); |
182 | myDebugColor = color; |
183 | applyColors(); |
184 | } |
185 | |
186 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
187 | void Missile::enableDebugColors(bool enabled) |
188 | { |
189 | myTIA->flushLineCache(); |
190 | myDebugEnabled = enabled; |
191 | applyColors(); |
192 | } |
193 | |
194 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
195 | void Missile::applyColorLoss() |
196 | { |
197 | myTIA->flushLineCache(); |
198 | applyColors(); |
199 | } |
200 | |
201 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
202 | void Missile::setInvertedPhaseClock(bool enable) |
203 | { |
204 | myUseInvertedPhaseClock = enable; |
205 | } |
206 | |
207 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
208 | void Missile::updateEnabled() |
209 | { |
210 | myIsEnabled = !myIsSuppressed && myEnam && !myResmp; |
211 | |
212 | collision = (myIsVisible && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled; |
213 | myTIA->scheduleCollisionUpdate(); |
214 | } |
215 | |
216 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
217 | void Missile::applyColors() |
218 | { |
219 | if (!myDebugEnabled) |
220 | { |
221 | if (myTIA->colorLossActive()) myObjectColor |= 0x01; |
222 | else myObjectColor &= 0xfe; |
223 | myColor = myObjectColor; |
224 | } |
225 | else |
226 | myColor = myDebugColor; |
227 | } |
228 | |
229 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
230 | uInt8 Missile::getPosition() const |
231 | { |
232 | // position = |
233 | // current playfield x + |
234 | // (current counter - 156 (the decode clock of copy 0)) + |
235 | // clock count after decode until first pixel + |
236 | // 1 (it'll take another cycle after the decode for the rendter counter to start ticking) |
237 | // |
238 | // The result may be negative, so we add 160 and do the modulus |
239 | // |
240 | // Mind the sign of renderCounterOffset: it's defined negative above |
241 | return (317 - myCounter - Count::renderCounterOffset + myTIA->getPosition()) % TIAConstants::H_PIXEL; |
242 | } |
243 | |
244 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
245 | void Missile::setPosition(uInt8 newPosition) |
246 | { |
247 | myTIA->flushLineCache(); |
248 | |
249 | // See getPosition for an explanation |
250 | myCounter = (317 - newPosition - Count::renderCounterOffset + myTIA->getPosition()) % TIAConstants::H_PIXEL; |
251 | } |
252 | |
253 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
254 | bool Missile::save(Serializer& out) const |
255 | { |
256 | try |
257 | { |
258 | out.putInt(collision); |
259 | out.putInt(myCollisionMaskDisabled); |
260 | out.putInt(myCollisionMaskEnabled); |
261 | |
262 | out.putBool(myIsEnabled); |
263 | out.putBool(myIsSuppressed); |
264 | out.putBool(myEnam); |
265 | out.putByte(myResmp); |
266 | |
267 | out.putByte(myHmmClocks); |
268 | out.putByte(myCounter); |
269 | out.putBool(isMoving); |
270 | out.putByte(myWidth); |
271 | out.putByte(myEffectiveWidth); |
272 | |
273 | out.putBool(myIsVisible); |
274 | out.putBool(myIsRendering); |
275 | out.putByte(myRenderCounter); |
276 | |
277 | out.putByte(myDecodesOffset); |
278 | |
279 | out.putByte(myColor); |
280 | out.putByte(myObjectColor); out.putByte(myDebugColor); |
281 | out.putBool(myDebugEnabled); |
282 | out.putBool(myInvertedPhaseClock); |
283 | } |
284 | catch(...) |
285 | { |
286 | cerr << "ERROR: TIA_Missile::save" << endl; |
287 | return false; |
288 | } |
289 | |
290 | return true; |
291 | } |
292 | |
293 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
294 | bool Missile::load(Serializer& in) |
295 | { |
296 | try |
297 | { |
298 | collision = in.getInt(); |
299 | myCollisionMaskDisabled = in.getInt(); |
300 | myCollisionMaskEnabled = in.getInt(); |
301 | |
302 | myIsEnabled = in.getBool(); |
303 | myIsSuppressed = in.getBool(); |
304 | myEnam = in.getBool(); |
305 | myResmp = in.getByte(); |
306 | |
307 | myHmmClocks = in.getByte(); |
308 | myCounter = in.getByte(); |
309 | isMoving = in.getBool(); |
310 | myWidth = in.getByte(); |
311 | myEffectiveWidth = in.getByte(); |
312 | |
313 | myIsVisible = in.getBool(); |
314 | myIsRendering = in.getBool(); |
315 | myRenderCounter = in.getByte(); |
316 | |
317 | myDecodesOffset = in.getByte(); |
318 | myDecodes = DrawCounterDecodes::get().missileDecodes()[myDecodesOffset]; |
319 | |
320 | myColor = in.getByte(); |
321 | myObjectColor = in.getByte(); myDebugColor = in.getByte(); |
322 | myDebugEnabled = in.getBool(); |
323 | myInvertedPhaseClock = in.getBool(); |
324 | |
325 | applyColors(); |
326 | } |
327 | catch(...) |
328 | { |
329 | cerr << "ERROR: TIA_Missile::load" << endl; |
330 | return false; |
331 | } |
332 | |
333 | return true; |
334 | } |
335 | |