mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 09:39:33 +01:00
Zuma: Correct aiming algorithm to behave better with analog sticks
Zuma is a pretty bad port from PC and has really weird aiming math, clearly partially derived from the mouse aiming code. - Each stick axis got its own dead zone (7864 of 32767), so the angle bent towards the nearest axis, and at half deflection anything within about 15 degrees of an axis was on it. - The aim puts a cursor 450 pixels out along the stick and points the frog at it, but only moves a cursor coordinate when that axis is non-zero. With an axis in its dead zone, or the stick straight along the other one, the frog pointed at wherever the cursor had last been: full right after a diagonal aimed 27 degrees off. - The axes are read as 127 minus the PSP's value, so a stick at rest reads -1, which the old dead zone hid. Two hooks: one gives the left stick a circular dead zone of the same size, centred on 128, once the game has stored its floats; the other moves a cursor coordinate that wasn't updated to the frog's own. Reading the angle the game sets for 24 directions at full, 50% and 45% deflection, it now matches the stick's to within 0.6 degrees, the stick's 8-bit steps. Before, it was off by up to 27 degrees. The aim still ignores the stick below about 43% of its travel: the game's own minimum length on top of the dead zone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a9175126e2
commit
b3c44cba8c
2 files changed
+75
No files matched your search
@@ -1856,6 +1856,77 @@ static int Hook_bejeweled2_draw_dirty_stuff(MIPSState *mips) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Zuma keeps the PC version's XInput-style gamepad code. It scales each stick axis to +-32640 and
|
||||
// gives each its own dead zone, so the stick's angle bends towards the nearest axis, and an axis
|
||||
// inside the dead zone reads exactly 0 (at half deflection, 15 degrees off horizontal is
|
||||
// horizontal). Here, after the floats are stored, the left stick gets a circular dead zone of the
|
||||
// same size instead.
|
||||
static int Hook_zuma_pad_analog(MIPSState *mips) {
|
||||
const u32 func = mips->pc - 0x234;
|
||||
// The hash masks immediates, so check the fields are where we expect.
|
||||
if (Memory::Read_Instruction(func + 0x198).encoding != 0x8FC40070 || // lw a0, 0x70(fp) - left stick x
|
||||
Memory::Read_Instruction(func + 0x1A0).encoding != 0x8FC50078 || // lw a1, 0x78(fp) - left stick y
|
||||
Memory::Read_Instruction(func + 0x1F4).encoding != 0xE6E00000 || // swc1 f0, 0x0(s7)
|
||||
Memory::Read_Instruction(func + 0x208).encoding != 0xE6E00004) { // swc1 f0, 0x4(s7)
|
||||
return 0;
|
||||
}
|
||||
const u32 axes = mips->r[MIPS_REG_FP];
|
||||
const u32 floats = mips->r[MIPS_REG_S7];
|
||||
const u32 pad = mips->r[MIPS_REG_S0];
|
||||
if (!Memory::IsValidRange(axes + 0x70, 0xC) || !Memory::IsValidRange(floats, 8) || !Memory::IsValidRange(pad + 0x514, 4)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The same scaling the game applies before its dead zone, except that it reads each axis as 127
|
||||
// minus the PSP's value, so a stick at rest (128) is -1, which the old dead zone hid.
|
||||
const float x = -255.0f * (float)((s32)Memory::ReadUnchecked_U32(axes + 0x70) + 1);
|
||||
const float y = 255.0f * (float)((s32)Memory::ReadUnchecked_U32(axes + 0x78) + 1);
|
||||
const float deadZone = (float)(s32)Memory::ReadUnchecked_U32(pad + 0x514);
|
||||
const float length = sqrtf(x * x + y * y);
|
||||
float fx = 0.0f;
|
||||
float fy = 0.0f;
|
||||
if (length > deadZone && deadZone >= 0.0f && deadZone < 32767.0f) {
|
||||
const float scale = (std::min(length, 32767.0f) - deadZone) / (32767.0f - deadZone) / length;
|
||||
fx = x * scale;
|
||||
fy = y * scale;
|
||||
}
|
||||
Memory::WriteUnchecked_Float(fx, floats);
|
||||
Memory::WriteUnchecked_Float(fy, floats + 4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Zuma aims the frog at a cursor it puts 450 pixels out along the stick, but only moves each
|
||||
// coordinate of the cursor when that axis is non-zero. With the stick straight up, say, the cursor
|
||||
// keeps whatever x it had, and the frog points there instead. Hooked where the two updates meet:
|
||||
// a coordinate that wasn't moved goes to the frog's own.
|
||||
static int Hook_zuma_aim_cursor(MIPSState *mips) {
|
||||
const u32 func = mips->pc - 0x210;
|
||||
if (Memory::Read_Instruction(func + 0x1A0).encoding != 0xAFA50034 || // sw a1, 0x34(sp) - frog x
|
||||
Memory::Read_Instruction(func + 0x1A4).encoding != 0xAFA40038 || // sw a0, 0x38(sp) - frog y
|
||||
Memory::Read_Instruction(func + 0x1B4).encoding != 0xC7AC0010 || // lwc1 f12, 0x10(sp) - x offset
|
||||
Memory::Read_Instruction(func + 0x1D0).encoding != 0xAC850208 || // sw a1, 0x208(a0) - cursor x
|
||||
Memory::Read_Instruction(func + 0x1F0).encoding != 0xC7AC0014 || // lwc1 f12, 0x14(sp) - y offset
|
||||
Memory::Read_Instruction(func + 0x20C).encoding != 0xAC85020C) { // sw a1, 0x20C(a0) - cursor y
|
||||
return 0;
|
||||
}
|
||||
const u32 sp = mips->r[MIPS_REG_SP];
|
||||
if (!Memory::IsValidRange(sp, 0x3C)) {
|
||||
return 0;
|
||||
}
|
||||
const u32 aim = Memory::ReadUnchecked_U32(sp);
|
||||
const float dx = Memory::ReadUnchecked_Float(sp + 0x10);
|
||||
const float dy = Memory::ReadUnchecked_Float(sp + 0x14);
|
||||
if ((dx == 0.0f) == (dy == 0.0f) || !Memory::IsValidRange(aim + 0x208, 8)) {
|
||||
return 0;
|
||||
}
|
||||
if (dx == 0.0f) {
|
||||
Memory::WriteUnchecked_U32(Memory::ReadUnchecked_U32(sp + 0x34), aim + 0x208);
|
||||
} else {
|
||||
Memory::WriteUnchecked_U32(Memory::ReadUnchecked_U32(sp + 0x38), aim + 0x20C);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// GTA: Vice City Stories (#13200). A vehicle that moves less than 0.005 a frame with its gas pedal up gets
|
||||
// stopped dead, so that parked vehicles don't drift. Planes never set the pedal (the Bovver '64 hovercraft
|
||||
// is one), so where the frame rate is high enough that they cover less than that per frame, they stick to
|
||||
@@ -2028,6 +2099,8 @@ static const ReplacementTableEntry entries[] = {
|
||||
{ "activisionhits_frame_limit", &Hook_activisionhits_frame_limit, 0, REPFLAG_HOOKEXIT, 0 },
|
||||
{ "syphonfilterls_update_trail", &Hook_resistance_update_trail, 0, REPFLAG_HOOKENTER, 0x434 },
|
||||
{ "syphonfilterls_draw_trail", &Hook_syphonfilterls_draw_trail, 0, REPFLAG_HOOKENTER, 0x198 },
|
||||
{ "zuma_pad_analog", &Hook_zuma_pad_analog, 0, REPFLAG_HOOKENTER, 0x234 },
|
||||
{ "zuma_aim_cursor", &Hook_zuma_aim_cursor, 0, REPFLAG_HOOKENTER, 0x210 },
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ static const HardHashTableEntry hardcodedHashes[] = {
|
||||
{ 0x1616ee7052542059, 48, "vtfm_t", },
|
||||
{ 0x16965ca11a4e7dac, 104, "vmmul_q_transp", },
|
||||
{ 0x16afe830a5dd2de2, 40, "vdiv_q", },
|
||||
{ 0x181b963854b3045f, 1736, "zuma_pad_analog", }, // Zuma
|
||||
{ 0x184e834a63a79016, 32, "isnanf", },
|
||||
{ 0x1874ee898c7b9f16, 512, "kokoroconnect_download_frame", }, // Kokoro Connect Yochi Random
|
||||
{ 0x189212bda9c94df1, 736, "atanf", },
|
||||
@@ -195,6 +196,7 @@ static const HardHashTableEntry hardcodedHashes[] = {
|
||||
{ 0x35d3527ff8c22ff2, 56, "matrix_scale_q", },
|
||||
{ 0x368f6cf979709a31, 744, "memmove", }, // Jui Dr. Touma Jotarou
|
||||
{ 0x373ce518eee5a2d2, 20, "matrix300_store_q", },
|
||||
{ 0x38260ad724f4d8f7, 804, "zuma_aim_cursor", }, // Zuma
|
||||
{ 0x3840f5766fada4b1, 592, "dissidia_recordframe_avi", }, // Dissidia (US), Dissidia 012 (US)
|
||||
{ 0x388043e96b0e11fd, 144, "sceGupMaterial", },
|
||||
{ 0x38f19bc3be215acc, 388, "log10f", },
|
||||
|
||||
Reference in new issue
Block a user