Subversion Repositories forest

Rev

Blame | Last modification | View Log | RSS feed

rem Nightmare Forest by Daniel Marschall
rem Revision: 31 May 2018

rem ---------------------

rem SPRUNG-PHYSIK:
rem - wenn man auf einem baum landet, kann man von dort aus nicht springen, und glitched manchmal auf dem boden
rem - im baum wird man trotzdem von skorpionen angefressen
rem ... scheinbar ist die jumpheight = 0 dann!

rem Der Himmel ist zu niedrig. Die Tannen sieht man nicht bis ganz oben

rem TODO: clone -> INSTANCE 

rem TODO: scorpion schiebt stehenden spieler nicht weg

rem TODO: scorpion soll nicht so nah rankommen, sodass er im spieler stehen würde, wenn der spieler eingeklemmt ist

rem TODO: ambient light wird falsch verwendet. es ist viel zu blau, und alle objekte haben es deaktiviert, weil es die farben zerstört

rem TODO: screen or desktop?
rem TODO: mehr konfigurierbar machen
rem TODO: alles einrücken

rem TODO: lahm?
rem TODO: map außenlinie ist nicht gesperrt mit bäumen
rem TODO: überall float verwenden

rem TODO
rem - HUD
rem - schwimmen
rem - im wasser langsamer
rem - menü am anfang und pause mit maus bedienen
rem - rascheln im busch
rem - die gegner kommen erst nach einer bestimmten zeit. spawnen weit weg vom spieler
rem - tag und nachtzeiten?

rem NICE TO HAVE
rem - radar breitbild
rem - wassermatrix bewegt sich nur in 1 richtung
rem - mausumkehrung einstellbar
rem - stars.bmp (5000px) braucht lange zum laden. 1000px ist verpixelt
rem - besserer bluteffekt

rem ToDo:
rem   Radar wackelt
rem   :startgame weglassen
rem   Streifen am Boden und am Himmel...
rem   Baum teiltransparenz
rem   Radar lieber als Matrix. Viele Probleme wie z.B. mit Skorpion transparenz
rem   Unscharfes Radar, Radar ganz dyn. zeichnen? Rader stay on top
rem   Menü: musik aus --> an: loop von position 0!

// --------------------------------
// Numerical Resources
// --------------------------------
rem TODO: vervollständigen
rem TODO: auch dateinamen als konstanten

#constant IMG_ForestFloor 1
#constant IMG_Stars 2
#constant IMG_Water 3
#constant IMG_Tree 4
#constant IMG_MapBackground 5
#constant IMG_PauseScreen 95
#constant IMG_TitleScreen 96
#constant IMG_BLOOD 97

#constant OBJ_Skybox 2
#constant OBJ_Tent 10
#constant OBJ_MapGhostObject 51
#constant OBJ_TreeTemplate 52
#constant OBJ_CollissionDummy 53
#constant OBJ_BloodParticle_Base 100
#constant OBJ_Monster_Base 1000
#constant OBJ_Tree_Base 1500

#constant MSH_TreeTemplate 52

#constant MAT_Ground 1
#constant MAT_Water 3

#constant SFX_ForestBG 1
#constant SFX_PauseMusic 5
#constant SFX_Walk_Earth 10
#constant SFX_Walk_Water 11
#constant SFX_Attack_Base 20
#constant SFX_Monster_Base 1000

#constant DLL_MapGen 1
#constant DLL_DBIni 2
#constant DLL_Resize32 3
    
#constant MEM_Map 1
#constant MEM_ResizeSrc 2
#constant MEM_ResizeDest 3

// --------------------------------
// File names
// --------------------------------

#constant FN_DLL_Resize32 "dll_resize\Resize32.dll"
#constant FN_DLL_MapGen32 "mapgen\mapgen32.dll"
#constant FN_DLL_DBIni "cfgread\dbini32.dll"

#constant FN_SFX_ForestBG "sound\forestbg.wav"
#constant FN_SFX_PauseMusic "music\pause.wav"
#constant FN_SFX_Walk_Earth "sound\walk.wav"
#constant FN_SFX_Walk_Water "sound\walk_water.wav"
#constant FN_SFX_ScorpWalk "test.wav"
#constant FN_SFX_ScorpionAttack1 "sound\scorpionAttack\attack1.wav"
#constant FN_SFX_ScorpionAttack2 "sound\scorpionAttack\attack2.wav"
#constant FN_SFX_ScorpionAttack3 "sound\scorpionAttack\attack3.wav"
#constant FN_SFX_ScorpionAttack4 "sound\scorpionAttack\attack4.wav"

#constant FN_OBJ_ScorpIdle "obj\scorpion\ScorpIdle.x"
#constant FN_OBJ_ScorpWalk "obj\scorpion\ScorpWalk.x"
#constant FN_OBJ_Tent "obj\tent\Tent.x"
#constant FN_GFX_PauseScreen "bitmap\titlescreen.jpg"

#constant FN_GFX_MapBackground "map\ST01L01a.bmp"
#constant FN_GFX_TitleScreen "bitmap\titlescreen.jpg"
#constant FN_GFX_FloorTexture "bitmap\forest_floor_texture.jpg"
#constant FN_GFX_Stars "bitmap\stars.bmp"
#constant FN_GFX_Water "bitmap\water.bmp"
#constant FN_GFX_Tree "bitmap\tree.bmp"
#constant FN_GFX_BLOOD "bitmap\blood.png"

// --------------------------------
// Language specific
// --------------------------------

rem TODO: vervollständigen. multilang per DLL
#constant LNG_StartingGame "Starting game..."

// --------------------------------
// Read configuration
// --------------------------------

load dll FN_DLL_DBIni, DLL_DBIni
call dll DLL_DBIni, "LoadINI", "forest.ini"
framerate                   = call dll(DLL_DBIni, "ReadInt",   "Game", "framerate",                   0)
clockSpeedFactor            = call dll(DLL_DBIni, "ReadInt",   "Game", "clockSpeedFactor",            0)
clockBlinksPerSecond        = call dll(DLL_DBIni, "ReadInt",   "Game", "clockBlinksPerSecond",        0)
clockShowSeconds            = call dll(DLL_DBIni, "ReadInt",   "Game", "clockShowSeconds",            0)
beginClockSeconds           = call dll(DLL_DBIni, "ReadInt",   "Game", "beginClockSeconds",           0)
enemyRotateSmoothness       = call dll(DLL_DBIni, "ReadInt",   "Game", "enemyRotateSmoothness",       0)
maxRunEnergy                = call dll(DLL_DBIni, "ReadInt",   "Game", "maxRunEnergy",                0)
waterLevel                  = call dll(DLL_DBIni, "ReadInt",   "Game", "waterLevel",                  0)
sound3dVolumeCor            = call dll(DLL_DBIni, "ReadInt",   "Game", "sound3dVolumeCor",            0)
invertMouse                 = call dll(DLL_DBIni, "ReadInt",   "Game", "invertMouse",                 0)
test                        = call dll(DLL_DBIni, "ReadInt",   "Game", "test",                        0)
camerarange                 = call dll(DLL_DBIni, "ReadInt",   "Game", "camerarange",                 0)
fogdistance                 = call dll(DLL_DBIni, "ReadInt",   "Game", "fogdistance",                 0)
waterMovementMaxDistance    = call dll(DLL_DBIni, "ReadInt",   "Game", "waterMovementMaxDistance",    0)
gravity#                    = call dll(DLL_DBIni, "ReadFloat", "Game", "gravity",                     0.0)
JumpStartVelocity#          = call dll(DLL_DBIni, "ReadFloat", "Game", "JumpStartVelocity",           0.0)
MinFallVelocity#            = call dll(DLL_DBIni, "ReadFloat", "Game", "MinFallVelocity",             0.0)
collissionboxsize#          = call dll(DLL_DBIni, "ReadFloat", "Game", "collissionboxsize",           0.0)
playerEyeHeight#            = call dll(DLL_DBIni, "ReadFloat", "Game", "playerEyeHeight",             0.0)
cMaxTrees                   = call dll(DLL_DBIni, "ReadInt",   "Game", "cMaxTrees",                   0)
cMapSizeX                   = call dll(DLL_DBIni, "ReadInt",   "Game", "cMapSizeX",                   0)
cMapSizeZ                   = call dll(DLL_DBIni, "ReadInt",   "Game", "cMapSizeZ",                   0)
cTreeRadius                 = call dll(DLL_DBIni, "ReadInt",   "Game", "cTreeRadius",                 0)
cMaxEnemies                 = call dll(DLL_DBIni, "ReadInt",   "Game", "cMaxEnemies",                 0)
HitInterval                 = call dll(DLL_DBIni, "ReadInt",   "Game", "HitInterval",                 0)
initialEnemySafezone#       = call dll(DLL_DBIni, "ReadFloat", "Game", "InitialEnemySafezone",        0.0)
cEnemySpawnMapBorderPadding = call dll(DLL_DBIni, "ReadInt",   "Game", "EnemySpawnMapBorderPadding",  0)
call dll DLL_DBIni, "UnloadINI"
delete dll DLL_DBIni

rem Setup & Startbildschirm
if check display mode( desktop width(), desktop height(), screen depth() )
    set display mode desktop width(), desktop height(), screen depth()
endif
hide mouse

Sync On
Sync Rate framerate

LoadImageFullscreen(FN_GFX_TitleScreen, IMG_TitleScreen)

PASTE IMAGE IMG_TitleScreen, 0, 0

rem Sync twice at application start for friendliness with dbpro's double buffering
rem (Required for newer versions of DB Pro)
sync : sync

gosub _menu

_startgame:

ink rgb(255, 230, 0), 0
PASTE IMAGE IMG_TitleScreen, 0, 0
text 28, 550, LNG_StartingGame

sync

randomize timer()

rem Working variables ONLY
JumpKeyStatePrev = 0
JumpPosition# = 0.0
onGround = 1
velocityY# = 0.0
runEnergy# = maxRunEnergy
clockSeconds = beginClockSeconds
collcounter = 0
hits = 0
LastAttackSound = 0

draw to front

gosub _setup_camera
gosub _create_floor
gosub _setup_player_position
gosub _create_sky
gosub _create_trees
gosub _draw_trees
gosub _create_water
gosub _create_radar

rem Bildschirm neu aufbauen
cls
sync

rem Lichtversuch
make light 1

rem Radar vorbereiten
radarpointcolor = 150

gosub _create_enemies
gosub _setupClock
gosub _setup_blood

Rem Forest atmo
load sound FN_SFX_ForestBG, SFX_ForestBG
load sound FN_SFX_PauseMusic, SFX_PauseMusic
load sound FN_SFX_Walk_Earth, SFX_Walk_Earth
load sound FN_SFX_Walk_Water, SFX_Walk_Water

#constant NumberOfAttackSounds 4
load sound FN_SFX_ScorpionAttack1, SFX_Attack_Base+0
load sound FN_SFX_ScorpionAttack2, SFX_Attack_Base+1
load sound FN_SFX_ScorpionAttack3, SFX_Attack_Base+2
load sound FN_SFX_ScorpionAttack4, SFX_Attack_Base+3

LoadImageFullscreen(FN_GFX_PauseScreen, IMG_PauseScreen)

#constant SPREAD1_MAX 10
#constant SPREAD2_MAX 15

Disable EscapeKey

loop sound SFX_ForestBG

rem Hauptschleife
do
    gosub _handle_weather
    gosub _handle_player
    gosub _update_trees
    gosub _handle_radar
    gosub _handle_enemies
    gosub _handleClock
    gosub _handle_blood
   
    rem TODO: does not work. There is always an attack!
    set object collision on OBJ_CollissionDummy
    position object OBJ_CollissionDummy, camera position x(), camera position y(), camera position z()
    for t = 0 to cMaxEnemies-1
        if object collision(OBJ_CollissionDummy, OBJ_Monster_Base+t) = 1
            collcounter = collcounter + 1
            print "Attacking enemy: ", t
        endif
    NEXT
    print "Collision counter: ", collcounter
    set object collision off OBJ_CollissionDummy
        
    if collcounter >= HitInterval
        collcounter = 0
        inc hits, 1
        gosub _play_attack_sound
        gosub _splat_blood
    endif
    print "Hits: ", hits
    
    if EscapeKey() = 1
        repeat
            sync
        until not (EscapeKey() = 1)
        gosub _pausemenu
    endif
    sceneInitialized=1
    sync
loop

_pausemenu:
    gosub _stop_game_sounds
    loop sound SFX_PauseMusic
    show mouse 
    
    repeat
        cls
        
        paste image IMG_PauseScreen, 0, 0
                
        rem Text
        ink rgb(255,255,255),1
        text 100,100,"text"
                
        text 28, 550, "Pause. ESC=Weiter. Runter=Ende."
        sync
        
        if DownKey() = 1
            goto _exit
        ENDIF
    UNTIL EscapeKey() = 1
    repeat
        sync
    until not (EscapeKey() = 1)
    hide mouse
    stop sound SFX_PauseMusic
    loop sound SFX_ForestBG
    
    rem TODO: das bringt nix. wenn man die maus bewegt, wird nach der pause die kamera gescrollt
    set cursor 0, 0
return

_setup_camera:
    Set camera range 1, camerarange
    AUTOMATIC CAMERA COLLISION 0,collissionboxsize#,0 : rem scorpione stoßen den spieler nicht weg, wenn keine taste gedrückt wird? syntax mit 4 parametern funktioniert im neuen dbpro free nicht!
    Autocam off
    
    rem TODO: zylinder
    MAKE OBJECT SPHERE OBJ_CollissionDummy, collissionboxsize#
    rem hide object OBJ_CollissionDummy
    set object collision off OBJ_CollissionDummy
return

_setup_player_position:
    X# = cMapSizeX / 2
    Y# = floorHeight#(X#, Z#) + playerEyeHeight
    Z# = cMapSizeZ / 2
    position camera X#, Y#, Z#
    
    rem TODO zelt ist winzig klein!
    rem TODO zelt ist schwarz schattiert
    rem TODO zelt beleuchten
    Load object FN_OBJ_Tent, OBJ_Tent
    show object OBJ_Tent
    scale object OBJ_Tent, 10000, 5000, 5000
    position object OBJ_Tent, X#-20, Y#, Z#-10
return

_handle_weather:
    rem Wetter
   
    color ambient light rgb(64,64,128)
    rem set ambient light 1
    set ambient light 50
    if fog available() = 1
        fog on
        fog distance fogdistance
        fog color rgb(0, 0, 0)
    endif
   
    if test = 1
        SET MATRIX WIREFRAME ON MAT_Ground
        SET MATRIX WIREFRAME ON MAT_Water
    endif
   
    rem Stars are moving
    scroll object texture OBJ_Skybox, 0.00002, 0    
    
    rem Water movement
    rem TODO: wasser soll sich langsamer bewegen, aber das ist schwierig. wenn man die bewegung nur zu bestimmten ticks
    rem       durchführt, dann ruckelt es.
    waterdir = (waterdir + 1) mod 360;
    watermov = 0 + cos(waterdir)*waterMovementMaxDistance
    position matrix MAT_Water, watermov, waterLevel, watermov
return

_handle_enemies:
    pX# = Camera Position X()
    pZ# = Camera Position Z()
    
    rem TODO: gegner sollen sich nicht überschneiden
    
    for t = 0 to cMaxEnemies-1
        eX# = Object Position X(OBJ_Monster_Base+t)
        eY# = Object Position Y(OBJ_Monster_Base+t)
        eZ# = Object Position Z(OBJ_Monster_Base+t)
       
        eRX# = Object Angle X(OBJ_Monster_Base+t)
        eRY# = Object Angle Y(OBJ_Monster_Base+t)
        eRZ# = Object Angle Z(OBJ_Monster_Base+t)
       
        deltaX# = pX#-eX#
        deltaZ# = pZ#-eZ#

        rem TODO: cor file
        yPosOffset# = 20
        
        dist# = sqrt( deltaX#*deltaX# + deltaZ#*deltaZ# )
        
        if clockSeconds > 1
            if not sound playing(SFX_Monster_Base+t)
                set sound volume SFX_Monster_Base+t, 0
                
                rem TODO: beliebiges startoffset?
                loop sound SFX_Monster_Base+t
            endif
        endif
        
        rem The mixing of 3dsound is bullshit (too loud from far distance, and not very loud on close distance),
        rem so we mix the volume ourself
        if sound3dVolumeCor>0 and dist#<sound3dVolumeCor then set sound volume SFX_Monster_Base+t, (1-(dist#/sound3dVolumeCor))*100
        
        rem TODO: 100 dynamisch machen
        rem at least a distance of 10 so that atanfull() does not bug, when deltas becomes 0
        rem Verhindern, dass der Skorpion in einem "drin" steht. Er soll mit den Scheren angreifen
        scorpMinDistance = 100
        
        if dist# > scorpMinDistance
            if dist# < enemy(t).AwarenessRadius
                enemy(t).Angle = atanfull(deltaX#, deltaZ#)
                enemy(t).Angle = WrapValue(enemy(t).Angle+180)
                enemy(t).CurSpeed = enemy(t).MaxSpeed
            else
                if enemy(t).WalkTimeout <= 0
                    enemy(t).WalkTimeout = RndBetween(enemy(t).WalkTimeoutMin, enemy(t).WalkTimeoutMax)
                    enemy(t).Angle = RndBetween(0,359)
                    enemy(t).CurSpeed = RndBetween(0, enemy(t).MaxSpeed)
                ENDIF
                enemy(t).WalkTimeout = enemy(t).WalkTimeout - 1
            endif
            
            if enemy(t).CurSpeed = 0
                gosub scorpChangeToIdle
            else
                gosub scorpChangeToWalk
                rem TODO: also make animation slower/faster
            endif

            a# = WrapValue(enemy(t).Angle + 180)
            XTest# = Newxvalue(eX#, a#, enemy(t).CurSpeed)
            ZTest# = Newzvalue(eZ#, a#, enemy(t).CurSpeed)
            if XTest# > 0 and XTest# < cMapSizeX then eX# = XTest#
            if ZTest# > 0 and ZTest# < cMapSizeZ then eZ# = ZTest#
            if XTest# <= 0 or XTest# >= cMapSizeX or ZTest# <= 0 or ZTest# >= cMapSizeZ
                rem Kehrtwende, um den Kartenrand zu verlassen
                enemy(t).Angle = WrapValue(enemy(t).Angle+180)
                enemy(t).WalkTimeout = RndBetween(enemy(t).WalkTimeoutMin, enemy(t).WalkTimeoutMax)
                enemy(t).CurSpeed = RndBetween(0, enemy(t).MaxSpeed)
            endif
    
            eY# = floorHeight#(eX#, eZ#) + yPosOffset#
            Position Object OBJ_Monster_Base+t, eX#, eY#, eZ#
            Position Sound SFX_Monster_Base+t, eX#, eY#, eZ#
            
            eRY# = smoothRotate#(eRY#, enemy(t).Angle, enemyRotateSmoothness)
            rotate object OBJ_Monster_Base+t, eRX#, eRY#, eRZ#
        endif
    next
return

#constant GROUNDTYPE_AIR 0
#constant GROUNDTYPE_EARTH 1
#constant GROUNDTYPE_WATER 2
rem #constant GROUNDTYPE_DEEPWATER 3
function groundType(onGround, waterLevel)
        if onGround = 0
                ret = GROUNDTYPE_AIR
        else
        X# = camera position x()
        Z# = camera position z()
                if floorHeight#(X#,Z#) > waterLevel
                        ret = GROUNDTYPE_EARTH
                else
                        ret = GROUNDTYPE_WATER
                endif
        endif
endfunction ret

_handle_player:
    set cursor 0, 0
    oldcAY# = cAY#
    oldcAX# = cAX#
   
    X# = camera position x()
    Y# = camera position y()
    Z# = camera position z()

    if invertMouse = 1
        cAX# = WrapValue(cAX# - MousemoveY() * 0.2)
    else
        cAX# = WrapValue(cAX# + MousemoveY() * 0.2)
    endif
    cAY# = WrapValue(cAY# + MousemoveX() * 0.2)
    cAZ# = Camera angle Z()   

    rem Sprungroutine
    rem TODO: + Anlaufsprung
   
    JumpKeyStateNow=jumpPressed()
    if (JumpKeyStatePrev=0) and (JumpKeyStateNow=1)
        gosub PressJumpKey
    ENDIF
    if (JumpKeyStatePrev=1) and (JumpKeyStateNow=0)
        gosub ReleaseJumpKey
    ENDIF
    JumpKeyStatePrev = JumpKeyStateNow
    gosub JumpUpdate
       
    rem Laufen
    speedboost# = 1
    
    gt = groundType(onGround, waterLevel)
    
    if upPressed()+downPressed()+leftPressed()+rightPressed() = 0
        runEnergy# = runEnergy# + 2
        stop sound SFX_Walk_Earth
        stop sound SFX_Walk_Water
    else
                if gt = GROUNDTYPE_EARTH
                        stop sound SFX_Walk_Water
                if not sound playing(SFX_Walk_Earth)
                loop sound SFX_Walk_Earth
                endif
                endif
                if gt = GROUNDTYPE_WATER
                        stop sound SFX_Walk_Earth
                if not sound playing(SFX_Walk_Water)
                loop sound SFX_Walk_Water
                endif
                endif
        if gt = GROUNDTYPE_AIR
                stop sound SFX_Walk_Earth
                stop sound SFX_Walk_Water
        endif
                energyChanged = 0
        if ShiftKey()+ControlKey() = 1 : rem shift+control at the same time = walk normal
            rem TODO: es ruckelt trotzdem noch
            if ControlKey()=1 and runEnergy#>0 : rem Energy >1 , damit es nicht ruckelt (run-walk-run-walk-run-walk) . 3 = walk regain
                speedboost# = 2
                runEnergy# = runEnergy# - 1
                energyChanged = 1
            endif
            if ShiftKey()=1
                speedboost# = 0.5
                runEnergy# = runEnergy# + 0.5
                energyChanged = 1
            endif
        endif
        if not energyChanged then runEnergy# = runEnergy# + 1
        if upPressed()
            XTest# = Newxvalue(X#, cAY#, 7*speedboost#)
            ZTest# = Newzvalue(Z#, cAY#, 7*speedboost#)
            gosub _WorldBoundCheck
        endif
        if downPressed()
            XTest# = Newxvalue(X#, Wrapvalue(cAY# - 180), 5*(speedboost#/2))
            ZTest# = Newzvalue(Z#, Wrapvalue(cAY# - 180), 5*(speedboost#/2))
            gosub _WorldBoundCheck
        endif
        if leftPressed()
            XTest# = Newxvalue(X#, Wrapvalue(cAY# - 90), 5*(speedboost#/1.3))
            ZTest# = Newzvalue(Z#, Wrapvalue(cAY# - 90), 5*(speedboost#/1.3))
            gosub _WorldBoundCheck
        endif
        if rightPressed()
            XTest# = Newxvalue(X#, Wrapvalue(cAY# + 90), 5*(speedboost#/1.3))
            ZTest# = Newzvalue(Z#, Wrapvalue(cAY# + 90), 5*(speedboost#/1.3))
            gosub _WorldBoundCheck
        endif
    endif
    if runEnergy# > maxRunEnergy then runEnergy# = maxRunEnergy
    print "Run energy: ", runEnergy#

    Rem Rotate camera
    cTestX# = WrapValue(cAX# - 180)
    if cTestX# > 225 then cAX# = 45
    if cTestX# < 135 then cAX# = 315
    YRotate camera CurveAngle(cAY#, oldcAY#, 24)
    XRotate camera CurveAngle(cAX#, oldcAX#, 24)

    Rem Position Camera
    if onGround=1
        Y#=floorHeight#(X#, Z#)
    else
        Y#=JumpPosition#
    ENDIF
    inc Y#, playerEyeHeight#
    Position Camera X#, Y#, Z#

    Rem Position Listener
    Position Listener X#, Y#, Z#
    Rotate Listener 0, cAY#, 0   : rem todo warum 0 statt cAZ# etc ?

    rem Sky sphere follows player
rem    position object OBJ_Skybox, X#, Y#, Z#
    position object OBJ_Skybox, X#, FloorHeight#(X#,Z#), Z#
    
    
    
rem Sollte man aus irgendeinem Grund die Skybox verlassen, soll man trotzdem zur Erde schauen können
backdrop on
color backdrop rgb(0,0,128) : rem TODO: schwarz + auslagern
set object cull OBJ_Skybox, 1

rem texture backdrop IMG_Stars
rem hide object OBJ_Skybox
rem set object fog OBJ_Skybox, 0

    rem TEST: extend skybox if player jumps
    if onGround = 0
        jumpheight#=Y#-FloorHeight#(X#,Z#)
    else
        jumpheight#=0
    endif

    rem TODO: beim springen sieht man am boden den backdrop    
    
    rem 500 = die originalgröße der box, vor der ersten scalisierung (setup sky)
    rem 600,120,600 = die originale skalierung als referenz
    rem *2 , da es von beiden seiten erweitert wird
    scalX# = 100.0*(500.0*6.00 + jumpheight#*2)/500.0
    scalY# = 100.0*(500.0*1.20 + jumpheight#*2)/500.0
    scalZ# = 100.0*(500.0*6.00 + jumpheight#*2)/500.0
    scale object OBJ_Skybox, scalX#, scalY#, scalZ#
return    

_WorldBoundCheck:
    if XTest# > 0 and XTest# < cMapSizeX then X# = XTest#
    if ZTest# > 0 and ZTest# < cMapSizeZ then Z# = ZTest#
return
    
_update_trees:
    if (ceil(X#) mod 10 = 0) or (ceil(Z#) mod 10 = 0) or (sceneInitialized=0)
        for t = 0 to cMaxTrees-1
            rx = tree(t).x
            rz = tree(t).z
            incircle = IsInCircle(rx, rz, X#, Z#, camerarange)
            rem incircle = IsInCircle(rx, rz, X#, Z#, fogdistance)
            if incircle <> tree(t).drawn
                if incircle = 1
                    if object exist(OBJ_Tree_Base+t)
                        show object OBJ_Tree_Base+t
                    else
                        make object OBJ_Tree_Base+t, MSH_TreeTemplate, IMG_Tree
                        position object OBJ_Tree_Base+t, rx, floorHeight#(rx, rz)+190, rz
                        set object OBJ_Tree_Base+t, 1, 1, 0
                        rotate object OBJ_Tree_Base+t, 0, RndBetween(0,359), 0
                    endif
                else
                    delete object OBJ_Tree_Base+t
                    rem hide object OBJ_Tree_Base+t
                endif 
            ENDIF      
            tree(t).drawn = incircle
        next t
    endif
return

_fademenu:
    if fade# > 100 then fadedir = 0
    if fade# < 50 then fadedir = 1
    if fadedir = 1
        fade# = fade# + 2.5
    else
        fade# = fade# - 2.5
    endif
    rem Beleuchtet
    ink rgb(255, fade#, 0), 0
    set text font "Tahoma"
    set text size 26
    if menu = 1
        if pos = 1 then text 260, 255, "Neues Spiel starten"
        if pos = 2 then text 260, 285, "Spiel laden"
        if pos = 3 then text 260, 315, "Einstellungen"
        if pos = 4 then text 260, 345, "Spiel beenden"
    endif
    if menu = 3
        if pos = 1
            if sound = 1 then text 260, 255, "Ton: An"
            if sound = 0 then text 260, 255, "Ton: Aus"
        endif
        if pos = 2
            if music = 1 then text 260, 285, "Musik: An"
            if music = 0 then text 260, 285, "Musik: Aus"
        endif
        if pos = 3 then text 260, 315, "Hauptmenü"
    endif
    rem Normal
    ink rgb(255, 230, 0), 0
    if menu = 1
        if pos <> 1 then text 260, 255, "Neues Spiel starten"
        if pos <> 2 then text 260, 285, "Spiel laden"
        if pos <> 3 then text 260, 315, "Einstellungen"
        if pos <> 4 then text 260, 345, "Spiel beenden"
    endif
    if menu = 3
        if pos <> 1
            if sound = 1 then text 260, 255, "Ton: An"
            if sound = 0 then text 260, 255, "Ton: Aus"
        endif
        if pos <> 2
            if music = 1 then text 260, 285, "Musik: An"
            if music = 0 then text 260, 285, "Musik: Aus"
        endif
        if pos <> 3 then text 260, 315, "Hauptmenü"
    endif
    sync
return

_handle_radar:
        rem TODO: the player dot should always stay at the same position
        rem TODO: don't show far away enemies
        
    rem Leuchtpunkt
    if radarpointrev = 0
        inc radarpointcolor, 3
    else
        dec radarpointcolor, 3
    endif
    if radarpointcolor > 255
        radarpointrev = 1
        radarpointcolor = 254
    endif
    if radarpointcolor < 150
        radarpointrev = 0
        radarpointcolor = 149
    endif
    rem Bäume
    for t = 0 to cMaxTrees-1
        if tree(t).drawn
                x = tree(t).x
                y = tree(t).z

                x0 = camera position x()
                y0 = camera position z()
                ang = camera angle y()
                tx = x0 + (x-x0)*cos(ang) - (y-y0)*sin(ang)
                        ty = y0 + (x-x0)*sin(ang) + (y-y0)*cos(ang)
                        x = tx
                        y = ty
                
            x = (x / 62.5) + 7.5
                y = screen height() - 7.5 - (y / 62.5)
                
            rem ink rgb(0, radarpointcolor, 0), 0
            ink rgb(0, 50, 0), 0
            circle x, y, 1
            circle x, y, 0
        endif
    next i
    rem Player
    x = (X# / 62.5) + 7.5
    y = screen height() - 7.5 - (Z# / 62.5)
    ink rgb(0, 0, radarpointcolor), 0
    circle x, y, 1
    circle x, y, 0
    rem Enemies
    for t = 0 to cMaxEnemies-1
        x = object position x(OBJ_Monster_Base+t)
        y = object position z(OBJ_Monster_Base+t)
        
            x0 = camera position x()
            y0 = camera position z()
            ang = camera angle y()
            tx = x0 + (x-x0)*cos(ang) - (y-y0)*sin(ang)
                ty = y0 + (x-x0)*sin(ang) + (y-y0)*cos(ang)
                x = tx
                y = ty
        
        x = (x / 62.5) + 7.5
        y = screen height() - 7.5 - (y / 62.5)
        
        ink rgb(radarpointcolor, 0, 0), 0
        circle x, y, 1
        circle x, y, 0
    next
return

_draw_trees:
    rem TODO: da ist ein baum an offset 0,0,0
    load image FN_GFX_Tree, IMG_Tree
    
    make object plain OBJ_TreeTemplate, 2*cTreeRadius, 400
    make mesh from object MSH_TreeTemplate, OBJ_TreeTemplate
    add limb OBJ_TreeTemplate, 1, MSH_TreeTemplate
    rotate limb OBJ_TreeTemplate, 1, 0, 90, 0
    make mesh from object MSH_TreeTemplate, OBJ_TreeTemplate
    delete object OBJ_TreeTemplate
return

_create_trees:
    dim tree(cMaxTrees-1) as treeType
    
    remstart
    for t = 0 to cMaxTrees-1
        tree(t).x = RndBetween(0, cMapSizeX) 
        tree(t).z = RndBetween(0, cMapSizeZ) 
        tree(t).drawn = 0
    NEXT
    remend
       
    rem TODO: wegbreite definieren
    load dll FN_DLL_MapGen32, DLL_MapGen
    call dll DLL_MapGen, "LoadParametersFromINI", "forest.ini"

    load dll FN_DLL_DBIni, DLL_DBIni
    call dll DLL_DBIni, "LoadINI", "seed.ini"
    onetimeseed = call dll(DLL_DBIni, "ReadInt", "Seed", "onetime", 0)
    staticseed  = call dll(DLL_DBIni, "ReadInt", "Seed", "value", 0)
    seedactive  = call dll(DLL_DBIni, "ReadInt", "Seed", "active", 0)
    IF (onetimeseed = 1) AND (seedactive = 1)
        call dll DLL_DBIni, "WriteInt", "Seed", "active", 0
    ENDIF
    IF seedactive
        call dll DLL_MapGen, "UseSeed", staticseed
    ELSE
        call dll DLL_MapGen, "RandomSeed"
    ENDIF
    call dll DLL_DBIni, "UnloadINI"
    delete dll DLL_DBIni
    
    make memblock MEM_Map, cMaxTrees*8
    call dll DLL_MapGen, "GenerateMap", cMaxTrees, cTreeRadius, cMapSizeX, cMapSizeZ, get memblock ptr(MEM_Map), 0
    for t = 0 to cMaxTrees-1
        tree(t).x = memblock dword(MEM_Map, t*8)
        tree(t).z = memblock dword(MEM_Map, t*8+4)
        tree(t).drawn = 0
    NEXT
    delete memblock MEM_Map
    delete dll DLL_MapGen
return

_menu:
    PASTE IMAGE IMG_TitleScreen, 0, 0
    set text font "times"
    set text size 27
    fade = 100
    pos = 1
    menu = 1
    sound = 1
    do
        gosub _fademenu
        if ( downkey() = 1 and pos < 4 and menu = 1 ) or ( downkey() = 1 and pos < 3 and menu = 3 )
            inc pos
            while downkey() = 1
                gosub _fademenu
            endwhile
        endif
        if upkey() = 1 and pos > 1
            dec pos
            while upkey() = 1
                gosub _fademenu
            endwhile
        endif
        if returnkey() = 1
            if menu = 1
                if pos = 1 then gosub _startgame
                if pos = 2
                    rem PASTE IMAGE IMG_TitleScreen, 0, 0
                    rem menu = 2
                    rem pos = 1
                endif
                if pos = 3
                    PASTE IMAGE IMG_TitleScreen, 0, 0
                    menu = 3
                    pos = 1
                endif
                if pos = 4 then end
            else
                if menu = 3
                    if pos = 1
                        if sound = 1
                            sound = 0
                        else
                            sound = 1
                        endif
                        PASTE IMAGE IMG_TitleScreen, 0, 0
                    endif
                    if pos = 2
                        if music = 1
                            music = 0
                        else
                            music = 1
                        endif
                        PASTE IMAGE IMG_TitleScreen, 0, 0
                    endif
                    if pos = 3
                        PASTE IMAGE IMG_TitleScreen, 0, 0
                        menu = 1
                        pos = 1
                    endif
                endif
            endif
            while returnkey() = 1
                gosub _fademenu
            endwhile
        endif
    loop
return

_create_enemies:
    dim enemy(cMaxEnemies-1) as enemyType
    for t = 0 to cMaxEnemies-1
        if t=0
            Load 3Dsound FN_SFX_ScorpWalk,SFX_Monster_Base+t
        else
            Clone Sound SFX_Monster_Base+t, SFX_Monster_Base
        endif

        pX# = Camera Position X()
        pY# = Camera Position Y()
        pZ# = Camera Position Z()

        repeat
            eX# = RndBetween(cEnemySpawnMapBorderPadding, cMapSizeX-cEnemySpawnMapBorderPadding)
            eZ# = RndBetween(cEnemySpawnMapBorderPadding, cMapSizeZ-cEnemySpawnMapBorderPadding)
            deltaX# = pX#-eX#
            deltaZ# = pZ#-eZ#
            dist# = sqrt( deltaX#*deltaX# + deltaZ#*deltaZ# )
            rem Prevent that the enemies spawn right at the player (5000/5000)
        until dist# >= initialEnemySafezone#
        enemy(t).AwarenessRadius = RndBetween(600, 1300)
        enemy(t).MaxSpeed = RndBetween(1, 4)
        enemy(t).WalkTimeoutMin = 200
        enemy(t).WalkTimeoutMax = 300
        
        if t = 0
            animScorpIdleStart = 0
            Load object FN_OBJ_ScorpIdle, OBJ_Monster_Base+t
            animScorpIdleEnd = total object frames(OBJ_Monster_Base+t)
            
            animScorpWalkStart = animScorpIdleEnd+1
            Append object FN_OBJ_ScorpWalk, OBJ_Monster_Base+t, animScorpWalkStart
            animScorpWalkEnd = total object frames(OBJ_Monster_Base+t)
        else
            Clone Object OBJ_Monster_Base+t, OBJ_Monster_Base        
        endif
        
        eY# = floorHeight#(eX#, eZ#) : rem TODO  + yPosOffset#
        position object OBJ_Monster_Base+t, eX#, eY#, eZ#
        Position Sound SFX_Monster_Base+t, eX#, eY#, eZ#
        
        fix object pivot OBJ_Monster_Base+t
        gosub scorpChangeToWalk
        set object collision on OBJ_Monster_Base+t
    NEXT
return

scorpChangeToIdle:
    rem TODO: smoothness (interpolation)
    if enemy(t).CurrentAnim <> 1
        loop object OBJ_Monster_Base+t, animScorpIdleStart, animScorpIdleEnd
        enemy(t).CurrentAnim = 1
    endif
return

scorpChangeToWalk:
    rem TODO: smoothness (interpolation)
    if enemy(t).CurrentAnim <> 2
        loop object OBJ_Monster_Base+t, animScorpWalkStart, animScorpWalkEnd
        enemy(t).CurrentAnim = 2
    endif
return

_setupClock:
    set text font "arial" : set text size 30 : set text transparent
    
    // Session variables
    clockTickCounter=0
    clockCurrentMidDot=0
    clockTicksPerSecond = framerate
return

_handleClock:
    if mod(clockTickCounter*clockBlinksPerSecond, clockTicksPerSecond) = 0
        clockCurrentMidDot = 1 - clockCurrentMidDot
    endif
    
    if mod(clockTickCounter*clockSpeedFactor, clockTicksPerSecond) = 0
        clockSeconds = clockSeconds + 1
    endif
    
    gosub _printClock

    clockTickCounter = clockTickCounter + 1
RETURN

_create_water:
    make matrix MAT_Water, cMapSizeX+2*waterMovementMaxDistance, cMapSizeZ+2*waterMovementMaxDistance, 15, 15
    load image FN_GFX_Water, IMG_Water
    prepare matrix texture MAT_Water, IMG_Water, 1, 1
    fill matrix MAT_Water, 0, 1
    position matrix MAT_Water, 0, waterLevel, 0
    ghost matrix on MAT_Water
    set matrix MAT_Water, 0, 0, 1, 1, 1, 1, 1
    update matrix MAT_Water
return

_create_floor:
    rem Boden
    make matrix MAT_Ground, cMapSizeX, cMapSizeZ, 15, 15

    load image FN_GFX_FloorTexture, IMG_ForestFloor
    prepare matrix texture MAT_Ground, IMG_ForestFloor, 1, 1
    fill matrix MAT_Ground, 0, 1
    set matrix MAT_Ground, 0, 0, 1, 1, 1, 1, 1
    randomize matrix MAT_Ground, 130
    
    rem TEST
    rem TODO: aber man kann 40,40 nicht updaten?!
    set matrix height MAT_Ground, 10, 10, 1000
    
    update matrix MAT_Ground
    
    remstart
    currentmatrix=1
    for z=1 to 20
        for x=1 to 20
    
          rem Get matrix heights
          rem print x, " - ", z
          rem sync
          rem sleep 100
          
          h8#=get matrix height(currentmatrix,x,z-1)
          h4#=get matrix height(currentmatrix,x-1,z)
          h#=get matrix height(currentmatrix,x,z)
          h2#=get matrix height(currentmatrix,x,z)
    
          rem Calculate projected angle X using heights
          x1#=(x-1)*25.0 : y1#=h#
          x2#=(x+0)*25.0 : y2#=h4#
          dx#=x2#-x1#
          dy#=y2#-y1#
          ax#=atanfull(dx#,dy#)
          ax#=wrapvalue(90-ax#)
    
          rem Calculate projected angle Z using heights
          z1#=(z-1)*25.0 : y1#=h2#
          z2#=(z+0)*25.0 : y2#=h8#
          dz#=z2#-z1#
          dy#=y2#-y1#
          az#=atanfull(dz#,dy#)
          az#=wrapvalue(90-az#)
    
          rem Make normal from projected angle
          nx#=sin(ax#)
          ny#=cos(ax#)
          nz#=sin(az#)
    
          rem Setting matrix normal for smoothness
          set matrix normal currentmatrix,x,z,nx#,ny#,nz#
    
       next x
    next z
    update matrix currentmatrix
    remend
return

_create_sky:
    if test = 1 
        load image FN_GFX_Tree, IMG_Stars : rem Any small picture - it doesn't matter since we only show wireframe
    else
        load image FN_GFX_Stars, IMG_Stars
    endif

rem TODO: landsize undefined
    make object sphere OBJ_Skybox, (landsize * 2) - 500

    rem make object sphere OBJ_Skybox, -4000
    
    set object collision off OBJ_Skybox
    rem scale object OBJ_Skybox, 2000, 750, 2000
    scale object OBJ_Skybox, 600, 120, 600
    set object OBJ_Skybox, 1, 0, 0, 0, 0, 1, 1
    texture object OBJ_Skybox, IMG_Stars
    rem fade object OBJ_Skybox, 0
    
    if test = 1
        backdrop on
        set object wireframe OBJ_Skybox, 1
    else
        backdrop off
    endif
return

_create_radar:
    rem Karte - Abstand zur Kamera: 1.1
    remstart
    load image FN_GFX_MapBackground, IMG_MapBackground
    make object plain OBJ_MapGhostObject, 0.352, 0.352
    lock object on OBJ_MapGhostObject
    position object OBJ_MapGhostObject, -0.6908, -0.4675, 1.1
    ghost object on OBJ_MapGhostObject
    texture object OBJ_MapGhostObject, IMG_MapBackground
    remend
    
    rem Karte - Abstand zur Kamera: 1.5
    load image FN_GFX_MapBackground, IMG_MapBackground
    make object plain OBJ_MapGhostObject, 0.48, 0.48
    lock object on OBJ_MapGhostObject
    position object OBJ_MapGhostObject, -0.942, -0.6375, 1.5
    ghost object on OBJ_MapGhostObject
    texture object OBJ_MapGhostObject, IMG_MapBackground
return

_printClock:
    if clockCurrentMidDot = 0
        middle$ = " "
    else
        middle$ = ":"
    endif
    mind = clockSeconds/60
    hours = mind/60
    if clockShowSeconds
        secsText$ = middle$+TwoDigit$(mod(clockSeconds,60))
    else
        secsText$ = ""
    endif
    print "Time: ", TwoDigit$(mod(hours,24)), middle$, TwoDigit$(mod(mind,60)), secsText$
return

rem Rotate smooth, and rotate in the direction with the lowest distance
rem "turn object left" ist das kommando
function smoothRotate#(von#, nach#, smoothness)
    if (von# > 270) and (nach# < 90)
        ret# = WrapValue(von# + (360 - von# + nach#)/smoothness)
    else
        if (von# < 90) and (nach# > 270)
            ret# = WrapValue(von# - (360 - nach# + von#)/smoothness)
        else
            ret# = von# + (nach# - von#)/smoothness
        endif
    endif
endfunction ret#

function floorHeight#(X#, Z#)
    rem TODO: implement water
    ret# = Get Ground Height(MAT_Ground, X#, Z#)
ENDFUNCTION ret#

function upPressed()
    ret = UpKey()=1 or keystate(17)=1 or joystick up()=1
ENDFUNCTION ret

function leftPressed()
    ret = LeftKey()=1 or keystate(30)=1 or joystick left()=1
ENDFUNCTION ret

function rightPressed()
    ret = RightKey()=1 or keystate(32)=1 or joystick right()=1
ENDFUNCTION ret

function downPressed()
    ret = DownKey()=1 or keystate(31)=1 or joystick down()=1
ENDFUNCTION ret

function jumpPressed()
    ret = keystate(57)=1 or JOYSTICK FIRE A()=1
ENDFUNCTION ret

function TwoDigit$(value)
    if value < 10
        ret$ = "0"+STR$(value)
    else
        ret$ = STR$(value)
    ENDIF
ENDFUNCTION ret$

function mod(num,modulus)
    value=num-((num/modulus)*modulus)
endfunction value

function fileReadInt(fileNum)
    read string 1, s$
    ret=val(s$)
ENDFUNCTION ret

function fileReadFloat#(fileNum)
    read string 1, s$
    ret#=val(s$)
ENDFUNCTION ret#

function RndBetween(a,b)
    ret = a+Rnd(b-a)
ENDFUNCTION ret

function isInCircle(pX#,pY#,cX#,cY#,cR#)
    ret = sqrt( (pX#-cX#)*(pX#-cX#) + (pY#-cY#)*(pY#-cY#) ) <= cR#
ENDFUNCTION ret

_stop_game_sounds:
    Stop Sound SFX_ForestBG
    Stop Sound SFX_PauseMusic
    Stop Sound SFX_Walk_Earth
    Stop Sound SFX_Walk_Water
    for t = 0 to cMaxEnemies-1
        Stop Sound SFX_Monster_Base+t
    next t
return

type treeType
   x as float
   z as float
   drawn as boolean
endtype

type enemyType
    Speed as float
    WalkTimeout as float
    WalkTimeoutMin as float
    WalkTimeoutMax as float
    AwarenessRadius as float
    CurSpeed as float
    MaxSpeed as float
    Angle as float
    CurrentAnim as float
endtype

PressJumpKey:
    if onGround=1
        rem TODO: absprungsound
        JumpPosition#=floorHeight#(X#,Z#)
        velocityY# = JumpStartVelocity#
        onGround = 0
    endif
return

ReleaseJumpKey:
    if velocityY# > MinFallVelocity#
        velocityY# = MinFallVelocity#
    endif
return

JumpUpdate:
    if onGround=0
        dec velocityY#, gravity#
        inc JumpPosition#, velocityY#
        
        rem TODO: man kann sich dann durch die decke glitchen
        curFloorHeight# = floorHeight#(X#,Z#)
        if JumpPosition# < curFloorHeight#
            rem TODO: aufprallsound
            JumpPosition# = curFloorHeight#
            velocityY# = 0.0
            onGround = 1
        endif
    endif
return

function resizeImage(image as dword, width as dword, height as dword)
        if DLL EXIST(DLL_Resize32) = 0
                load dll FN_DLL_Resize32, DLL_Resize32
        endif
        
        make memblock from image MEM_ResizeSrc, image
        size = call dll(DLL_Resize32, "DestSize", get memblock ptr(MEM_ResizeSrc), width, height)
        
        make memblock MEM_ResizeDest, size

        call dll DLL_Resize32, "Resize", get memblock ptr(MEM_ResizeSrc), get memblock ptr(MEM_ResizeDest), width, height
        
        
        make image from memblock image, MEM_ResizeDest
        
        delete memblock MEM_ResizeSrc
        delete memblock MEM_ResizeDest
        
        REM delete dll DLL_Resize

endfunction

function LoadImageFullscreen(imgFN as string, imgID)
    Load Image imgFN, imgID
    width  = desktop width()
    height = image height(imgID)*desktop width()/1.0/image width(imgID);
    resizeImage(imgID, width, height)
ENDFUNCTION

_play_attack_sound:
    repeat
        r=RndBetween(0,NumberOfAttackSounds-1)
    UNTIL (r <> LastAttackSound) or (NumberOfAttackSounds = 1)
    LastAttackSound=r
    play sound SFX_Attack_Base+r
return

_setup_blood:
    // Make 100 particle objects
    particleIndex = 0
    #constant PARTICLEMAX = 500
    dim particle(PARTICLEMAX) as particleType
    
    rem TODO: mehr
    rem http://nobacks.com/blood-splatter-eighty-eight/
    load image FN_GFX_BLOOD, IMG_BLOOD
    
    for i = 0 to PARTICLEMAX-1
        make object plain OBJ_BloodParticle_Base+i, 2, 2
        set object ambient OBJ_BloodParticle_Base+i, 0
        set object collision off OBJ_BloodParticle_Base+i
        
        rem color object OBJ_BloodParticle_Base+i, rgb(128, 0, 0)
        texture object OBJ_BloodParticle_Base+i, IMG_BLOOD
        set object cull OBJ_BloodParticle_Base+i, 0
        set object transparency OBJ_BloodParticle_Base+i, 6
        
        rem TEST
rem        rotate object OBJ_BloodParticle_Base+i, RndBetween(0,359), RndBetween(0,359), RndBetween(0,359)
        
        exclude object on OBJ_BloodParticle_Base+i
    next i
return

_handle_blood:
   // Update particles
   for i = 0 to PARTICLEMAX-1
      UpdateParticle(i)
   next i
return

_splat_blood:
    rem https://forum.thegamecreators.com/thread/160210#msg1886513 , modified
    
    rem TODO: sieht einfach nur scheiße aus. außerdem ist der strahl in 2 himmelsrichtungen schmal (seitlich)
    
rem    yoff = rnd(SPREAD1_MAX*2)-SPREAD1_MAX;

    // Activate blood particles around box
    for i = 0 to PARTICLEMAX-1
        // The angle particles should fly
        a#=camera angle y()
rem        yAng# = a#+rnd(SPREAD2_MAX*2)-SPREAD2_MAX+yoff
        yAng#=a#

        // Calculate directions
        xSpeed# = sin(yAng#)
        zSpeed# = cos(yAng#)
                
        // Calculate speeds
        xSpeed# = xSpeed# * (rnd(20)/5.0)
        ySpeed# = ((rnd(20)-rnd(20))/10.0)
        zSpeed# = zSpeed# * (rnd(20)/5.0)
        // Activate particle
         
        x# = camera position x()
        y# = camera position y()
        z# = camera position z()
        
        life = 30+rnd(30)
         
        particleIndex = ActivateParticle(particleIndex, life, x#, y#, z#, xSpeed#, ySpeed#, zSpeed#)
    next i
return
 
// Activate the specified particle and give it an initial position/velocity
function ActivateParticle(i, life, x#, y#, z#, xSpeed#, ySpeed#, zSpeed#)
    // Cycle through the particle objects to use
    inc i
    if i > PARTICLEMAX-1 then dec i, PARTICLEMAX-1
    
    // Set particle variables
    particle(i).life = life
    particle(i).x = x#
    particle(i).y = y#
    particle(i).z = z#
    particle(i).xSpeed = xSpeed#
    particle(i).ySpeed = ySpeed#
    particle(i).zSpeed = zSpeed#
    
    // Show particle object
    exclude object off OBJ_BloodParticle_Base+i
endfunction i
 
// Update positioning for specified particle
function UpdateParticle(i)
    // Only update particles that are alive
    if particle(i).life > 0
        // Apply gravity
rem        dec particle(i).ySpeed, 0.098
        dec particle(i).ySpeed, 0.200
 
        // Move the particle
        inc particle(i).x, particle(i).xSpeed
        inc particle(i).y, particle(i).ySpeed
        inc particle(i).z, particle(i).zSpeed
 
        // Position particle
        position object OBJ_BloodParticle_Base+i, particle(i).x, particle(i).y, particle(i).z
rem         point object OBJ_BloodParticle_Base+i, camera position x(), camera position y(), camera position z()


 
        // Lower particle's life
        dec particle(i).life
 
        // Hide particles when they die
        if particle(i).life <= 0
            exclude object on OBJ_BloodParticle_Base+i
        endif
    endif
endfunction
 
// Data type used by particles
type particleType
   life as integer      // Remaining life of particle (0=Dead)
   x as float           // Position
   y as float           // ...
   z as float           // ...
   xSpeed as float      // Velocity
   ySpeed as float      // ...
   zSpeed as float      // ...
endtype

rem -- MUST STAY AT THE END OF THE PROGRAM --
_exit:
    exit