BizHawk/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.Input.cs

590 lines
15 KiB
C#

using System;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Sega.MasterSystem
{
public partial class SMS
{
public static readonly ControllerDefinition SmsController = new ControllerDefinition
{
Name = "SMS Controller",
BoolButtons =
{
"Reset", "Pause",
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 B1", "P1 B2",
"P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 B1", "P2 B2"
}
};
public static readonly ControllerDefinition GGController = new ControllerDefinition
{
Name = "GG Controller",
BoolButtons =
{
"Reset",
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 B1", "P1 B2", "P1 Start"
}
};
public static readonly ControllerDefinition SMSPaddleController = new ControllerDefinition
{
Name = "SMS Paddle Controller",
BoolButtons =
{
"Reset", "Pause",
"P1 Left", "P1 Right", "P1 B1",
"P2 Left", "P2 Right", "P2 B1",
},
FloatControls =
{
"P1 Paddle",
"P2 Paddle"
},
FloatRanges =
{
new ControllerDefinition.FloatRange(0, 128, 255),
new ControllerDefinition.FloatRange(0, 128, 255)
}
};
public static readonly ControllerDefinition SMSLightPhaserController = new ControllerDefinition
{
Name = "SMS Light Phaser Controller",
BoolButtons =
{
"Reset", "Pause",
"P1 Trigger",
},
FloatControls =
{
"P1 X", "P1 Y",
},
FloatRanges =
{
new ControllerDefinition.FloatRange(0, 64, 127),
new ControllerDefinition.FloatRange(0, 500, 1000)
}
};
public static readonly ControllerDefinition SMSSportsPadController = new ControllerDefinition
{
Name = "SMS Sports Pad Controller",
BoolButtons =
{
"Reset", "Pause",
"P1 Left", "P1 Right", "P1 Up", "P1 Down", "P1 B1", "P1 B2",
"P2 Left", "P2 Right", "P2 Up", "P2 Down", "P2 B1", "P2 B2"
},
FloatControls =
{
"P1 X", "P1 Y",
"P2 X", "P2 Y"
},
FloatRanges =
{
new ControllerDefinition.FloatRange(-64, 0, 63),
new ControllerDefinition.FloatRange(-64, 0, 63),
new ControllerDefinition.FloatRange(-64, 0, 63),
new ControllerDefinition.FloatRange(-64, 0, 63)
}
};
const int PaddleMin = 0;
const int PaddleMax = 255;
const int SportsPadMin = -64;
const int SportsPadMax = 63;
// The paddles and sports pads have data select states
bool Controller1SelectHigh = true;
bool Controller2SelectHigh = true;
bool LatchLightPhaser = false;
// further state value for sports pad, may be useful for other controllers in future
int Controller1State = 3;
int Controller2State = 3;
int ControllerTick = 0; // for timing in japan
private byte ReadControls1()
{
InputCallbacks.Call();
_lagged = false;
byte value = 0xFF;
switch (SyncSettings.ControllerType)
{
case "Paddle":
{
// use analog values from a controller, see http://www.smspower.org/Development/Paddle
int paddle1Pos;
if (_controller.IsPressed("P1 Left"))
paddle1Pos = PaddleMin;
else if (_controller.IsPressed("P1 Right"))
paddle1Pos = PaddleMax;
else
paddle1Pos = (int)_controller.GetFloat("P1 Paddle");
int paddle2Pos;
if (_controller.IsPressed("P2 Left"))
paddle2Pos = PaddleMin;
else if (_controller.IsPressed("P2 Right"))
paddle2Pos = PaddleMax;
else
paddle2Pos = (int)_controller.GetFloat("P2 Paddle");
PresetControllerState(1);
// Hard-wired together?
Controller2SelectHigh = Controller1SelectHigh;
if (Controller1SelectHigh)
{
if ((paddle1Pos & 0x10) == 0) value &= 0xFE;
if ((paddle1Pos & 0x20) == 0) value &= 0xFD;
if ((paddle1Pos & 0x40) == 0) value &= 0xFB;
if ((paddle1Pos & 0x80) == 0) value &= 0xF7;
}
else
{
if ((paddle1Pos & 0x01) == 0) value &= 0xFE;
if ((paddle1Pos & 0x02) == 0) value &= 0xFD;
if ((paddle1Pos & 0x04) == 0) value &= 0xFB;
if ((paddle1Pos & 0x08) == 0) value &= 0xF7;
}
if (_controller.IsPressed("P1 B1")) value &= 0xEF;
if (!Controller1SelectHigh) value &= 0xDF;
if (Controller2SelectHigh)
{
if ((paddle2Pos & 0x10) == 0) value &= 0xBF;
if ((paddle2Pos & 0x20) == 0) value &= 0x7F;
}
else
{
if ((paddle2Pos & 0x01) == 0) value &= 0xBF;
if ((paddle2Pos & 0x02) == 0) value &= 0x7F;
}
PostsetControllerState(1);
}
break;
case "Light Phaser":
if (_controller.IsPressed("P1 Trigger")) value &= 0xEF;
break;
case "Sports Pad":
{
int p1X;
if (_controller.IsPressed("P1 Left"))
p1X = SportsPadMin;
else if (_controller.IsPressed("P1 Right"))
p1X = SportsPadMax;
else
p1X = (int)_controller.GetFloat("P1 X");
int p1Y;
if (_controller.IsPressed("P1 Up"))
p1Y = SportsPadMin;
else if (_controller.IsPressed("P1 Down"))
p1Y = SportsPadMax;
else
p1Y = (int)_controller.GetFloat("P1 Y");
int p2X;
if (_controller.IsPressed("P2 Left"))
p2X = SportsPadMin;
else if (_controller.IsPressed("P2 Right"))
p2X = SportsPadMax;
else
p2X = (int)_controller.GetFloat("P2 X");
int p2Y;
if (_controller.IsPressed("P2 Up"))
p2Y = SportsPadMin;
else if (_controller.IsPressed("P2 Down"))
p2Y = SportsPadMax;
else
p2Y = (int)_controller.GetFloat("P2 Y");
if(_region == "Japan")
{
p1X += 128;
p1Y += 128;
p2X += 128;
p2Y += 128;
} else
{
p1X *= -1;
p1Y *= -1;
p2X *= -1;
p2Y *= -1;
}
PresetControllerState(1);
// advance state
if (Controller1SelectHigh && (Controller1State % 2 == 0))
{
++Controller1State;
}
else if (!Controller1SelectHigh && (Controller1State % 2 == 1))
{
if (++Controller1State == (_region == "Japan" ? 6 : 4))
Controller1State = 0;
}
if (Controller2SelectHigh && (Controller2State % 2 == 0))
{
++Controller2State;
}
else if (!Controller2SelectHigh && (Controller2State % 2 == 1))
{
if (++Controller2State == (_region == "Japan" ? 6 : 4))
Controller2State = 0;
}
switch (Controller1State)
{
case 0:
if ((p1X & 0x10) == 0) value &= 0xFE;
if ((p1X & 0x20) == 0) value &= 0xFD;
if ((p1X & 0x40) == 0) value &= 0xFB;
if ((p1X & 0x80) == 0) value &= 0xF7;
break;
case 1:
if ((p1X & 0x01) == 0) value &= 0xFE;
if ((p1X & 0x02) == 0) value &= 0xFD;
if ((p1X & 0x04) == 0) value &= 0xFB;
if ((p1X & 0x08) == 0) value &= 0xF7;
break;
case 2:
if ((p1Y & 0x10) == 0) value &= 0xFE;
if ((p1Y & 0x20) == 0) value &= 0xFD;
if ((p1Y & 0x40) == 0) value &= 0xFB;
if ((p1Y & 0x80) == 0) value &= 0xF7;
break;
case 3:
if ((p1Y & 0x01) == 0) value &= 0xFE;
if ((p1Y & 0x02) == 0) value &= 0xFD;
if ((p1Y & 0x04) == 0) value &= 0xFB;
if ((p1Y & 0x08) == 0) value &= 0xF7;
break;
case 4:
// specific to Japan: sync via TR
value &= 0xDF;
break;
case 5:
// specific to Japan: buttons
if (_controller.IsPressed("P1 B1")) value &= 0xFE;
if (_controller.IsPressed("P1 B2")) value &= 0xFD;
break;
}
if (_region != "Japan")
{
// Buttons like normal in Export
if (_controller.IsPressed("P1 B1")) value &= 0xEF;
if (_controller.IsPressed("P1 B2")) value &= 0xDF;
}
else
{
// In Japan, it contains selectHigh
if (!Controller1SelectHigh) value &= 0xEF;
}
switch (Controller2State)
{
case 0:
if ((p2X & 0x10) == 0) value &= 0xBF;
if ((p2X & 0x20) == 0) value &= 0x7F;
break;
case 1:
if ((p2X & 0x01) == 0) value &= 0xBF;
if ((p2X & 0x02) == 0) value &= 0x7F;
break;
case 2:
if ((p2Y & 0x10) == 0) value &= 0xBF;
if ((p2Y & 0x20) == 0) value &= 0x7F;
break;
case 3:
if ((p2Y & 0x01) == 0) value &= 0xBF;
if ((p2Y & 0x02) == 0) value &= 0x7F;
break;
case 5:
// specific to Japan: buttons
if (_controller.IsPressed("P2 B1")) value &= 0xBF;
if (_controller.IsPressed("P2 B2")) value &= 0x7F;
break;
}
PostsetControllerState(1);
}
break;
default:
// Normal controller
if (_controller.IsPressed("P1 Up")) value &= 0xFE;
if (_controller.IsPressed("P1 Down")) value &= 0xFD;
if (_controller.IsPressed("P1 Left")) value &= 0xFB;
if (_controller.IsPressed("P1 Right")) value &= 0xF7;
if (_controller.IsPressed("P1 B1")) value &= 0xEF;
if (_controller.IsPressed("P1 B2")) value &= 0xDF;
if (_controller.IsPressed("P2 Up")) value &= 0xBF;
if (_controller.IsPressed("P2 Down")) value &= 0x7F;
break;
}
return value;
}
private byte ReadControls2()
{
InputCallbacks.Call();
_lagged = false;
byte value = 0xFF;
switch (SyncSettings.ControllerType)
{
case "Paddle":
{
// use analog values from a controller, see http://www.smspower.org/Development/Paddle
int paddle2Pos;
if (_controller.IsPressed("P2 Left"))
paddle2Pos = PaddleMin;
else if (_controller.IsPressed("P2 Right"))
paddle2Pos = PaddleMax;
else
paddle2Pos = (int)_controller.GetFloat("P2 Paddle");
PresetControllerState(2);
if (Controller2SelectHigh)
{
if ((paddle2Pos & 0x40) == 0) value &= 0xFE;
if ((paddle2Pos & 0x80) == 0) value &= 0xFD;
}
else
{
if ((paddle2Pos & 0x04) == 0) value &= 0xFE;
if ((paddle2Pos & 0x08) == 0) value &= 0xFD;
}
if (_controller.IsPressed("P2 B1")) value &= 0xFB;
if (!Controller2SelectHigh) value &= 0xF7;
PostsetControllerState(2);
}
break;
case "Light Phaser":
if (LatchLightPhaser)
{
value &= 0xBF;
LatchLightPhaser = false;
}
break;
case "Sports Pad":
{
int p2X;
if (_controller.IsPressed("P2 Left"))
p2X = SportsPadMin;
else if (_controller.IsPressed("P2 Right"))
p2X = SportsPadMax;
else
p2X = (int)_controller.GetFloat("P2 X");
int p2Y;
if (_controller.IsPressed("P2 Down"))
p2Y = SportsPadMin;
else if (_controller.IsPressed("P2 Up"))
p2Y = SportsPadMax;
else
p2Y = (int)_controller.GetFloat("P2 Y");
if (_region == "Japan")
{
p2X += 128;
p2Y += 128;
}
else
{
p2X *= -1;
p2Y *= -1;
}
PresetControllerState(2);
if (Controller2SelectHigh && (Controller2State % 2 == 0))
{
++Controller2State;
}
else if (!Controller2SelectHigh && (Controller2State % 2 == 1))
{
if (++Controller2State == (_region == "Japan" ? 6 : 4))
Controller2State = 0;
}
switch (Controller2State)
{
case 0:
if ((p2X & 0x40) == 0) value &= 0xFE;
if ((p2X & 0x80) == 0) value &= 0xFD;
break;
case 1:
if ((p2X & 0x04) == 0) value &= 0xFE;
if ((p2X & 0x08) == 0) value &= 0xFD;
break;
case 2:
if ((p2Y & 0x40) == 0) value &= 0xFE;
if ((p2Y & 0x80) == 0) value &= 0xFD;
break;
case 3:
if ((p2Y & 0x04) == 0) value &= 0xFE;
if ((p2Y & 0x08) == 0) value &= 0xFD;
break;
}
if (_region != "Japan")
{
// Buttons like normal in Export
if (_controller.IsPressed("P2 B1")) value &= 0xFB;
if (_controller.IsPressed("P2 B2")) value &= 0xF7;
}
else
{
if (!Controller2SelectHigh) value &= 0xF7;
}
PostsetControllerState(2);
}
break;
default:
// Normal controller
if (_controller.IsPressed("P2 Left")) value &= 0xFE;
if (_controller.IsPressed("P2 Right")) value &= 0xFD;
if (_controller.IsPressed("P2 B1")) value &= 0xFB;
if (_controller.IsPressed("P2 B2")) value &= 0xF7;
break;
}
if (_controller.IsPressed("Reset")) value &= 0xEF;
if ((Port3F & 0x0F) == 5)
{
if (_region == "Japan")
{
value &= 0x3F;
}
else // US / Europe
{
if (Port3F >> 4 == 0x0F)
value |= 0xC0;
else
value &= 0x3F;
}
}
return value;
}
internal void ProcessLineControls()
{
const int phaserRadius = 4;
// specifically lightgun needs to do things on a per-line basis
if (SyncSettings.ControllerType == "Light Phaser")
{
byte phaserX = (byte)(_controller.GetFloat("P1 X") + 20);
int phaserY = (int)_controller.GetFloat("P1 Y");
int scanline = Vdp.ScanLine;
if (!LatchLightPhaser && phaserY >= scanline - phaserRadius && phaserY <= scanline + phaserRadius)
{
if (scanline >= Vdp.FrameHeight)
return;
// latch HCounter via TH
Vdp.HCounter = phaserX;
LatchLightPhaser = true;
}
else
{
LatchLightPhaser = false;
}
}
}
byte ReadPort0()
{
if (IsGameGear == false)
{
return 0xFF;
}
byte value = 0xFF;
if ((_controller.IsPressed("Pause") && !IsGameGear) ||
(_controller.IsPressed("P1 Start") && IsGameGear))
{
value ^= 0x80;
}
if (RegionStr == "Japan")
{
value ^= 0x40;
}
return value;
}
private void PresetControllerState(int pin)
{
// The 3F port's TH slot is also used on games in some games in Export BIOS to clock the paddle state
// Re: the paddle: Yes it's silly considering the paddle was never released outside Japan but the games think otherwise
if (_region != "Japan")
{
if ((Port3F & 0x02) == 0x00)
{
Controller1SelectHigh = (Port3F & 0x20) != 0;
// resync
Controller2State = 3;
}
if ((Port3F & 0x08) == 0x00)
{
Controller2SelectHigh = (Port3F & 0x80) != 0;
// resync
Controller1State = 3;
}
}
}
private void PostsetControllerState(int pin)
{
// for the benefit of the Japan region
if (_region == "Japan" && (++ControllerTick) == 2)
{
ControllerTick = 0;
if (pin == 1)
{
Controller1SelectHigh ^= true;
}
else
{
Controller1SelectHigh = false;
Controller2SelectHigh ^= true;
}
}
}
}
}