
;; ----------------------------------------------------------------------------
;; Blitz-Irrlicht 3rd Person Demo by Ed Musolino (AquaLung 2014)
;; Blitz-Irrlicht 1.8.3 Interface by Ed Musolino (AquaLung 2016)
;; Based off of Frank Dodd's Irrlicht 1.7 Freebasic Wrapper (2006-2010)
;; ----------------------------------------------------------------------------

;;  This software is provided 'as-is', without any express Or implied
;;  warranty.  In no event will the authors be held liable For any damages
;;  arising from the use of this software.

;;  Permission is granted To anyone To use this software For any purpose,
;;  including commercial applications, And To alter it And redistribute it
;;  freely, subject To the following restrictions:

;;  1. The Origin of this software must Not be misrepresented; you must not
;;     claim that you wrote the original software. If you use this software
;;     in a product, an acknowledgement in the product documentation would be
;;     appreciated but is Not required.
;;  2. Altered source versions must be clearly marked as such, And must Not be
;;     misrepresented as being the original software.
;;  3. This notice may Not be removed Or altered from any source distribution.
;;
;; /////////////////////////////////////////////////////////////////////////////

Include "../_libs/bIrrlicht.bb"

;	Create a type for future network expansion
Type Players
	Field Node%
	Field Pivot%
	Field Name$
	Field X#
	Field Y#
	Field Z#
	Field Yaw%
	Field Jumping% = False
	Field JumpTime%
	Field Shadow%
End Type

; --------------------------------------------------------------------------------
; Create Cos\Sin lookup tables so we don't have To calculate this every game loop
;  When tested, an Array of Floats is slightly faster than a Bank of Floats,
;   probably because of having to calculate the Bank Offset. We'll use Arrays.
; --------------------------------------------------------------------------------
Dim Cos_Table#(360)
Dim Sin_Table#(360)

For i = 0 To 360
	Cos_Table(i) = Cos(i)
	Sin_Table(i) = Sin(i)
Next
; --------------------------------------------------------------------------------

Const DeviceType% = IRR_EDT_DIRECT3D9
Const ScreenWidth% = 1280
Const ScreenHeight% = 800
Const Gravity# = 0.2
Const MaxVel# = 5.0

Global BILogo%
Global IrrlichtLogo%
Global Terrain%
Global TerrainNode%
Global TerrainTexture0%
Global TerrainTexture1%
Global TerrainHeightMap%
Global terrainColor%
Global TerrainHeightY#

Global Camera%
Global CameraNode%
Global CameraPivotNode%
Global CameraPivotYaw% = 0
Global CameraPivotX% = -200
Global CameraPivotY% = 100
Global CameraPivotZ% = 0

Global CloudTexture%
Global CloudNode%

Global WaterMesh%
Global WaterNode%
Global WaterTexture%
Global WaterDepth% = 300
Global BelowWater = False

Global Local_Player.Players = New Players
Global CurrentAnim%
Global LastAnim%
Global RunSpeed# = 1.0
Global xAcc#
Global zAcc#
Global xVel#
Global zVel#
Global FirstMouse% = True
Global ModelRotation% = 0
Global ModelYawOffset% = 0

Global grassMap%
Global grassTexture%
Global grassNode%

Global Light%
Global FlareNode%
Global FlareTexture%
Global Sun%

Global BitmapFont%
Global AppFPS% = 60
Global FramePeriod = 1000 / AppFPS
Global FrameTime = MilliSecs () - FramePeriod

Global TerrainCollision%
Global WaterCollision%

; -----------------------------------------------------------------------------
; Fire Up Irrlicht
; -----------------------------------------------------------------------------
Select DeviceType
	Case IRR_EDT_OPENGL
		iUseOpenGL()
		
	Case IRR_EDT_SOFTWARE
		iUseSoftware()
		
	Default
		iUseDirect3D9()
End Select

iVSync( False )
iIrr3D( ScreenWidth, ScreenHeight, 32, False )
iShadowColor( 128, 0, 0, 0 )
iAppTitle( "Blitz-Irrlicht 3rd Person Demo by AquaLung (Irrlicht 1.8.3)" )

; add a zip file that is passwor protected - if password is wrong, file contents will not exist.
If Not iAddZipFile( "./Blitz 3rd Person Resources.rez", IRR_IGNORE_CASE, IRR_IGNORE_PATHS, "bIrrlichtExample" )
	RuntimeError( "File Not Found!" ) : End
End If

;BitmapFont = bLoadFont ( "./media/bitmapfont.bmp" )
BILogo = iLoadTexture( "./media/BlitzIrrlichtLogoMD.png" )
IrrlichtLogo = iLoadTexture( "./media/IrrlichtLogo.png" )

; -----------------------------------------------------------------------------
; Fire Up IrrKlang
; -----------------------------------------------------------------------------
iKlangInit()

; -----------------------------------------------------------------------------
; Add Clouds
; -----------------------------------------------------------------------------
CloudTexture = iLoadTexture( "./media/cloud4.png" )
CloudNode = iCreateClouds( CloudTexture, 5, 1, 300 )
iNodeMaterialFlag( CloudNode, IRR_EMF_FOG_ENABLE, True )
iPositionNode( CloudNode, 0,2000,0 )
iRotationAnimator( CloudNode, 0, 0.02, 0 )

; -----------------------------------------------------------------------------
; Add Terrain	257 X 257
; -----------------------------------------------------------------------------
TerrainTexture0 = iLoadTexture( "./media/island.jpg" )
TerrainTexture1 = iLoadTexture( "./media/Detailmap3.jpg" )
Terrain = iLoadTerrain( "./media/iheight.jpg",0,0,0,0,0,0,1,1,1,255,255,255,255,2,5,ETPS_17 )
If Not Terrain
	iEndIrr3D()
	RuntimeError("Failed to create the Terrain!")
End If
iScaleTerrainTexture( Terrain, 1.0, 40.0 )
TerrainNode = Terrain
iScaleNode( TerrainNode, 40.0, 4, 40.0 ) 
iNodeTexture( TerrainNode, TerrainTexture0, 0 )
iNodeTexture( TerrainNode, TerrainTexture1, 1 )
iNodeMaterialType ( TerrainNode, IRR_EMT_DETAIL_MAP )
iNodeMaterialFlag( TerrainNode, IRR_EMF_LIGHTING, True )
iNodeMaterialFlag( TerrainNode, IRR_EMF_FOG_ENABLE, True )
; SETUP TERRAIN FOR RAY COLLISION TESTING
TerrainCollision = iGetCollisionGroupFromTerrain( TerrainNode, 0 )

; -----------------------------------------------------------------------------
; Add Grass
; -----------------------------------------------------------------------------
TerrainHeightMap = iLoadImage( "./media/iheight.jpg" )
terrainColor = iLoadImage( "./media/island.jpg" )
grassMap = iLoadImage( "./media/island.jpg" )
grassTexture = iLoadTexture( "./media/grass.png" )
For x = 0 To 3
    For y = 0 To 3
        grassNode = iCreateGrass( TerrainNode, x, y, 1100 * 3, 2.0, True, 0.3, 500, 0, 0, TerrainHeightMap, terrainColor, grassMap, grassTexture )
        iGrassDensity ( grassNode, 300, 4000 )
        iGrassWind ( grassNode, 3.0, 1.0 )
        
        iNodeMaterialFlag( grassNode, IRR_EMF_LIGHTING, True )
        iNodeMaterialFlag( grassNode, IRR_EMF_FOG_ENABLE, True )
    Next
Next

; -----------------------------------------------------------------------------
; Add Water
; -----------------------------------------------------------------------------
WaterMesh = iCreateHillPlaneMesh( "Water", 256.0, 256.0, 32, 32,  0,  0.0, 4, 3, 8, 8 )
WaterNode = iCreateWater( WaterMesh,2.0,300.0,10.0,False,-1,0,WaterDepth,0,0,0,0,1.0,1.0,1.0)
WaterTexture = iLoadTexture( "./media/water.jpg" )
iScaleNode( WaterNode, 5, 1, 5 )
iNodeTexture( WaterNode, WaterTexture, 0 )
iNodeTexture( WaterNode, WaterTexture, 1 )
iNodeMaterialFlag( WaterNode, IRR_EMF_LIGHTING, True )
iNodeMaterialType ( WaterNode, IRR_EMT_REFLECTION_2_LAYER )
iNodeMaterialFlag( WaterNode, IRR_EMF_FOG_ENABLE, True )
; SETUP WATER FOR RAY COLLISION TESTING
WaterCollision = iGetCollisionGroupFromComplexMesh( WaterMesh, WaterNode, 0 )

; -----------------------------------------------------------------------------
; Add Fog
; -----------------------------------------------------------------------------
iCameraFog ( 128,128,255, IRR_EXPONENTIAL_FOG, 0.0,4000.0, 0.5 )

; -----------------------------------------------------------------------------
; 3rd Person Camera
; -----------------------------------------------------------------------------
CameraPivotNode = iCreatePivot()
Camera = iCreateCamera( False )
iCameraRange( Camera, 12000, 1.0 )
CameraNode = Camera

; -----------------------------------------------------------------------------
; Add Local Player
; -----------------------------------------------------------------------------
Local_Player\Pivot = iCreatePivot()
Local_Player\Node = iLoadAnimMesh( "./media/zumlin.md2", False )
MeshTexture = iLoadTexture( "./media/zumlin.pcx" )
iNodeTexture( Local_Player\Node, MeshTexture, 0 )
MeshTexture = iLoadTexture( "./media/ParticleGrey.tga" )
iNodeTexture( Local_Player\Node, MeshTexture, 1 )
iNodeMaterialFlag( Local_Player\Node, IRR_EMF_LIGHTING, True )
iPlayMD2Animation( Local_Player\Node, IRR_EMAT_STAND )
Local_Player\Shadow = iNodeShadow( Local_Player\Node )

; -----------------------------------------------------------------------------
; Set Parent Child Relationships
; -----------------------------------------------------------------------------
iNodeParent(CameraNode, CameraPivotNode)
iNodeParent(CameraPivotNode, Local_Player\Pivot)
iNodeParent(Local_Player\Node, Local_Player\Pivot)

; -----------------------------------------------------------------------------
; Initialize Positions
; -----------------------------------------------------------------------------
iPositionNode(CameraPivotNode, CameraPivotX, CameraPivotY, CameraPivotZ)
iPositionNode(Local_Player\Pivot, 5000, 0, 3500)

; -----------------------------------------------------------------------------
; Add Sun Flare
; -----------------------------------------------------------------------------
FlareTexture = iLoadTexture( "./media/flares.jpg" )
FlareNode = iCreateFlare( FlareTexture )
iPositionNode( FlareNode, 1000,1000,500 )

Sun = iCreateBillBoard(64,64,1000,1000,500)
SunTex = iLoadTexture( "./media/sun2.tga" )
iNodeTexture( Sun, SunTex, 0 )
iNodeMaterialType ( Sun, IRR_EMT_TRANSPARENT_ALPHA_CHANNEL )
iNodeMaterialFlag( Sun, IRR_EMF_LIGHTING, False )
iNodeMaterialFlag( Sun, IRR_EMF_FOG_ENABLE, False )

; -----------------------------------------------------------------------------
; Add Light
; -----------------------------------------------------------------------------
Light = iCreateLight( False, 1000,1000,500, 0.9,0.3,0.3, 20000.0 )
iAmbientLight( 0.5, 0.5, 0.5 )

; -----------------------------------------------------------------------------
; Prepare for Main Loop
; -----------------------------------------------------------------------------
iHideMouse()
iMoveMouse( 0.5, 0.5 )
lastTick% = MilliSecs()

iCameraCLSColor( 128, 128, 255 )
iClearUnusedMeshes()

; -----------------------------------------------------------------------------
; Main Loop	-------------------------------------------------------------------
; -----------------------------------------------------------------------------
While iRun() And (Not iKeyHit( KEY_ESCAPE ))

; -----------------------------------------------------------------------------
;	Frame Timer to keep position updates in sync
; -----------------------------------------------------------------------------
	Repeat
		FrameElapsed = MilliSecs () - FrameTime
	Until FrameElapsed
	FrameTicks = FrameElapsed / FramePeriod
	For FrameLimit = 1 To FrameTicks
		FrameTime = FrameTime + FramePeriod
;	Anything affected by CPU speed goes here!
		Update_Player()    
	Next

;	BEFORE WE DRAW THE SCENE:
;	HIDE LENS FLARE WHEN CAMERA IS BEHIND TERRAIN OR UNDER WATER (Shoot a ray from the sun to the Camera)
	HitNode = iLinePick( 1000, 1000, 500, iNodeX(Camera,True), iNodeY(Camera,True), iNodeZ(Camera,True) )
	If HitNode = TerrainNode Or HitNode = WaterNode
		iHideNode( FlareNode )
	Else
		iShowNode( FlareNode )
	End If
	
;	HIDE OUR SHADOW WHEN OUR PLAYER IS BEHIND TERRAIN OR UNDER WATER (Shoot a ray from the sun to our Player)
	HitNode = iLinePick( 1000, 1000, 500, iNodeX(Local_Player\Node,True), iNodeY(Local_Player\Node,True) + 30, iNodeZ(Local_Player\Node,True) )
	If HitNode = TerrainNode Or HitNode = WaterNode
		iHideNode( Local_Player\Shadow )
		iNodeMaterialType ( Local_Player\Node, IRR_EMT_DETAIL_MAP )
		iNodeMaterialFlag( Local_Player\Node, IRR_EMF_LIGHTING, False )
	Else
		iShowNode( Local_Player\Shadow )
		iNodeMaterialType ( Local_Player\Node, IRR_EMT_SOLID )
		iNodeMaterialFlag( Local_Player\Node, IRR_EMF_LIGHTING, True )
	End If
; -----------------------------------------------------------------------------
; Begin the Scene
; -----------------------------------------------------------------------------
    iBeginScene()
	
; -----------------------------------------------------------------------------
;	Draw 3D Stuff Here
; -----------------------------------------------------------------------------
    iDrawScene()

; -----------------------------------------------------------------------------
;	Draw 2D Stuff Here
; -----------------------------------------------------------------------------
	iDrawGUI()
	iDrawImageElement( BILogo,( ScreenWidth - 200 ), ScreenHeight - 64,0,0,200,64, True )
	iDrawImageElement( IrrlichtLogo,0, ScreenHeight - 74,0,0,256,74, False )
	
	Info$ = "X: "+Int(Local_Player\X)+"   Z: "+Int(Local_Player\Z)
	iText ( BitmapFont, Info$, (ScreenWidth/2) - Len(Info) , 10, 500, 30 )
	Info$ = "HEADING: "+Str(CameraPivotYaw)
	iText ( BitmapFont, Info$, (ScreenWidth/2) - Len(Info), 40, 500, 60 )
	Info$ = "FPS: "+Str(iGetFPS())
	iText ( BitmapFont, Info$, 10, 20, 50, 40 )
	Info$ = "IRRLICHT RUN TIME: "+Str(iTime())
	iText ( BitmapFont, Info$, 10, 10, 50, 20 )

; -----------------------------------------------------------------------------
;	Finish Scene to start again
; -----------------------------------------------------------------------------
    iEndScene()
Wend
; -----------------------------------------------------------------------------
;	END GAME	---------------------------------------------------------------
; -----------------------------------------------------------------------------
iKlangEnd()
iEndIrr3D()
End
; -----------------------------------------------------------------------------


; -----------------------------------------------------------------------------
; Functions
; -----------------------------------------------------------------------------
Function Update_Player()

; -----------------------------------------------------------------------------
;	To prevent movement 'jerk' when initially pressing the mouse look, we must
;	 zero out the mouse (center screen) and then caculate second time around.
; -----------------------------------------------------------------------------
	If iMouseDown(IRR_RMOUSE_BUTTON) And FirstMouse Then
		iMoveMouse( 0.5, 0.5 )
		FirstMouse = False
		Return
	End If
	
	Local Actual_Heading% = 0
; -----------------------------------------------------------------------------
;	Get and store all current values
; -----------------------------------------------------------------------------
	Local_Player\X = iNodeX( Local_Player\Pivot, False )
	Local_Player\Y = iNodeY( Local_Player\Pivot, False )
	Local_Player\Z = iNodeZ( Local_Player\Pivot, False )
	Local_Player\Yaw = iNodeYaw( Local_Player\Pivot, False )
	CameraPivotYaw = iNodeYaw( Local_Player\Pivot, False )
	CameraPivotX = iNodeX( CameraPivotNode, False )
	CameraPivotY = iNodeY( CameraPivotNode, False )
	CameraPivotZ = iNodeZ( CameraPivotNode, False )
	xAcc = 0
	zAcc = 0
; -----------------------------------------------------------------------------
;	Check for Key Movement Input
; -----------------------------------------------------------------------------
	Select Local_Player\Jumping
		Case False
			If iKeyDown( KEY_KEY_W ) 			;Forward
				CurrentAnim = IRR_EMAT_RUN
				xAcc = RunSpeed
				zAcc = RunSpeed
				If iKeyDown( KEY_KEY_A )		;Forward and Left
					ModelRotation = 315
					ModelYawOffset = -45				
				ElseIf iKeyDown( KEY_KEY_D )	;Forward and Right
					ModelRotation = 45
					ModelYawOffset = 45				
				Else
					ModelRotation = 0
					ModelYawOffset = 0
				End If
			ElseIf iKeyDown( KEY_KEY_S )		;Backward
				CurrentAnim = IRR_EMAT_RUN
				xAcc = RunSpeed
				zAcc = RunSpeed
				If iKeyDown( KEY_KEY_A )		;Backward and Left
					ModelRotation = 225
					ModelYawOffset = -135
				ElseIf iKeyDown( KEY_KEY_D )	;Backward and Right
					ModelRotation = 135
					ModelYawOffset = 135				
				Else
					ModelRotation = 180
					ModelYawOffset = 180
				End If
			ElseIf iKeyDown( KEY_KEY_A )		;Left
				CurrentAnim = IRR_EMAT_RUN
				xAcc = RunSpeed
				zAcc = RunSpeed
				ModelRotation = 270
				ModelYawOffset = -90
			ElseIf iKeyDown( KEY_KEY_D )		;Right
				CurrentAnim = IRR_EMAT_RUN
				xAcc = RunSpeed
				zAcc = RunSpeed
				ModelRotation = 90	
				ModelYawOffset = 90			
			Else
				CurrentAnim = IRR_EMAT_STAND
				iShowMouse()
			EndIf
	End Select
; -----------------------------------------------------------------------------
;	Check for jump
; -----------------------------------------------------------------------------
	If (Not Local_Player\Jumping) And iKeyHit( KEY_SPACE )
		CurrentAnim = IRR_EMAT_JUMP
		Local_Player\Jumping = True
		Local_Player\JumpTime% = MilliSecs()
	End If
	If Local_Player\Jumping And (MilliSecs() > Local_Player\JumpTime + 400)
		Local_Player\Jumping = False
		CurrentAnim = IRR_EMAT_STAND
	End If
; -----------------------------------------------------------------------------
;	Check Mouse Movement Input
; -----------------------------------------------------------------------------
	If iMouseDown(IRR_RMOUSE_BUTTON) And (Not FirstMouse)
		iHideMouse()
		MouseXMove = ((ScreenWidth/2) - iMouseX()) * 0.5
		MouseYMove = ((ScreenHeight/2) - iMouseY()) * 0.5
		iMoveMouse( 0.5, 0.5 )
		CameraPivotY = CameraPivotY - MouseYMove
		CameraPivotYaw = CameraPivotYaw - MouseXMove
		If iMouseDown(IRR_LMOUSE_BUTTON) Then
			If Not Local_Player\Jumping Then CurrentAnim = IRR_EMAT_RUN
			xAcc = RunSpeed
			zAcc = RunSpeed
			ModelRotation = 0
			ModelYawOffset = 0
		End If
	Else
		FirstMouse = True
	End If
;	Set a min and max for the camera height
	If CameraPivotY < -20 Then CameraPivotY = -20
	If CameraPivotY > 300 Then CameraPivotY = 300
	CameraPivotX = CameraPivotX + (iMouseWheel() * 15)
;	Set a min and max on mouse zoom
	If CameraPivotX < -400 Then CameraPivotX = -400
	If CameraPivotX > -110 Then CameraPivotX = -110
; -----------------------------------------------------------------------------	
;	Update Velocities
; -----------------------------------------------------------------------------
;	First check To ensure there was an acceleration
	If xAcc <> 0 Then
		xVel = xAcc * FrameTime
		If Abs(xVel) > MaxVel Then xVel = Sgn(xVel) * MaxVel
	Else
;	Otherwise If Not, lets fake friction
		If Not Local_Player\Jumping
			xVel = xVel * Gravity
			If Abs(xVel) < .001 Then xVel = 0
		End If
	End If	
	If zAcc <> 0 Then
		zVel = zAcc * FrameTime
		If Abs(zVel) > MaxVel Then zVel = Sgn(zVel) * MaxVel
	Else
		If Not Local_Player\Jumping
			zVel = zVel * Gravity
			If Abs(zVel) < .001 Then zVel = 0
		End If
	End If
; -----------------------------------------------------------------------------	
;	Check for Animation Change and apply if necessary	
; -----------------------------------------------------------------------------
	If LastAnim <> CurrentAnim Then
		iPlayMD2Animation( Local_Player\Node, CurrentAnim )
		LastAnim = CurrentAnim
	End If
; -----------------------------------------------------------------------------	
;	Filter Compass Heading 0 to 359
; -----------------------------------------------------------------------------
	While CameraPivotYaw > 359
		CameraPivotYaw = CameraPivotYaw - 359
	Wend
	While CameraPivotYaw < 0
		CameraPivotYaw = CameraPivotYaw +359
	Wend
;	CameraPivotYaw = CameraPivotYaw Mod 360

;	Lets keep our Actual_Heading within the limits of the Sin And Cos Tables (0-359)
	Actual_Heading = Local_Player\Yaw + ModelYawOffset
	If Actual_Heading > 359 Then Actual_Heading = Actual_Heading - 359
	If Actual_Heading < 0 Then Actual_Heading = Actual_Heading + 359
; -----------------------------------------------------------------------------
;	Caculate new player position based on vel and yaw
; -----------------------------------------------------------------------------
	Local_Player\X = Local_Player\X + ( Cos_Table( Actual_Heading ) * xVel )
	Local_Player\Z = Local_Player\Z - ( Sin_Table( Actual_Heading ) * zVel )
; -----------------------------------------------------------------------------
;   Calculate Terrain Height for player x and z location
; -----------------------------------------------------------------------------
	TerrainHeightY = iTerrainHeight( TerrainNode, Local_Player\X, Local_Player\Z )
;	If the player walks off the map limit them to Terrain Height of 0
	If TerrainHeightY < 0 Then TerrainHeightY = 0
; -----------------------------------------------------------------------------
;	CHECK FOR CAMERA BELOW WATER AND FLIP WATER MESH WHEN NEEDED
; -----------------------------------------------------------------------------
	If iNodeY( CameraNode, True ) > ( WaterDepth - 5 ) And BelowWater = True
		CameraPivotY = CameraPivotY + 10
		BelowWater = False
		iFlipNodeMesh( WaterNode )
		iCameraCLSColor( 128, 128, 255 )
		iShadowColor( 128, 0, 0, 0 )
		iCameraFog ( 128,128,255, IRR_EXPONENTIAL_FOG, 500.0,3000.0, 0.5 )
		iNodeMaterialFlag( Local_Player\Node, IRR_EMF_FOG_ENABLE, False )
	ElseIf iNodeY( CameraNode, True ) < (WaterDepth + 5) And BelowWater = False
		CameraPivotY = CameraPivotY - 10
		BelowWater = True
		iFlipNodeMesh( WaterNode )
		iCameraCLSColor( 49, 165, 190 )
		iShadowColor( 0, 0, 0, 0 )
		iCameraFog ( 49, 165, 190, IRR_EXPONENTIAL_FOG, 0.0,200.0, 0.5 )
		iNodeMaterialFlag( Local_Player\Node, IRR_EMF_FOG_ENABLE, True )
	End If
; -----------------------------------------------------------------------------
;	Re-position everything
; -----------------------------------------------------------------------------
	iPositionNode( CameraPivotNode, CameraPivotX, CameraPivotY, 0 )
	iRotateNode( Local_Player\Pivot, 0, CameraPivotYaw, 0 )
	iRotateNode( Local_Player\Node, 0, ModelRotation, 0 )
	iCameraTarget( CameraNode,Local_Player\X,Local_Player\Y+40,Local_Player\Z )
	iPositionNode( Local_Player\Pivot, Local_Player\X, TerrainHeightY + 25, Local_Player\Z )

End Function