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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
23Missile::Missile(uInt32 collisionMask)
24 : myCollisionMaskDisabled(collisionMask),
25 myCollisionMaskEnabled(0xFFFF),
26 myIsSuppressed(false),
27 myDecodesOffset(0),
28 myTIA(nullptr)
29{
30 reset();
31}
32
33// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
34void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
57void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
71void Missile::hmm(uInt8 value)
72{
73 myHmmClocks = (value >> 4) ^ 0x08;
74}
75
76// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
77void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
114void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
131void Missile::toggleCollisions(bool enabled)
132{
133 myCollisionMaskEnabled = enabled ? 0xFFFF : (0x8000 | myCollisionMaskDisabled);
134}
135
136// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
137void Missile::toggleEnabled(bool enabled)
138{
139 myIsSuppressed = !enabled;
140 updateEnabled();
141}
142
143// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
144void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
157void Missile::startMovement()
158{
159 isMoving = true;
160}
161
162// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
163void Missile::nextLine()
164{
165 myIsVisible = myIsRendering && (myRenderCounter >= 0);
166 collision = (myIsVisible && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled;
167}
168
169// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
170void Missile::setColor(uInt8 color)
171{
172 if (color != myObjectColor && myIsEnabled) myTIA->flushLineCache();
173
174 myObjectColor = color;
175 applyColors();
176}
177
178// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
179void Missile::setDebugColor(uInt8 color)
180{
181 myTIA->flushLineCache();
182 myDebugColor = color;
183 applyColors();
184}
185
186// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
187void Missile::enableDebugColors(bool enabled)
188{
189 myTIA->flushLineCache();
190 myDebugEnabled = enabled;
191 applyColors();
192}
193
194// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
195void Missile::applyColorLoss()
196{
197 myTIA->flushLineCache();
198 applyColors();
199}
200
201// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
202void Missile::setInvertedPhaseClock(bool enable)
203{
204 myUseInvertedPhaseClock = enable;
205}
206
207// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
208void Missile::updateEnabled()
209{
210 myIsEnabled = !myIsSuppressed && myEnam && !myResmp;
211
212 collision = (myIsVisible && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled;
213 myTIA->scheduleCollisionUpdate();
214}
215
216// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
217void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
230uInt8 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
245void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
254bool 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
294bool 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