
-- SRB2 TWO POINT TWO
-- Mario by Other Chen
-- Anyone can use or modify this PK3 file, as long as I remain credited here.
-- Any modifications must be released with a different filename (i.e. NOT bouncy-finalproto.pk3).


-- KNOWN ISSUES:
-- FindWall() sometimes returns true while above a slope.
-- FindWall() returns false on certain walls, especially the outermost boundaries of a map.
-- Pressing A or B while sliding under something kills the player. I can't find a way to detect and prevent this before it happens.
-- Holding B after a slide allows bouncy to stay belly-flopped in place with PF_SPINNING.


-- HOW TO SPRITE:
--
-- JUMP: Normal jumping and falling. Make sure there's frames here, or bouncy will use his sliding frames instead!
-- FLY_: Triple Jump. If you use 8 frames for the Tornado Jump, you should probably use 6 or 8 for this too.
--
-- TWIN: High Kick. Returns to JUMP frames afterward. This will look much better as an actual kick.
-- SWIM: Swim (unless you're getting fancy and making custom spr2 thingies)
--
-- SPIN: Dive Attack
-- ROLL: Sliding (PF_SPINNING) on the ground
-- GLID: Sliding in the air, e.g. by falling off or slide-hopping.
-- Could create a custom spr2 for this, if your PAIN sprite doesn't look quite right for this purpose.
--
-- MLEE: Crouch. You could play with the frame duration
-- depending on whether bouncy's braking (from full speed) or just crouching in place.
-- Or even make a separate frame for braking...
-- DASH: Tornado Jump. Rotating T-pose seems fitting for this move.


-- Functions!
local bouncyReset -- initialize variables
local bouncyA, bouncyAA, bouncyB, bouncyBb -- buttons
local bouncyPassive, bouncyStanding
local bouncyComboJump, bouncyHighKick, bouncyWallJump, bouncySwim
local bouncyDiveAttack, bouncyDiving, bouncySliding, bouncySlideHop
local bouncyCrouching, bouncyTornadoJump, bouncyTornadoing
local CanBombShield
local WASDAngle -- adjust angle based on movement keys, not actual movement direction
local FindWall, CanWallJump, MarkWallJump, ClearWallJump
local PlayerThrust, PlayerMomZ -- correct for scale, and strength is halved while underwater
local PlayerBrake -- subtract 1/f from object's speed, or 2/f if underwater
local PlayerSpeed -- p.speed corrected for scale

-- Console commands to change some of bouncy's stats
local cv_triple -- (default: 3) How many jumps bouncy needs to "Triple" Jump.
local cv_rekick -- (default: Off) Whether bouncy can High Kick again after wall-jumping.
local cv_swims -- (default: 5) How many times bouncy can swim upward. Use -1 for infinite swimming.
local cv_tornado -- (default: 1) How much stronger bouncy's Tornado Jump is while letting go of WASD.

-- Console commands to disable some of bouncy's moves (server-wide).
local cv_notriple -- Double/Triple Jump
local cv_noabil -- High Kick and Swim (Wall Jump is still available above water)
local cv_nowall -- Wall Jump
local cv_nodive -- Dive Attack (allows shield abilities to be used normally)
local cv_nocrouch -- Crouch and Tornado Jump

freeslot("sfx_mrland", "sfx_mrcrh1", "sfx_bltj", "sfx_blhp", "sfx_blpw", "sfx_blbn", "sfx_mrcrh2")


bouncyReset = function(p)
	p.bouncygrounded = 0 -- How long have we been on the ground? Updates every ThinkFrame hook
	p.bouncycombo = 0 -- How many jumps have we done? (for Triple Jump)
	ClearWallJump(p) -- Where did we last wall-jump?
	p.bouncyswims = 0 -- How many times can we swim upward?
	p.bouncydiving = 0 -- Are we diving in the air? Sliding is just "spinning" on the ground
	p.bouncycrouching = 0 -- Are we crouching by holding B?
end


bouncyA = function(p)
	-- Jumping from the ground.
	
	p.jumpfactor = skins.bouncy.jumpfactor
	ClearWallJump(p)
	p.bouncyswims = cv_swims.value
	
	if (p.pflags & PF_SPINNING) then
		return bouncySlideHop(p)
		
	elseif p.bouncycrouching then
		return bouncyTornadoJump(p)
		
	elseif not (p.mo.eflags & MFE_UNDERWATER)
	and not cv_notriple.value then
		return bouncyComboJump(p) -- Double/Triple Jump
		
	else
		p.bouncycombo = 0
		return false -- Normal jump
		
	end
end

bouncyAA = function(p)
	-- Jump ability.
	
	if (p.mo.eflags & MFE_UNDERWATER) then
		return bouncySwim(p)

	elseif CanWallJump(p) then
		return bouncyWallJump(p)
		
	elseif not (p.pflags & PF_THOKKED) then
		return bouncyHighKick(p)
		
	end
end

bouncyB = function(p)
	-- "Spin" ability i.e. diving and sliding.
	
	if P_IsObjectOnGround(p.mo) then
		if (p.pflags & PF_SPINNING) then
			p.pflags = $1 & ~PF_SPINNING
			return true -- Just stop sliding (and probably crouch afterward)
			
		elseif (p.cmd.forwardmove or p.cmd.sidemove) then
			return bouncyDiveAttack(p)
			
		end
	else
		if not (p.pflags & PF_SPINNING) then
			if CanBombShield(p) then
				return false -- Detonate the shield
				
			else
				return bouncyDiveAttack(p)
				
			end
		end
	end
	return true
	
	-- If nothing happened, then we're holding B.
	--             vvv See below vvv
	
end

bouncyBb = function(p)
	-- Hold B to crouch.
	if not (p.pflags & PF_SPINNING) then
		p.bouncycrouching = $1+1
	end
	return true
end


bouncyPassive = function(p)
	
	-- Initialize S_SKIN values, because other functions may change these.
	p.normalspeed = skins.bouncy.normalspeed
	p.thrustfactor = 5
	
	if P_IsObjectOnGround(p.mo) then
		p.bouncygrounded = $1+1
	else
		p.bouncygrounded = 0
	end
	
	if P_PlayerInPain(p) or p.deadtimer then
		bouncyReset(p)
		
	elseif p.bouncydiving then
		bouncyDiving(p)
		
	elseif (p.pflags & PF_SPINNING) then
		bouncySliding(p)
		
	elseif p.bouncycrouching then
		bouncyCrouching(p)
		
	else
		bouncyStanding(p)
		
	end
end


bouncyStanding = function(p)
	-- bouncy's behavior when not crouching or sliding.
	
	if p.bouncygrounded == 1 then
		S_StartSoundAtVolume(p.mo, sfx_mrland, 128, p) -- Landing sound
	elseif (p.bouncygrounded > 12) then
		p.bouncycombo = 0 -- Reset Triple Jump combo
	end
	
	if (p.mo.state == S_PLAY_ROLL)
	or (p.mo.state == S_PLAY_DASH) then
		-- Standing from a slide or Tornado Jump
		p.mo.state = S_PLAY_WALK
	elseif (p.mo.state == S_PLAY_FLY)
	or (p.mo.state == S_PLAY_TWINSPIN) then
		-- High Kick and Triple Jump allow bouncy to attack from below.
		p.pflags = $1 & ~PF_NOJUMPDAMAGE
	end
	
end


bouncyComboJump = function(p)
	-- Jump three times in a row to Triple Jump.

	p.bouncycombo = $1+1
	if PlayerSpeed(p) < skins.bouncy.normalspeed/2 then
		-- Don't finish the Triple Jump unless we're running fast enough
		p.bouncycombo = min($1, cv_triple.value - 1)
	end
		
	if p.bouncycombo >= cv_triple.value then
		-- Triple Jump
		p.jumpfactor = $1 * (4 + p.bouncycombo)/5
		P_DoJump(p)
		
		p.bouncycombo = 0
		if not p.powers[pw_invulnerability] then
			p.scoreadd = $1+1
		end
		
		p.mo.state = S_PLAY_FLY
		p.mo.tics = 9
		
		local spark = P_SpawnMobjFromMobj(p.mo, 0, 0, 0, MT_SUPERSPARK)
		if spark.valid then spark.state = S_SSPK3 end
		
		S_StartSoundAtVolume(p.mo, sfx_bltj, 128, p)
		
		return true

	elseif p.bouncycombo > 1 then
		-- Double jump
		p.jumpfactor = $1 * (4 + p.bouncycombo)/5
		P_DoJump(p)
		
		local spark = P_SpawnMobjFromMobj(p.mo, 0, 0, 0, MT_SUPERSPARK)
		if spark.valid then spark.state = S_SSPK4 end
		
		-- Helper sounds, for when cvar bouncy_triple is greater than 3
		if (p.bouncycombo >= 3) then
			if (p.bouncycombo % 2) then
				S_StartSoundAtVolume(p.mo, sfx_hoop1, 128, p)
			else
				S_StartSoundAtVolume(p.mo, sfx_hoop2, 128, p)
			end
		end
		
		return true
		
	else
		return false -- Normal jump
		
	end
end


bouncyHighKick = function(p)
	-- Basically a tiny double jump. Allows bouncy to attack from below.
	if cv_noabil.value then return false end
	
	p.jumping = false -- disable momz cutting
	
	PlayerBrake(p.mo, 2)
	PlayerMomZ(p.mo, 7*skins.bouncy.jumpfactor)
	
	p.mo.state = S_PLAY_TWINSPIN
	p.pflags = $1|PF_THOKKED
	
	S_StartSound(p.mo, sfx_putput)
	
	return true
end

bouncyWallJump = function(p)
	-- Jump backward off of a nearby wall.
	-- Behaves slightly differently when directionchar is disabled, and that's okay~
	
	p.jumping = false -- disable momz cutting
	
	PlayerThrust(p.mo, p.drawangle, -10*skins.bouncy.jumpfactor)
	PlayerMomZ(p.mo, 10*skins.bouncy.jumpfactor)
	
	p.mo.state = S_PLAY_JUMP
	p.drawangle = $1 + ANGLE_180
	p.bouncycombo = 1
	
	if cv_rekick.value then
		p.pflags = $1 & ~PF_THOKKED
	end
	
	local spark = P_SpawnMobjFromMobj(p.mo, 0, 0, 0, MT_SUPERSPARK)
	if spark.valid then spark.state = S_SSPK4 end
	S_StartSound(p.mo, sfx_blbn)
	
	return true
end

bouncySwim = function(p)
	-- Basically a tiny double jump. Does not allow bouncy to attack from below.
	if cv_noabil.value then return false end
	if p.bouncyswims == 0 then
		return true
	elseif p.bouncyswims > 0 then
		p.bouncyswims = $1-1
	end
	
	PlayerBrake(p.mo, 4)
	PlayerMomZ(p.mo, 7*skins.bouncy.jumpfactor)
	
	p.pflags = $1 & ~PF_THOKKED
	p.mo.state = S_PLAY_SWIM
	P_SpawnMobjFromMobj(p.mo, 0, 0, 0, MT_MEDIUMBUBBLE)
	S_StartSound(p.mo, sfx_pudpud)
	if not p.bouncyswims then
		S_StartSoundAtVolume(p.mo, sfx_blpw, 128, p) -- oh no! we're out of swims
	end
	
	return true
end


bouncyDiveAttack = function(p)
	-- Leap forward into a dive, attacking enemies.
	-- Allows bouncy to instantly change direction and maintain his current speed.
	
	p.bouncydiving = 20*TICRATE -- If bouncy somehow gets stuck diving, he'll stop after 20 seconds.
	p.bouncycrouching = 0
	
	PlayerThrust(p.mo, WASDAngle(p), max(PlayerSpeed(p), skins.bouncy.actionspd) )
	p.drawangle = WASDAngle(p)
	
	if P_IsObjectOnGround(p.mo) then
		PlayerMomZ(p.mo, skins.bouncy.actionspd/4)
	else
		if (p.mo.momz * P_MobjFlip(p.mo) < 0)
		or not (p.pflags & PF_JUMPDOWN) then
			PlayerMomZ(p.mo, 0)
		end
	end
	
	p.mo.state = S_PLAY_SPINDASH
	p.pflags = $1|PF_THOKKED|PF_SPINNING & ~PF_JUMPED
	p.bouncycombo = 0
	
	S_StartSound(p.mo, sfx_thok)
	
	return true
end

bouncyDiving = function(p)
	-- Diving in the air. bouncy starts sliding only once he lands.
	
	p.normalspeed = $1/2
	p.thrustfactor = 1
	p.bouncydiving = $1-1
	
	if p.bouncygrounded
	or (p.mo.state ~= S_PLAY_SPINDASH) then
		p.pflags = $1|PF_SPINNING
		p.mo.state = S_PLAY_ROLL
		p.bouncydiving = 0
	end
	
end

bouncySliding = function(p)
	-- Sliding on the ground to attack and fit under gaps.
	
	p.normalspeed = $1/2
	p.thrustfactor = 3
	
	if p.bouncygrounded then
		PlayerBrake(p.mo, 40)
		if PlayerSpeed(p) > FRACUNIT then
			-- Sliding sound
			local volume = PlayerSpeed(p) * 128/skins.bouncy.normalspeed + 128
			S_StartSoundAtVolume(p.mo, sfx_s3k7e, min(volume, 255) )
		end
	else
		if (p.mo.state == S_PLAY_DASH) then
			-- Tornado Jumping, which uses PF_SPINNING
			bouncyTornadoing(p)
			
		elseif p.mo.momz * P_MobjFlip(p.mo) < 0
		and (p.mo.state ~= S_PLAY_GLIDE)
		and (p.mo.state ~= S_PLAY_FALL) then
			-- Jumping out of a slide
			p.mo.state = S_PLAY_GLIDE
			p.mo.tics = 20
			p.mo.nextstate = S_PLAY_FALL
			
		end
	end
end

bouncySlideHop = function(p)
	-- Jump out of a slide.
	
	PlayerMomZ(p, 4*FRACUNIT)
	
	p.pflags = $1|PF_THOKKED
	p.bouncycombo = 1
	
	S_StartSound(p.mo, sfx_blhp)
	
	return true
end


bouncyCrouching = function(p)
	-- Brake, and prepare for a Tornado Jump.
	-- I tried height-changing and crawling, but it wasn't as safe as I wanted my final prototype to be.
	
	p.normalspeed = 0
	p.thrustfactor = 0
	
	if p.bouncygrounded then
		PlayerBrake(p.mo, 4)
		p.mo.state = S_PLAY_MELEE
		if p.bouncycrouching == 1 then
			if (PlayerSpeed(p) > skins.bouncy.normalspeed * 5/8) then
				-- "Braking"
				P_SpawnMobjFromMobj(p.mo, 0, 0, 0, MT_SPINDUST)
				S_StartSound(p.mo, sfx_skid)
			end
		end
	end
	
	-- Uncrouch
	if not (p.pflags & PF_USEDOWN) then
		p.bouncycrouching = 0
		if (p.mo.state == S_PLAY_MELEE)
		or (p.mo.state == S_PLAY_MELEE_FINISH) then
			p.mo.state = S_PLAY_WALK
		end
	end
	
	-- T-bag sounds after finishing a level. Be nice to your fellow players!
	if (p.pflags & PF_FINISHED)	or p.exiting then
		if p.bouncycrouching == 1 then
			S_StartSoundAtVolume(p.mo, sfx_mrcrh1, 128)
		elseif p.bouncycrouching == 0 then
			S_StartSoundAtVolume(p.mo, sfx_mrcrh2, 128)
		end
	end
	
end

bouncyTornadoJump = function(p)
	-- Jump upward and spin slowly to the ground.
	-- Let go of WASD to jump higher at the expense of some mobility.
	
	p.bouncycrouching = 0
	
	p.jumpfactor = $1*2
	P_DoJump(p, false)
	if (p.cmd.forwardmove or p.cmd.sidemove) then
		PlayerThrust(p, WASDAngle(p), skins.bouncy.normalspeed/2)
	else
		PlayerBrake(p.mo)
		p.mo.momz = $1 * (cv_tornado.value+4)/4
	end
	
	p.pflags = $1|PF_THOKKED|PF_SPINNING
	p.pflags = $1 & ~PF_JUMPED
	p.mo.state = S_PLAY_DASH
	p.bouncycombo = 0
	
	S_StartSound(p.mo, sfx_spndsh)
	
	return true
end

bouncyTornadoing = function(p)
	-- Spin slowly to the ground after Tornado Jumping.

	if PlayerSpeed(p) > skins.bouncy.normalspeed * 2/3 then
		PlayerBrake(p, 30)
	end
	
	if p.mo.momz * P_MobjFlip(p.mo) > 0 then
		p.mo.tics = min($1, 1)
		p.mo.momz = $1 * 15/16
	elseif (p.pflags & PF_JUMPDOWN) then
		p.mo.tics = min($1, 1)
		p.mo.momz = $1 * 7/8
	end
	
end


-- All of these hooks are essential to bouncy's behavior, and they work fine as is.
-- Don't touch 'em unless you know what you're doing!


-- Initialize variables
addHook("PlayerSpawn", bouncyReset)

-- Press A to jump from the ground.
addHook("JumpSpecial", function(p)
	if p.mo.skin ~= "bouncy" then return false end
	if P_PlayerInPain(p) or p.deadtimer then return false end
	if not P_IsObjectOnGround(p.mo) then return false end
	if (p.pflags & PF_JUMPDOWN) then return false end
	return bouncyA(p)
end)

-- Press A again to jump in the air.
addHook("AbilitySpecial", function(p)
	if p.mo.skin ~= "bouncy" then return false end
	if P_PlayerInPain(p) or p.deadtimer then return false end
	if p.bouncydiving then return true end
	if (p.pflags & PF_SPINNING) then return false end
	return bouncyAA(p)
end)

-- Press B while jumping to dive and slide.
addHook("SpinSpecial", function(p)
	if p.mo.skin ~= "bouncy" then return false end
	if P_PlayerInPain(p) or p.deadtimer then return false end
	if not (P_IsObjectOnGround(p.mo) or (p.pflags & PF_JUMPED)) then return false end
	if (p.pflags & PF_USEDOWN) then return true end
	if cv_nodive.value then return false end
	return bouncyB(p)
end)

-- Hold B on the ground to crouch.
addHook("SpinSpecial", function(p)
	if p.mo.skin ~= "bouncy" then return false end
	if P_PlayerInPain(p) or p.deadtimer then return false end
	if not P_IsObjectOnGround(p.mo) then return true end
	if p.bouncydiving then return true end
	if cv_nocrouch.value then return false end
	return bouncyBb(p)
end)

addHook("ThinkFrame", do
	for p in players.iterate do
		if p.mo.skin == "bouncy" then bouncyPassive(p) else bouncyReset(p) end
	end
end)


-- This function was adapted from 2.1.x Mighty.lua by Tripel the fox

-- Search for a wall or FOF in front of the player.
-- If you're touching the wall, this should work within 60deg on either side.
FindWall = function(p, angle)
	local r = p.mo.radius
	local wall = R_PointInSubsector(p.mo.x + FixedMul(2*r, cos(angle)), p.mo.y + FixedMul(2*r, sin(angle))).sector
	if (wall.floorheight > p.mo.z)
	or ((wall.ceilingheight <= p.mo.height + p.mo.z) and not (wall.floorheight == wall.ceilingheight)) then
		return true
	end
	-- FOFs can be walls too, so let's check for those
	for fwall in wall.ffloors() do
		if (p.mo.z <= fwall.topheight) and (p.mo.height + p.mo.z > fwall.bottomheight)
		and (fwall.flags & FF_BLOCKPLAYER) then return true end
	end
	return false
end


-- The rest of these functions are mine, but you can use them freely in your projects.

-- Check whether the player can wall jump.
-- Don't call this every tic -- only ONCE per wall jump!
CanWallJump = function(p)
	if cv_nowall.value then return false end
	
	-- Check both the camera direction and sprite direction.
	if FindWall(p, p.mo.angle) then
		p.drawangle = p.mo.angle
	elseif not FindWall(p, p.drawangle) then
		return false
	end
	
	-- Don't worry about angles on the first wall jump,
	-- or if the player has moved some distance away.
	if (p.walljumpangle == nil) then
		MarkWallJump(p)
		return true
	end
	if (R_PointToDist2(p.mo.x, p.mo.y, p.walljumpposx, p.walljumpposy) > 2*RING_DIST)
	or (abs(p.mo.z - p.walljumpposz) > RING_DIST/2) then
		MarkWallJump(p)
		return true
	end
	
	-- Make sure player's angle has changed since the previous wall jump.
	if not bump
	and (p.drawangle - p.walljumpangle) > -ANG60
	and (p.drawangle - p.walljumpangle) < ANG60 then
		return false
	end
	
	-- Done!
	MarkWallJump(p)
	return true
end

-- Mark the previous wall jump spot
MarkWallJump = function(p)
	p.walljumpangle = p.drawangle
	p.walljumpposx = p.mo.x
	p.walljumpposy = p.mo.y
	p.walljumpposz = p.mo.z
end

-- Forget the previous wall jump spot
ClearWallJump = function(p)
	p.walljumpangle = nil
	p.walljumpposx = p.mo.x
	p.walljumpposy = p.mo.y
	p.walljumpposz = p.mo.z
end


-- Check whether the player can press B to detonate the Armageddon shield.
CanBombShield = function(p)
	return (p.powers[pw_shield] & SH_ARMAGEDDON) == SH_ARMAGEDDON
	and (p.cmd.buttons & (BT_CUSTOM1|BT_CUSTOM2|BT_CUSTOM3))
end


-- Return the in-world angle of the player's WASD/joystick direction,
-- or straight ahead if no direction is held.
-- (There should be a native P_ function for this. STJr pls)
WASDAngle = function(p)
	if p.player then p = p.player end
	local plus = 0
	if p.cmd.sidemove then
		if p.cmd.forwardmove > 0 then plus = ANGLE_45
		elseif p.cmd.forwardmove < 0 then plus = ANGLE_135
		else plus = ANGLE_90 end
		if p.cmd.sidemove > 0 then plus = -$1 end
	elseif p.cmd.forwardmove < 0 then plus = ANGLE_180 end
	return p.mo.angle + plus
end


-- Thrust the player horizontally. Use the "relative" flag just like P_SetObjectMomZ().
-- Corrects for player's scale, and thrusts at half speed while underwater.
PlayerThrust = function(p, angle, speed, relative)
	if p.player then p = p.player end
	if (p.mo.eflags & MFE_UNDERWATER) then speed = $1/2 end
	if relative then P_Thrust(p.mo, angle, FixedMul(speed, p.mo.scale))
	else P_InstaThrust(p.mo, angle, FixedMul(speed, p.mo.scale)) end
end

-- Thrust the player vertically. Use the "relative" flag just like P_SetObjectMomZ().
-- Corrects for player's scale (like the native function), and thrusts at half speed while underwater.
PlayerMomZ = function(p, speed, relative)
	if p.player then p = p.player end
	if (p.mo.eflags & MFE_UNDERWATER) then speed = $1/2 end
	P_SetObjectMomZ(p.mo, speed, relative)
end

-- Get the player's speed, corrected for scale.
PlayerSpeed = function(p)
	return FixedDiv(p.speed, p.mo.scale)
end

-- Slows the player down.
-- Higher values apply less braking power, and are good for slowing gradually.
-- Lower values are good for instantly slowing down in a single tic.
PlayerBrake = function(p, f)
	if p.player then p = p.player end
	local s = 1
	if not f or (f <= 1) then f = 1
	elseif (p.mo.eflags & MFE_UNDERWATER) then s = 2 end
	p.mo.momx = $1 * (f-s) / f
	p.mo.momy = $1 * (f-s) / f
end

addHook("ThinkFrame", do
    for player in players.iterate
        if player.mo and player.mo.skin == "bouncy"
        player.powers[pw_underwater] = 1073741824
        end
    end
end)

-- Cvars are described at the top.
cv_triple = CV_RegisterVar({
	name = "bouncy_triple",
	PossibleValue = {MIN = 3, MAX = 10},
	defaultvalue = 3,
	flags = CV_NETVAR|CV_CHEAT
})
cv_rekick = CV_RegisterVar({
	name = "bouncy_rekick",
	PossibleValue = CV_OnOff,
	defaultvalue = "Off",
	flags = CV_NETVAR|CV_CHEAT
})
cv_swims = CV_RegisterVar({
	name = "bouncy_swims",
	PossibleValue = {MIN = -1, MAX = 30},
	defaultvalue = 5,
	flags = CV_NETVAR|CV_CHEAT
})
cv_tornado = CV_RegisterVar({
	name = "bouncy_tornado",
	PossibleValue = {MIN = 0, MAX = 10},
	defaultvalue = 1,
	flags = CV_NETVAR|CV_CHEAT
})
cv_notriple = CV_RegisterVar({
	name = "bouncy_notriple",
	PossibleValue = CV_OnOff,
	defaultvalue = "Off",
	flags = CV_NETVAR|CV_CHEAT
})
cv_noabil = CV_RegisterVar({
	name = "bouncy_noabil",
	PossibleValue = CV_OnOff,
	defaultvalue = "Off",
	flags = CV_NETVAR|CV_CHEAT
})
cv_nowall = CV_RegisterVar({
	name = "bouncy_nowall",
	PossibleValue = CV_OnOff,
	defaultvalue = "Off",
	flags = CV_NETVAR|CV_CHEAT
})
cv_nocrouch = CV_RegisterVar({
	name = "bouncy_nocrouch",
	PossibleValue = CV_OnOff,
	defaultvalue = "Off",
	flags = CV_NETVAR|CV_CHEAT
})
cv_nodive = CV_RegisterVar({
	name = "bouncy_nodive",
	PossibleValue = CV_OnOff,
	defaultvalue = "Off",
	flags = CV_NETVAR|CV_CHEAT
})


--
--              @@@@@@@@  @@@@@@  
--          @@@@******@@@@      @@
--        @@************@@      @@
--      @@******@@@@@@@@@@@@    @@
--    @@******@@@@@@@@@@@@@@@@  @@
--    @@**@@@@            @@@@@@  
--  @@@@@@@@      @@  @@  @@****@@
--  @@  @@@@      @@  @@  @@**@@
--@@    @@@@@@              @@**@@
--@@      @@    @@          @@**@@
--  @@        @@@@@@@@    @@@@@@  
--    @@@@        @@@@@@@@@@**@@  
--      @@@@@@          @@****@@  
--    @@****@@@@@@@@@@@@@@**@@    
--  @@********@@@@      @@@@      
--@@@@********@@          @@      
--@@@@********@@          @@      
--@@@@@@********@@      @@@@@@@@  
--  @@@@@@******@@@@@@@@@@@@@@@@  
--    @@@@@@@@@@@@@@@@@@@@@@****@@
--  @@****@@@@@@@@@@@@@@@@******@@
--@@@@**@@@@@@@@@@@@@@@@@@******@@
--@@****@@@@@@@@@@@@@@@@@@******@@
--@@****@@@@@@@@@@      @@****@@@@
--@@****@@@@              @@@@@@  
--  @@@@                          
--
-- "Yahoo! Nothing-a useful down here!"