document 2 player cart scroll bug. Thanks @archycoffee
Additionally, added mesen2-compatible lua scripts.
This commit is contained in:
parent
c6acdb8299
commit
b477373b5b
34
docs/Bugs.md
34
docs/Bugs.md
|
@ -222,4 +222,36 @@ updating each orb on the arm to be begin its self destruct routine. However,
|
|||
that never happens. So, the shoulder doesn't know to destroy itself. Instead
|
||||
the shoulder operates as if it wasn't destroyed and when it decides that a
|
||||
projectile should be created, that overwrites the hand with a different enemy
|
||||
type, and clears all the links between the hand and the arm.
|
||||
type, and clears all the links between the hand and the arm.
|
||||
|
||||
# 5. 2 Player Moving Cart Scroll Bug
|
||||
|
||||
When playing a 2 player game, _Contra_ will ensure that if one player is causing
|
||||
a scroll, the other player's position on the screen is compensated so that,
|
||||
relatively speaking, the player stays in the same location in the level. The
|
||||
game logic assumes that for horizontal scrolling, the scroll speed will always
|
||||
be 1px per frame. However, this isn't true when a player is moving to the right
|
||||
while standing on a moving cart in level 7 hangar. When a player is moving
|
||||
right while on a moving cart and causing scroll, the scroll speed will actually
|
||||
be 2px per frame. When this happens, the player that is not causing scroll will
|
||||
not have their X position properly adjusted and will be 'dragged' forward at a
|
||||
rate of 1px per frame. Thanks to @archycoffee for reporting this bug.
|
||||
|
||||
This bug occurs in the `scroll_player` logic.
|
||||
|
||||
```
|
||||
scroll_player:
|
||||
...
|
||||
dec SPRITE_X_POS,x ; move player that isn't causing scroll back
|
||||
```
|
||||
|
||||
Below is the corrected implementation.
|
||||
|
||||
```
|
||||
scroll_player:
|
||||
...
|
||||
lda SPRITE_X_POS,x
|
||||
sec
|
||||
sbc FRAME_SCROLL
|
||||
sta SPRITE_X_POS,x
|
||||
```
|
|
@ -1,6 +1,6 @@
|
|||
-- used for testing to show enemy data
|
||||
|
||||
function display_enemy_data()
|
||||
function Main()
|
||||
for i = 0,0xf do
|
||||
local ENEMY_X_POS = emu.read(0x033e + i, emu.memType.cpu)
|
||||
local ENEMY_Y_POS = emu.read(0x0324 + i, emu.memType.cpu)
|
||||
|
@ -30,8 +30,4 @@ function display_enemy_data()
|
|||
end
|
||||
end
|
||||
|
||||
function Main()
|
||||
display_enemy_data()
|
||||
end
|
||||
|
||||
emu.addEventCallback(Main, emu.eventType.endFrame)
|
|
@ -43,8 +43,8 @@ function get_bg_collision(x, y)
|
|||
collisionCode = collisionCode & 0x03;
|
||||
|
||||
local floorColor = 0x508fbc8f
|
||||
local waterColor = 0x500096FF
|
||||
local solidColor = 0x50A9A9A9
|
||||
local waterColor = 0x500096ff
|
||||
local solidColor = 0x50a9a9a9
|
||||
local tileColor = 0x0
|
||||
if collisionCode == 0x01 then
|
||||
tileColor = floorColor
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
function Main()
|
||||
-- 0xb0/b1 in memory store the amount of time the B weapon (barrier) lasts.
|
||||
-- By continually overwriting the timer to the max value (FF), the players
|
||||
-- will always be invincible
|
||||
emu.write(0x00b0, 0xff, emu.memType.nesMemory) -- player 1
|
||||
emu.write(0x00b1, 0xff, emu.memType.nesMemory) -- player 1
|
||||
end
|
||||
|
||||
emu.addEventCallback(Main, emu.eventType.endFrame)
|
|
@ -0,0 +1,33 @@
|
|||
-- used for testing to show enemy data
|
||||
|
||||
function Main()
|
||||
for i = 0,0xf do
|
||||
local ENEMY_X_POS = emu.read(0x033e + i, emu.memType.nesMemory)
|
||||
local ENEMY_Y_POS = emu.read(0x0324 + i, emu.memType.nesMemory)
|
||||
local ENEMY_ANIMATION_DELAY = emu.read(0x0538 + i, emu.memType.nesMemory)
|
||||
local ENEMY_ATTRIBUTES = emu.read(0x05a8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_FRAME = emu.read(0x0568 + i, emu.memType.nesMemory)
|
||||
local ENEMY_HP = emu.read(0x0578 + i, emu.memType.nesMemory)
|
||||
local ENEMY_STATE_WIDTH = emu.read(0x0598 + i, emu.memType.nesMemory)
|
||||
local ENEMY_VAR_A = emu.read(0x0548 + i, emu.memType.nesMemory)
|
||||
local ENEMY_VAR_B = emu.read(0x0558 + i, emu.memType.nesMemory)
|
||||
local ENEMY_VAR_1 = emu.read(0x05b8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_VAR_2 = emu.read(0x05c8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_VAR_3 = emu.read(0x05d8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_VAR_4 = emu.read(0x05e8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_SPRITES = emu.read(0x030a + i, emu.memType.nesMemory)
|
||||
local ENEMY_ROUTINE = emu.read(0x04b8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_ATTACK_DELAY = ENEMY_VAR_B
|
||||
local ENEMY_X_VELOCITY_FAST = emu.read(0x0508 + i, emu.memType.nesMemory)
|
||||
local ENEMY_X_VELOCITY_FRACT = emu.read(0x0518 + i, emu.memType.nesMemory)
|
||||
local ENEMY_X_VEL_ACCUM = emu.read(0x04d8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_Y_VELOCITY_FAST = emu.read(0x04e8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_Y_VELOCITY_FRACT = emu.read(0x04f8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_Y_VEL_ACCUM = emu.read(0x04c8 + i, emu.memType.nesMemory)
|
||||
|
||||
-- change variable to interested variable for studying
|
||||
emu.drawString(ENEMY_X_POS, ENEMY_Y_POS, string.format("%x", ENEMY_HP))
|
||||
end
|
||||
end
|
||||
|
||||
emu.addEventCallback(Main, emu.eventType.endFrame)
|
|
@ -0,0 +1,72 @@
|
|||
-- shows the background collision codes
|
||||
-- floor, water, solid, or empty
|
||||
|
||||
function get_bg_collision(x, y)
|
||||
local VERTICAL_SCROLL = emu.read(0xfc, emu.memType.nesMemory)
|
||||
local HORIZONTAL_SCROLL = emu.read(0xfd, emu.memType.nesMemory)
|
||||
local PPUCTRL_SETTINGS = emu.read(0xff, emu.memType.nesMemory)
|
||||
local adjusted_y = y + VERTICAL_SCROLL
|
||||
local adjusted_x = x + HORIZONTAL_SCROLL
|
||||
|
||||
if adjusted_y >= 0xf0 then
|
||||
adjusted_y = adjusted_y + 0x0f
|
||||
adjusted_y = adjusted_y - 255
|
||||
end
|
||||
|
||||
-- $10 is always #$00, except when moving cart is calling get_bg_collision
|
||||
local nametable_number = (PPUCTRL_SETTINGS ~ 0x00) & 0x01
|
||||
if adjusted_x > 255 then
|
||||
nametable_number = nametable_number ~ 1
|
||||
adjusted_x = adjusted_x - 255
|
||||
end
|
||||
|
||||
adjusted_y = (adjusted_y >> 2) & 0x3c
|
||||
adjusted_x = adjusted_x >> 4
|
||||
local bg_collision_offset = (adjusted_x >> 2) | adjusted_y
|
||||
if nametable_number == 1 then
|
||||
bg_collision_offset = bg_collision_offset | 0x40;
|
||||
end
|
||||
|
||||
local collisionCodeByte = emu.read(0x680 + bg_collision_offset, emu.memType.nesMemory)
|
||||
adjusted_x = adjusted_x & 0x03;
|
||||
local collisionCode = 0
|
||||
if adjusted_x == 0 then
|
||||
collisionCode = collisionCodeByte >> 6
|
||||
elseif adjusted_x == 1 then
|
||||
collisionCode = collisionCodeByte >> 4
|
||||
elseif adjusted_x == 2 then
|
||||
collisionCode = collisionCodeByte >> 2
|
||||
else
|
||||
collisionCode = collisionCodeByte
|
||||
end
|
||||
|
||||
collisionCode = collisionCode & 0x03;
|
||||
|
||||
local floorColor = 0x508fbc8f
|
||||
local waterColor = 0x500096ff
|
||||
local solidColor = 0x50a9a9a9
|
||||
local tileColor = 0x0
|
||||
if collisionCode == 0x01 then
|
||||
tileColor = floorColor
|
||||
elseif collisionCode == 0x02 then
|
||||
tileColor = waterColor
|
||||
elseif collisionCode == 0x03 then
|
||||
tileColor = solidColor
|
||||
end
|
||||
|
||||
if collisionCode ~= 0 then
|
||||
emu.drawRectangle(x, y, 16, 16, tileColor, true)
|
||||
end
|
||||
end
|
||||
|
||||
function Main()
|
||||
local VERTICAL_SCROLL = emu.read(0xfc, emu.memType.nesMemory)
|
||||
local HORIZONTAL_SCROLL = emu.read(0xfd, emu.memType.nesMemory)
|
||||
for i = 0,300,16 do
|
||||
for j = 0,300,16 do
|
||||
get_bg_collision(i - math.fmod(HORIZONTAL_SCROLL, 16), j - math.fmod(VERTICAL_SCROLL, 16))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
emu.addEventCallback(Main, emu.eventType.endFrame)
|
|
@ -0,0 +1,106 @@
|
|||
-- shows the player - enemy collision boxes
|
||||
-- does not show the player bullet - enemy collision boxes
|
||||
-- doesn't currently handle collision code f (rising spiked walls and fire beams)
|
||||
|
||||
function check_player_x_collision(player_index)
|
||||
local PLAYER_STATE = emu.read(0x90 + player_index, emu.memType.nesMemory)
|
||||
if PLAYER_STATE ~= 0x01 then
|
||||
-- exit if player not in normal state
|
||||
return nil
|
||||
end
|
||||
|
||||
-- @check_in_water_crouched
|
||||
local collision_box = 0x00
|
||||
local LEVEL_LOCATION_TYPE = emu.read(0x40 + player_index, emu.memType.nesMemory)
|
||||
local PLAYER_WATER_STATE = emu.read(0xb2 + player_index, emu.memType.nesMemory)
|
||||
if LEVEL_LOCATION_TYPE & 0xfe == 0x00 then
|
||||
-- outdoor
|
||||
local PLAYER_SPRITE_SEQUENCE = emu.read(0xbc + player_index, emu.memType.nesMemory)
|
||||
if PLAYER_WATER_STATE ~= 0x00 and PLAYER_SPRITE_SEQUENCE == 0x02 then
|
||||
-- exit if player crouched in water, no collision can happen with player
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if PLAYER_WATER_STATE == 0x00 then
|
||||
collision_box = collision_box + 1
|
||||
end
|
||||
|
||||
-- not checking for crouched while on indoor level, because non-bullets can
|
||||
-- collide when crouching player on indoor levels
|
||||
|
||||
local PLAYER_JUMP_STATUS = emu.read(0xa0 + player_index, emu.memType.nesMemory)
|
||||
local PLAYER_SPRITE_CODE = emu.read(0xd6 + player_index, emu.memType.nesMemory)
|
||||
if PLAYER_JUMP_STATUS == 0x00 then
|
||||
-- player not jumping
|
||||
collision_box = collision_box + 1
|
||||
if PLAYER_SPRITE_CODE ~= 0x17 then
|
||||
collision_box = collision_box + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- draw player collision point
|
||||
local SPRITE_Y_POS = emu.read(0x031a + player_index, emu.memType.nesMemory)
|
||||
local SPRITE_X_POS = emu.read(0x0334 + player_index, emu.memType.nesMemory)
|
||||
emu.drawRectangle(SPRITE_X_POS, SPRITE_Y_POS, 1, 1, 0x0000ff, false)
|
||||
|
||||
for i = 0,0xf do
|
||||
local ENEMY_ROUTINE = emu.read(0x04b8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_ROUTINE = emu.read(0x04b8 + i, emu.memType.nesMemory)
|
||||
local ENEMY_STATE_WIDTH = emu.read(0x0598 + i, emu.memType.nesMemory)
|
||||
local should_test_collision = ENEMY_STATE_WIDTH & 0x01 == 0x00
|
||||
if ENEMY_ROUTINE ~= 0x00 and should_test_collision then
|
||||
set_enemy_collision_box(player_index, i, collision_box)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function set_enemy_collision_box(player_index, i, collision_box)
|
||||
local ENEMY_SCORE_COLLISION = emu.read(0x0588 + i, emu.memType.nesMemory) & 0x0f
|
||||
|
||||
if ENEMY_SCORE_COLLISION == 0x0f then
|
||||
-- todo handle (fire beam and rising spiked wall)
|
||||
return
|
||||
end
|
||||
|
||||
local collision_box_addr = emu.readWord(0xe4e8 + (collision_box * 2), emu.memType.nesMemory) -- collision_box_codes_tbl
|
||||
local offset = ENEMY_SCORE_COLLISION * 4
|
||||
local y0 = emu.read(collision_box_addr + offset, emu.memType.nesMemory)
|
||||
local x0 = emu.read(collision_box_addr + offset + 1, emu.memType.nesMemory)
|
||||
local height = emu.read(collision_box_addr + offset + 2, emu.memType.nesMemory)
|
||||
local width = emu.read(collision_box_addr + offset + 3, emu.memType.nesMemory)
|
||||
local ENEMY_Y_POS = emu.read(0x0324 + i, emu.memType.nesMemory)
|
||||
local ENEMY_X_POS = emu.read(0x033e + i, emu.memType.nesMemory)
|
||||
|
||||
local topY = ENEMY_Y_POS
|
||||
if y0 > 0x80 then
|
||||
y0 = negateInt(y0)
|
||||
topY = ENEMY_Y_POS - y0
|
||||
else
|
||||
topY = ENEMY_Y_POS + y0
|
||||
end
|
||||
|
||||
local topX = ENEMY_X_POS
|
||||
if x0 > 0x80 then
|
||||
x0 = negateInt(x0)
|
||||
topX = ENEMY_X_POS - x0
|
||||
else
|
||||
topX = ENEMY_X_POS + x0
|
||||
end
|
||||
|
||||
emu.drawRectangle(topX, topY, width, height, 0x0000ff, false)
|
||||
end
|
||||
|
||||
function negateInt(num)
|
||||
if(num > 0x80) then
|
||||
num = (~num + 1) & 0xff
|
||||
end
|
||||
|
||||
return num
|
||||
end
|
||||
|
||||
function Main()
|
||||
check_player_x_collision(0)
|
||||
end
|
||||
|
||||
emu.addEventCallback(Main, emu.eventType.endFrame)
|
|
@ -0,0 +1,27 @@
|
|||
-- shows information about weapon on head-up-display area
|
||||
|
||||
function Main()
|
||||
local p1score1 = emu.read(0x07e2, emu.memType.nesMemory)
|
||||
local p1score2 = emu.read(0x07e3, emu.memType.nesMemory)
|
||||
p1score = ((p1score2 << 8) + p1score1) * 100
|
||||
|
||||
local weapon_hex = emu.read(0x00aa, emu.memType.nesMemory)
|
||||
local rapid_fire = ""
|
||||
if weapon_hex & 0x10 == 0x10 then
|
||||
rapid_fire = " Rapid "
|
||||
else
|
||||
rapid_fire = " "
|
||||
end
|
||||
|
||||
weapon_hex = weapon_hex & 0x0f
|
||||
local weapon_name = ""
|
||||
|
||||
if weapon_hex == 0 then weapon_name = "Default Gun"
|
||||
elseif weapon_hex == 1 then weapon_name = "Machine Gun"
|
||||
elseif weapon_hex == 2 then weapon_name = "Flame Thrower"
|
||||
elseif weapon_hex == 3 then weapon_name = "Spray"
|
||||
elseif weapon_hex == 4 then weapon_name = "Laser" end
|
||||
emu.drawString(5,40, "P1: " .. p1score .. rapid_fire .. weapon_name)
|
||||
end
|
||||
|
||||
emu.addEventCallback(Main, emu.eventType.endFrame)
|
|
@ -3996,7 +3996,12 @@ scroll_player:
|
|||
jsr find_scrolled_player ; find the player that isn't causing scroll and needs to be scrolled
|
||||
beq @exit ; exit if both players are causing scroll, don't cause any player to be scrolled
|
||||
dec SPRITE_X_POS,x ; move player that isn't causing scroll back
|
||||
; other player will remain at same relative position on screen
|
||||
; causing the player to remain at same relative position on screen
|
||||
; !(BUG?) assumes player causing scroll can only cause 1px of horizontal scroll per frame
|
||||
; when player causing scroll is on a moving cart and moving right, FRAME_SCROLL will be #$02
|
||||
; due to this bug, the player on the moving cart will cause other player to be dragged forward
|
||||
; 1px for each frame the player is causing scroll while on moving cart
|
||||
; thanks @archycoffee
|
||||
|
||||
@exit:
|
||||
rts
|
||||
|
|
Loading…
Reference in New Issue