BizHawk/libsnes/bsnes/snes/controller/superscope/superscope.cpp

155 lines
4.5 KiB
C++
Raw Normal View History

#ifdef CONTROLLER_CPP
//The Super Scope is a light-gun: it detects the CRT beam cannon position,
//and latches the counters by toggling iobit. This only works on controller
//port 2, as iobit there is connected to the PPU H/V counter latch.
//(PIO $4201.d7)
//It is obviously not possible to perfectly simulate an IR light detecting
//a CRT beam cannon, hence this class will read the PPU raster counters.
//A Super Scope can still technically be used in port 1, however it would
//require manual polling of PIO ($4201.d6) to determine when iobit was written.
//Note that no commercial game ever utilizes a Super Scope in port 1.
void SuperScope::enter() {
unsigned prev = 0;
while(true) {
unsigned next = cpu.vcounter() * 1364 + cpu.hcounter();
if(offscreen == false) {
unsigned target = y * 1364 + (x + 24) * 4;
if(next >= target && prev < target) {
//CRT raster detected, toggle iobit to latch counters
iobit(0);
iobit(1);
}
}
if(next < prev) {
//Vcounter wrapped back to zero; update cursor coordinates for start of new frame
int nx = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::X);
int ny = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::Y);
nx += x;
ny += y;
x = max(-16, min(256 + 16, nx));
y = max(-16, min(240 + 16, ny));
offscreen = (x < 0 || y < 0 || x >= 256 || y >= (ppu.overscan() ? 240 : 225));
}
prev = next;
step(2);
}
}
uint2 SuperScope::data() {
if(counter >= 8) return 1;
if(counter == 0) {
//turbo is a switch; toggle is edge sensitive
bool newturbo = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::Turbo);
if(newturbo && !turbo) {
turbo = !turbo; //toggle state
turbolock = true;
} else {
turbolock = false;
}
//trigger is a button
//if turbo is active, trigger is level sensitive; otherwise, it is edge sensitive
trigger = false;
bool newtrigger = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::Trigger);
if(newtrigger && (turbo || !triggerlock)) {
trigger = true;
triggerlock = true;
} else if(!newtrigger) {
triggerlock = false;
}
//cursor is a button; it is always level sensitive
cursor = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::Cursor);
//pause is a button; it is always edge sensitive
pause = false;
bool newpause = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::Pause);
if(newpause && !pauselock) {
pause = true;
pauselock = true;
} else if(!newpause) {
pauselock = false;
}
offscreen = (x < 0 || y < 0 || x >= 256 || y >= (ppu.overscan() ? 240 : 225));
}
switch(counter++) {
case 0: return offscreen ? 0 : trigger;
case 1: return cursor;
case 2: return turbo;
case 3: return pause;
case 4: return 0;
case 5: return 0;
case 6: return offscreen;
case 7: return 0; //noise (1 = yes)
}
}
void SuperScope::latch(bool data) {
if(latched == data) return;
latched = data;
2012-09-04 20:23:18 +00:00
counter = 0;
}
void SuperScope::serialize(serializer& s) {
Processor::serialize(s);
//Save block.
unsigned char block[Controller::SaveSize] = {0};
block[0] = latched ? 1 : 0;
block[1] = counter;
block[2] = trigger ? 1 : 0;
block[3] = cursor ? 1 : 0;
block[4] = turbo ? 1 : 0;
block[5] = pause ? 1 : 0;
block[6] = offscreen ? 1 : 0;
block[7] = (unsigned short)x >> 8;
block[8] = (unsigned short)x;
block[9] = (unsigned short)y >> 8;
block[10] = (unsigned short)y;
s.array(block, Controller::SaveSize);
if(s.mode() == nall::serializer::Load) {
latched = (block[0] != 0);
counter = block[1];
trigger = (block[2] != 0);
cursor = (block[3] != 0);
turbo = (block[4] != 0);
pause = (block[5] != 0);
offscreen = (block[6] != 0);
x = (short)(((unsigned short)block[7] << 8) | (unsigned short)block[8]);
y = (short)(((unsigned short)block[9] << 8) | (unsigned short)block[10]);
}
}
SuperScope::SuperScope(bool port) : Controller(port) {
create(Controller::Enter, 21477272);
latched = 0;
counter = 0;
//center cursor onscreen
x = 256 / 2;
y = 240 / 2;
trigger = false;
cursor = false;
turbo = false;
pause = false;
offscreen = false;
turbolock = false;
triggerlock = false;
pauselock = false;
}
#endif