/*
	This script file is part of the Sonic 3 A.I.R. script software distribution.
	Copyright (C) 2017-2024 by Eukaryot

	Published under the GNU GPLv3 open source software license, see license.txt
	or https://www.gnu.org/licenses/gpl-3.0.en.html
*/



// Sub-types (stored in objA0.subtype2c):
//  - Upper 4 bits == 0: Moving only down one wall - can be left or right
//  - Upper 4 bits == 4: Moving between two walls, short (1.5 rotations)
//  - Upper 4 bits == 8: Moving between two walls, long (3.5 rotations)
//  - Lower 4 bits: Number of jumps


//  u8[A0 + 0x36]: Number of jumps off a wall remaining

// Special handling only done by standalone, and only for the one-wall sub-type:
//  u8[A0 + 0x36]: Number of jumps off a wall remaining, but multiplied by 2
//  u8[A0 + 0x49]: Current downwards movement speed between 1 and 3


function void SOZZipLine.resetJumpsCounter()
{
	u8[A0 + 0x36] = objA0.subtype2c & 0x0f
#if STANDALONE
	if ((objA0.subtype2c & 0xf0) == 0)
	{
		u8[A0 + 0x36] *= 2
	}
#endif
}


//# address-hook(0x04a9a8) end(0x04aa44)
function void fn04a9a8()
{
	A1 = A0
	objA1.update_address = 0x04aa80
	fn04aa46()

	u8[A1 + 0x22] = 0x20
	D2.u16 = objA0.position.x.u16
	D3.u16 = objA0.position.y.u16
	objA0.value3e = D2.u16
	u16[A0 + 0x40] = D3.u16
	SOZZipLine.resetJumpsCounter()
	objA0.value32 = 0x30
	D1 = 0x10

	if (allocDynamicObjectAfterA0())
	{
		u16[A0 + 0x44] = A1.u16
		objA1.update_address = 0x04ad7a
		u16[A1 + 0x42] = A0.u16

		while (true)
		{
			A2 = A1
			fn04aa46()

			objA1.position.x.u16 = D2.u16
			objA1.position.y.u16 = D3.u16
			D3.u16 += 0x10
			++u16[A0 + 0x34]
			u16[A1 + 0x34] = u16[A0 + 0x34]

			--D1.s16
			if (D1.s16 < 0)
			{
				objA1.update_address = 0x04af0a
				u8[A1 + 0x22] = 0x21
				u16[A0 + 0x46] = A1.u16
				break
			}

			if (!allocDynamicObjectAfterA0())
				break

			objA1.update_address = 0x04ae50
			u16[A1 + 0x42] = A2.u16
			u16[A2 + 0x44] = A1.u16
			u16[A1 + 0x46] = A0.u16
		}
	}

	fn04aa80()
}


//# address-hook(0x04aa46) end(0x04aa7e)
function void fn04aa46()
{
	objA1.render_flags = render_flag.WORLD
	objA1.box_size.x = 0x10
	objA1.box_size.y = 0x10
	objA1.sprite_priority = 0x0280
	objA1.mapping_offset = 0x04b1d0
	objA1.sprite_attributes = (sprite_attribute.PALETTE.LINE2 | 0x0411)
	u8[A1 + 0x2a] = objA0.flags2a
	u16[A1 + 0x3e] = objA0.position.x.u16
	u16[A1 + 0x40] = objA0.position.y.u16
}


//# address-hook(0x04aa80) end(0x04aa90)
function void fn04aa80()
{
	A1 = 0xffff0000 + u16[A0 + 0x46]
	if (u8[A1 + 0x38] != 0)
	{
		objA0.update_address = 0x04aa94
	}
	fn04ad32()
}


//# address-hook(0x04aa94) end(0x04aac4)
function void fn04aa94()
{
	A1 = 0xffff0000 + u16[A0 + 0x46]
	if (u8[A1 + 0x38] == 0)
	{
		objA0.update_address = 0x04acc6
	}

#if STANDALONE
	// Go down faster (before the first wall jump)
	objA0.value32 += (objA0.value32 < 0xb4) ? 3 : 2
#else
	objA0.value32 += 2
#endif

	if (objA0.value32 >= 0xc0)
	{
		playSound(0x4c)
		objA0.update_address = 0x04aac8
		objA0.countdown_value = 0x01
	}

	fn04ad32()
}


//# address-hook(0x04aac8) end(0x04ab6a)
function void fn04aac8()
{
	// State: Character hanging at the wall, waiting for next jump
	A1 = 0xffff0000 + u16[A0 + 0x46]
	if (u8[A1 + 0x38] == 0)
	{
		objA0.update_address = 0x04acc6
	}

#if STANDALONE
	// Allow for just holding a jump button
	bool jumpPressed = (control.player1.state & CONTROL_ABC) != 0
#else
	bool jumpPressed = (control.player1.pressed & CONTROL_ABC) != 0
#endif
	if (jumpPressed)
	{
		playSound(SFX_JUMP)

	#if STANDALONE
	/*
		// Allow for jumping off
		// TODO: Does not work yet
		if (control.player1.state & CONTROL_DPAD)
		{
			objA1.hitbox_extends.x = char.hitbox.x.ROLLING
			objA1.hitbox_extends.y = char.hitbox.y.ROLLING
			objA1.state = char.state.ROLLING
			objA1.flags2a |= (0x02 | 0x04)
			u8[A1 + 0x40] = 1

			fn04ad32()
			return
		}
	*/
	#endif

		u8 subtype = objA0.subtype2c & 0xf0
		if (subtype == 0)
		{
			// Just going down one wall
			u16[A0 + 0x30] = 0x3f
			objA0.value26 = 0

		#if STANDALONE
			// Try to make larger jumps
			if (u8[A0 + 0x36] == 4)
			{
				u8[A0 + 0x49] = 2
				u8[A0 + 0x36] -= 2
			}
			else if (u8[A0 + 0x36] >= 3)
			{
				u8[A0 + 0x49] = 3
				u8[A0 + 0x36] -= 3
			}
			else
			{
				u8[A0 + 0x49] = u8[A0 + 0x36]
				u8[A0 + 0x36] = 0
			}
		#else
			--u8[A0 + 0x36]
		#endif
			if (u8[A0 + 0x36] == 0)
			{
				// Reached the end
				objA0.update_address = 0x04ab9e
			}
			else
			{
				objA0.update_address = 0x04ab6e
			}
		}
		else
		{
			// Alternating between two walls
			u16[A0 + 0x30] = (subtype * 2) - 7
			objA0.value26 = 0x80
			if (objA0.flags2a & 0x01)
			{
				objA0.velocity.x = 0x400
				u8[A0 + 0x37] = 0x80
			}
			else
			{
				objA0.velocity.x = -0x400
				u8[A0 + 0x37] = 0
			}

			objA0.flags2a ^= 0x01
			objA0.countdown_value = 0x03
			u16[A0 + 0x3c] = 0x06

			--u8[A0 + 0x36]
			if (u8[A0 + 0x36] == 0)
			{
				// Reached the end
				objA0.update_address = 0x04ac3a
			}
			else
			{
				objA0.update_address = 0x04abd8
			}
		}
	}

	fn04ad32()
}


//# address-hook(0x04ab6e) end(0x04ab9a)
function void fn04ab6e()
{
	A1 = 0xffff0000 + u16[A0 + 0x46]
	if (u8[A1 + 0x38] == 0)
	{
		objA0.update_address = 0x04acc6
	}

	objA0.value26 += 2
#if STANDALONE
	objA0.value32 += u8[A0 + 0x49]
#else
	objA0.value32 += 2
#endif

	--u16[A0 + 0x30]
	if (s16[A0 + 0x30] < 0)
	{
		playSound(0x4c)
		objA0.update_address = 0x04aac8
	}

	fn04ad32()
}


//# address-hook(0x04ab9e) end(0x04aba4)
function void fn04ab9e()
{
	objA0.countdown_value = 2
	objA0.update_address = 0x04abaa
	fn04abaa()
}


//# address-hook(0x04abaa) end(0x04abd4)
function void fn04abaa()
{
	A1 = 0xffff0000 + u16[A0 + 0x46]
	if (u8[A1 + 0x38] == 0)
	{
		objA0.update_address = 0x04acc6
	}

	objA0.value26 += 2
#if STANDALONE
	objA0.value32 += u8[A0 + 0x49]
#else
	objA0.value32 += 2
#endif

	--u16[A0 + 0x30]
	if (s16[A0 + 0x30] < 0)
	{
		u8[A0 + 0x27] = objA0.value26
		objA0.update_address = 0x04ac98
	}

	fn04ad32()
}


//# address-hook(0x04abd8) end(0x04ac36)
function void fn04abd8()
{
	UpdateMovementStraightSimple()

	A1 = 0xffff0000 + u16[A0 + 0x46]
	if (u8[A1 + 0x38] == 0)
	{
		objA0.update_address = 0x04acc6
	}

	if (u16[A0 + 0x30] >= 0x1a)
	{
		if (u16[A0 + 0x30] == 0x1a)
		{
			objA0.countdown_value = 1
		}
		u8[A0 + 0x37] += 4
	}

	objA0.value26 += 2
	++objA0.value32

	--u16[A0 + 0x30]
	if (s16[A0 + 0x30] < 0)
	{
		objA0.velocity.x = 0
		playSound(0x4c)
		objA0.update_address = 0x04aac8
	}

	if (u16[A0 + 0x3c] != 0)
	{
		--u16[A0 + 0x3c]
		++objA0.value32
		objA0.value26 += 2
	}

	fn04ad32()
}


//# address-hook(0x04ac3a) end(0x04ac94)
function void fn04ac3a()
{
	UpdateMovementStraightSimple()

	A1 = 0xffff0000 + u16[A0 + 0x46]
	if (u8[A1 + 0x38] == 0)
	{
		objA0.update_address = 0x04acc6
	}
	if (u16[A0 + 0x30] >= 0x1a)
	{
		u8[A0 + 0x37] += 4
	}

	objA0.value26 += 2
	++objA0.value32

	--u16[A0 + 0x30]
	if (s16[A0 + 0x30] < 0)
	{
		objA0.velocity.x = 0
		objA0.countdown_value = 2
		u8[A0 + 0x27] = objA0.value26
		objA0.update_address = 0x04ac98
	}

	if (u16[A0 + 0x3c] != 0)
	{
		--u16[A0 + 0x3c]
		++objA0.value32
		objA0.value26 += 2
	}

	fn04ad32()
}


//# address-hook(0x04ac98) end(0x04acc2)
function void fn04ac98()
{
	A1 = 0xffff0000 + u16[A0 + 0x46]
	if (u8[A1 + 0x38] == 0)
	{
		objA0.update_address = 0x04acc6
	}

	u8[A0 + 0x27] += 2
	if (u8[A0 + 0x27] >= 0xfc || u8[A0 + 0x27] < 0x86)
	{
		objA0.value26 = u8[A0 + 0x27]
	}

	fn04ad32()
}


//# address-hook(0x04acc6) end(0x04ad30)
function void fn04acc6()
{
	if (objA0.value26 != 0)
	{
		if (u8[A0 + 0x27] < 0x86)
		{
			objA0.value26 += 2
			if (objA0.value26 >= 0x80 && objA0.value26 < 0xfc)
			{
				objA0.value26 = 0
			}
			u8[A0 + 0x27] = 0
		}
		else
		{
			objA0.value26 = 0xfc
			u8[A0 + 0x27] += 2
			if (s8[A0 + 0x27] < 0)
			{
				fn04ad32()
				return
			}
		}
	}

	objA0.value32 -= 4
	if (objA0.value32 <= 0x30)
	{
		objA0.value32 = 0x30
		objA0.countdown_value = 0
		SOZZipLine.resetJumpsCounter()
		objA0.update_address = 0x04aa80
		D0.u16 = objA0.value3e
	}
	else
	{
		D0.u16 = objA0.position.x.u16
	}

	fn04ad36()
}


//# address-hook(0x04ad32) end(0x04ad32)
function void fn04ad32()
{
	D0.u16 = objA0.position.x.u16
	fn04ad36()
}


//# address-hook(0x04ad36) end(0x04ad74)
function void fn04ad36()
{
	D0.u16 = (D0.u16 & 0xff80) - level.despawn_position.x
	if (D0.u16 <= 0x0280)
	{
		DrawObject()
		return
	}

	D2.u16 = u16[A0 + 0x34]
	A2 = 0xffff0000 + u16[A0 + 0x44]
	while (D2.s16 > 0)
	{
		A1 = A2
		A2 = 0xffff0000 + u16[A1 + 0x44]
		UnloadObjectA1()
		--D2.s16
	}

	if (objA0.respawn_entry)
	{
		A2 = 0xffff0000 + objA0.respawn_entry
		u8[A2] &= ~0x80
	}

	UnloadObject()
}


//# address-hook(0x04ad7a) end(0x04ae4e)
function void fn04ad7a()
{
	A1 = 0xffff0000 + objA0.value42
	u16[A0 + 0x26] = u16[A1 + 0x26]
	objA0.value32 = u16[A1 + 0x32]
	D0.u8 = objA0.value26
	if ((u8[A1 + 0x2a] & char.flag.FACING_LEFT) == 0)
	{
		D0.u8 += 0x80
	}
	LookupHighPrecisionSinCos()

	D0.s32 = s32(D0.s16) * 0x20
	D2.u16 = objA0.value32
	D0.s16 = D0.s32 / D2.s16
	objA0.velocity.x = D0.u16
	D1.u16 = D0.u16
	D1.s16 >>= 4
	LookupAngleByVector()
	LookupHighPrecisionSinCos()

	objA0.velocity.y = D0.s16 >> 2
	D0 = 0
	D1 = 0
	D4.u16 = D2.u16
	D5.s16 = (-D2.s16) & 0x0f
	D2.s32 = objA0.velocity.x
	D2 <<= 4
	D3.s32 = objA0.velocity.y
	D3 <<= 4
	--D5.u16
	if (D5.s16 >= 0)
	{
		D4.u16 += 0x10
		while (D5.s16 >= 0)
		{
			D0 -= D2
			D1 -= D3
			--D5.s16
		}
	}

	D2 <<= 4
	D3 <<= 4
	D4.u16 >>= 4
	D4.u16 -= 0x10
	if (D4.s16 < 0)
	{
		D4.s16 = -D4.s16
		D2.s32 = -D2.s32
		D3.s32 = -D3.s32
	}

	while (D4.s16 > 0)
	{
		D0 += D2
		D1 += D3
		--D4.s16
	}

	objA0.position.x = D0 + objA1.position.x
	objA0.position.y = D1 + objA1.position.y

	D1.u16 = objA0.velocity.x
	D2.u16 = objA0.velocity.y
	LookupAngleByVector()
	objA0.animation.sprite = (D0.u8 + 0xc0 + 4) >> 3

	D0.s16 = objA0.position.y.u16 + 0x10
	if (D0.s16 > s16[A0 + 0x40])
	{
		DrawObject()
	}
}


//# address-hook(0x04ae50) end(0x04ae90)
function void fn04ae50()
{
	A1 = 0xffff0000 + objA0.value42
	objA0.animation.sprite = u8[A1 + 0x22]
	fn04aee0()

	if (u16[A0 + 0x34] == 0x0d)
	{
		A2 = 0xffff0000 + u16[A0 + 0x46]
		if (u16[A2 + 0x2e] == 0x02 && u8[A2 + 0x27] >= 0x86)
		{
			fn04ae92()
		}
	}

	D0.u16 = objA0.position.y.u16 + 0x10
	if (D0.s16 > s16[A0 + 0x40])
	{
		DrawObject()
	}
}


//# address-hook(0x04ae92) end(0x04aede)
function void fn04ae92()
{
	D0.u8 = u8[A2 + 0x27]
	if (u8[A2 + 0x2a] & char.flag.FACING_LEFT)
	{
		D0.u8 += 0x80
	}
	LookupSinCos()
	D0.s32 = s32(D0.s16) * 0x3c
	D0.s16 >>= 8
	D0.u16 += 0x40
	LookupHighPrecisionSinCos()
	objA0.velocity.x = D1.s16 >> 2
	D0.s16 >>= 2
	objA0.velocity.y = D0.u16

	D1.u16 = objA0.velocity.x
	D2.u16 = objA0.velocity.y
	LookupAngleByVector()
	D0.u8 = (D0.u8 + 0xc4) >> 3
	objA0.animation.sprite = D0.u8
}


//# address-hook(0x04aee0) end(0x04af08)
function void fn04aee0()
{
	objA0.velocity.x = objA1.velocity.x
	objA0.velocity.y = objA1.velocity.y
	D0 = objA1.position.x + (objA1.velocity.x << 8)
	D1 = objA1.position.y + (objA1.velocity.y << 8)
	objA0.position.x = D0
	objA0.position.y = D1
}


//# address-hook(0x04af0a) end(0x04af6a)
function void fn04af0a()
{
	D4.u16 = objA0.position.x.u16
	D5.u16 = objA0.position.y.u16
	A1 = 0xffff0000 + objA0.value42
	fn04aee0()

	if (D4.u16 != objA0.position.x.u16)
	{
		objA0.value3e = D4.u16
	}
	if (D5.u16 != objA0.position.y.u16)
	{
		u16[A0 + 0x40] = D5.u16
	}

	A3 = 0xffff0000 + u16[A0 + 0x46]
	A2 = A0 + 0x38
	A1 = 0xffffb000
	D0.u16 = control.player1
	fn04af80()

	A1 = 0xffffb04a
	++A2
	D0.u16 = control.tails
	fn04af80()

	if (u16[A3 + 0x2e] == 3)
	{
		D0 = u8(u8[A3 + 0x37] + 4) >> 4
		objA0.animation.sprite = u8[0x04af70 + D0.u16]
	}
	else
	{
		objA0.animation.sprite = 0x21
	}

	DrawObject()
}


//# address-hook(0x04af80) end(0x04b088)
function void fn04af80()
{
	if (u8[A2] == 0)
	{
		fn04b130()
		return
	}

	if (s8[A2] > 0)
	{
		if ((objA1.render_flags & render_flag.VISIBLE) && u8[A1 + 0x05] < 0x04 && debug_mode.state == 0)
		{
			if (u16[A3 + 0x2e] != 0x02 || (D0.u8 & 0x70) == 0)
			{
				objA1.position.x.u16 = objA0.position.x.u16
				objA1.position.y.u16 = objA0.position.y.u16 + 0x14
				objA1.state = char.state.HANGING

				D0.u16 = u16[A3 + 0x2e]
				if (D0.u16 == 0)
				{
					objA1.animation.sprite = 0x92		// Player characters' hanging sprite
				}
				else
				{
					if (D0.u16 != 1)
					{
						fn04b0b0()
						return
					}

					D0 = u8(u8[A3 + 0x26] * 2 + 8) >> 4
					objA1.animation.sprite = u8[0x04b0a0 + D0.u16]		// Player characters' hanging sprites
				}

				fn04b08e()
			}
			else
			{
				u8[A1 + 0x2e] = 0
				u8[A2] = 0

				objA1.velocity.x = (objA0.position.x.u16 - objA0.value3e) << 8
				objA1.velocity.y = (objA0.position.y.u16 - u16[A0 + 0x40]) << 8

				u8[A2 + 0x02] = 0x3c
				if (D0 & 0x0800)		// CONTROL_RIGHT held
				{
					objA1.velocity.x = 0x200
				}
				else if (D0 & 0x0400)		// CONTROL_LEFT held
				{
					objA1.velocity.x = -0x200
				}
				objA1.velocity.y -= 0x380
				u8[A1 + 0x2a] |= char.flag.IN_AIR
				u8[A1 + 0x40] = 1
				objA1.hitbox_extends.y = char.hitbox.y.ROLLING
				objA1.hitbox_extends.x = char.hitbox.x.ROLLING
				objA1.state = char.state.ROLLING
				u8[A1 + 0x2a] |= char.flag.ROLLING

				playSound(SFX_JUMP)
			}
			return
		}
	}
	else
	{
		objA1.velocity.x = 0x0300
		objA1.velocity.y = 0x0200
		u8[A1 + 0x2a] |= char.flag.IN_AIR
	}

// 0x04b03c:
	u8[A1 + 0x2e] = 0
	u8[A2] = 0
	u8[A2 + 0x02] = 0x3c
	return


}


//# address-hook(0x04b08e) end(0x04b09e)
function void fn04b08e()
{
	D0 = objA1.animation.sprite
	u32 backupA2 = A2
	SetCharacterSpritePatterns()
	A2 = backupA2
}


//# address-hook(0x04b0b0) end(0x04b0cc)
function void fn04b0b0()
{
	if (D0.u16 != 2)
	{
		fn04b0de()
		return
	}

	objA1.state = char.state.HANGING
	D0 = u8(u8[A3 + 0x26] + 8) >> 4
	objA1.animation.sprite = u8[0x04b0ce + D0.u16]		// Player characters' hanging sprites

	fn04b08e()
}


//# address-hook(0x04b0de) end(0x04b10c)
function void fn04b0de()
{
	u8[A1 + 0x20] = 0
	D0 = u8(u8[A3 + 0x37] + 4) >> 4
	objA1.animation.sprite = u8[0x04b110 + D0.u16]		// Player characters' hang turn sprites
	D0.u8 = u8[0x04b120 + D0.u16]

	objA1.render_flags = (objA1.render_flags & ~(render_flag.FLIP_X | render_flag.FLIP_Y)) | D0.u8
	u8[A1 + 0x2a] = (u8[A1 + 0x2a] & ~(char.flag.FACING_LEFT | char.flag.IN_AIR)) | D0.u8

	fn04b08e()
}


//# address-hook(0x04b130) end(0x04b1ce)
function void fn04b130()
{
	if (u8[A2 + 0x02] != 0)
	{
		--u8[A2 + 0x02]
		if (u8[A2 + 0x02] != 0)
			return
	}

	D0.u16 = objA1.position.x.u16 - objA0.position.x.u16 + 0x10
	D1.u16 = objA1.position.y.u16 - objA0.position.y.u16
	if (D0.u16 < 0x20 && D1.u16 < 0x18)
	{
		if (u8[A1 + 0x2e] == 0 && u8[A1 + 0x05] < 0x04 && debug_mode.state == 0)
		{
			objA1.render_flags &= ~(render_flag.FLIP_X | render_flag.FLIP_Y)
			objA1.velocity.x = 0
			objA1.velocity.y = 0
			objA1.groundspeed = 0
			objA1.position.x.u16 = objA0.position.x.u16
			objA1.position.y.u16 = objA0.position.y.u16 + 0x14
			objA1.state = char.state.HANGING
			u8[A1 + 0x2e] = (control_flag.DISABLE_ANIMATION | control_flag.DISABLE_UPDATE)
			u8[A1 + 0x3d] = 0

			D0.u8 = u8[A3 + 0x2a] & char.flag.FACING_LEFT
			objA1.render_flags |= D0.u8
			u8[A1 + 0x2a] &= ~0x03
			u8[A1 + 0x2a] |= D0.u8
			u8[A2] = 0x01

			playSound(SFX_GRAB)
		}
	}
}
