CP - Alien Breed Remake - 3

Miscellaneous Forums/Blitz Showcase/CP - Alien Breed Remake - 3

I like the idea, most of it's probably a case of getting used to it. I suspect you were hindered a lot by maintaining compatibility...but maybe we are best sticking to vanilla blitz. Who knows, it may even compile in blitzmax - how cool would that be for a first demo? ;-)

Anyway, here we go with tonights updates. Lots of them...

In editor\blitz.bb, at the top of the mouse input code, amend the main layer stuff to:
If xpos>-1
		If MouseDown(1) And (activelayer<>LayerCollision And activeLayer <> LayerHits) Then
			If activelayer=LayerObject Then
				If activetile<NumObjects Then map(xpos,ypos,activelayer)=activetile+1
			Else
				map(xpos,ypos,activelayer)=activetile+1
			End If
		End If


In engine.bb, remove these two separate bits of code
Dim objectname$(9)
and
; object names
Data "Ammo","Health","Generic Key","Red Key","Green Key","Blue Key","V Door","H Door","Alien Blood"
For n=1 To 9
	Read objectname(n)
Next
and add ClearVis() after the load() at the top

In consts.bb, I've moved the map and objectdef arrays in here from map.bb, plus type definitions
; TYPES and ARRAYS
Type scriptitem
	Field command$
	Field param$
End Type

Type objdef
	Field mx,my ; map coords
	Field image%
	Field name$
	Field trigger% ; see constants below
	Field script$
End Type
Global NumObjects%
Dim objectdef.objdef(NumObjects)

Dim map(255,255,NumLayers+1)

Const ScriptTrigger_OnOver=1
Const ScriptTrigger_OnTouch=2

And I moved in the LAYER_VISIBLE global from map.bb for neatness

In editor.bp, add an include for "inc\consts.bb"

In map.bb, add the following to the load function between the MAP and OBJ loads
	;load the object file
	For od.objdef=Each objdef
		Delete od
	Next

also, change all ReadInt or ReadStrings in OBJECT *load* and *save* code to ReadLine, and add the following new function
Function ClearVis()
	;set initial visibility for all tiles to none at all
	;we'll set initial player vis when we create the player
	For x=0 To 255
		For y=0 To 255
			map(x,y,LAYER_VISIBLE)=0
		Next
	Next
End Function


You'll need the new OBJ file, straight text for now, so I've just copied it in below: copy, paste and save as test.obj
11
0
1
Ammo
addammo(50)|remove(me)
1
1
Health
addhealth(10)|remove(me)
2
1
Key (grey)
addkey(1)|remove(me)
3
1
Key (red)
addkey(2)|remove(me)
4
1
Key (green)
addkey(3)|remove(me)
5
1
Key (blue)
addkey(4)|remove(me)
6
2
Door
checkkey()|opendoor()|remove(nearby)
7
2
Door
checkkey()|opendoor()|remove(nearby)
8
1
100 Credit
addcredit(100)|remove(me)
9
1
50 Credit
addcredit(50)|remove(me)
10
1
10 Credit
addcredit(10)|remove(me)


In players.bb, add the following
;-----------scripting stuff

Function RunScript(pl.player,obj.objdef,x,y)

	si.scriptitem=ParseScript(obj\script)
	While si<>Null
		Select si\command
			Case "addammo"
				givePlayerAmmo(pl,Int(si\param$))

			Case "opendoor"
				opendoor(x/32,y/32)

			Case "checkkey"
			   	If Not keycheck(pl\id) Then Return

			Case "addkey"
				p.pickedup = New pickedup
				p\player = pl\id ; allowing for multiple players
				p\obj=Int(si\param)+2
				playoneshot(key_pickup,x,y)

			Case "addcredit" ; TODO: Credits!

			Case "addhealth"
				playoneshot(health_pickup,x,y)
				p.pickedup = New pickedup
				p\player = pl\id ; allowing for multiple players
				p\obj = 2

			Case "remove"
				If si\param="me" Then puttile(x,y,LayerObject,0)

			Default:RuntimeError "Unrecognised command : "+si\command

		End Select
		nsi.scriptitem=After si
		Delete si
		si=nsi
	Wend

End Function

Function ParseScript.scriptitem(s$)
	s$=Trim(s$)
	p=Instr(s$,"|")
	p2=Instr(s$,"(")
	si.scriptitem=New scriptitem
	fsi.scriptitem=si
	While p>0
		If p2>p Or p2=0 Then RuntimeError "ERROR: Malformed script: "+s$
		si\command=Left(s$,p2-1)
		si\param=Mid(s$,p2+1,p-p2-2)
		s$=Trim(Mid(s$,p+1))
		si=New scriptitem
		p=Instr(s$,"|")
		p2=Instr(s$,"(")
	Wend
	si\command=Left(s$,p2-1)
	si\param=Mid(s$,p2+1,Len(s$)-p2-1)
	Return fsi
End Function

and replace Player_updateall with this:
Function Player_UpdateAll()

	ScreenX=0
	ScreenY=0
	Player_Count=0

	For pl.player = Each player

	move=False
	direct$=""
	mr=False
	ml=False
	mu=False
	md=False

	firing = False

	; get input
	If control(pright,pl\id) And pl\x<7552 Then mr=True
	If control(pleft,pl\id) And pl\x>0 Then ml=True
	If control(pup,pl\id) And pl\y>0 Then mu=True
	If control(pdown,pl\id) And pl\y<7552 Then md=True
				
	If control(pfire1,pl\id)
		fireWeapon(pl,pl\dir*45)
		firing = True
	EndIf
	
	If Firing=True And (pl\clip>=0 And pl\ammo>0) Then
		If MilliSecs()>Pl\IsFiringTimer
			Pl\IsFiringTimer=MilliSecs()+25
			pl\IsFiring = Not pl\IsFiring
		EndIf
	Else
		Pl\IsFiring=False
	EndIf
	
	; restrict players to the visible screen
	If pl\x-GOffsetX > GraphicsWidth()-32 And mr=True Then mr=False
	If pl\x-GOffsetX < 32 And ml=True Then ml=False
	If pl\y-GOffsety > GraphicsHeight()-32 And md=True Then md=False
	If pl\y-GOffsety < 32 And mu=True Then mu=False
		
	; Object Collision (walls, doors etc)
	If mr Then
		If gettile(pl\x+playerradius,pl\y,0)=0 Then
			If control(pstrafe,Player_Count+1)=0 Then pl\x=pl\x+1
			move=True
		EndIf
		tile=map((pl\x+playerradius)/32,pl\y/32,LayerObject)
		If tile>0 Then
			If objectdef(tile)\trigger=ScriptTrigger_OnTouch Then RunScript(pl,objectdef(tile),pl\x+playerradius,pl\y)
		End If
		direct=direct+"R"
	End If
	If ml Then 
		If gettile(pl\x-playerradius,pl\y,0)=0 Then 
			If control(pstrafe,Player_Count+1)=0 Then pl\x=pl\x-1
			move=True	
		End If
		tile=map((pl\x-playerradius)/32,pl\y/32,LayerObject)
		If tile>0 Then
			If objectdef(tile)\trigger=ScriptTrigger_OnTouch Then RunScript(pl,objectdef(tile),pl\x-playerradius,pl\y)
		End If
		direct=direct+"L"
	End If
	If mu Then
		If gettile(pl\x,pl\y-playerradius,0)=0 Then 
			If control(pstrafe,Player_Count+1)=0 Then pl\y=pl\y-1
			move=True
		End If
		tile=map(pl\x/32,(pl\y-playerradius)/32,LayerObject)
		If tile>0 Then
			If objectdef(tile)\trigger=ScriptTrigger_OnTouch Then RunScript(pl,objectdef(tile),pl\x,pl\y-playerradius)
		End If
		direct=direct+"U"
	End If
	If md Then 
		If gettile(pl\x,pl\y+playerradius,0)=0 Then 
			If control(pstrafe,Player_Count+1)=0 Then pl\y=pl\y+1
			move=True
		End If	
		tile=map(pl\x/32,(pl\y+playerradius)/32,LayerObject)
		If tile>0 Then
			If objectdef(tile)\trigger=ScriptTrigger_OnTouch Then RunScript(pl,objectdef(tile),pl\x,pl\y+playerradius)
		End If
		direct=direct+"D"
	End If

	; if you're not firing change the direction you're looking
	If Not firing
		If direct = "U" Then pl\targetdir=0
		If direct = "RU" Then pl\targetdir =1
		If direct = "R" Then pl\targetdir =2 
		If direct = "RD" Then pl\targetdir = 3
		If direct = "D" Then pl\targetdir = 4
		If direct = "LD" Then pl\targetdir = 5
		If direct = "L" Then pl\targetdir = 6
		If direct = "LU" Then pl\targetdir = 7
		
		If turndelay = 0
			pl\dir = dirdif (pl\dir,pl\targetdir)
		EndIf
	EndIf
	

	ScreenX = ScreenX + pl\x
	ScreenY = ScreenY + pl\y
	Player_Count = Player_Count+1
	
	; collect objects
	If map(pl\x/32,pl\y/32,LayerObject)>0 Then 
		If objectdef(map(pl\x/32,pl\y/32,LayerObject))\trigger=ScriptTrigger_OnOver Then RunScript(pl,objectdef(map(pl\x/32,pl\y/32,LayerObject)),pl\x,pl\y)
	End If
		
	If pl\health = 0 Then
		;TODO: Death of player
		
	End If	

Next
End Function

Finally, in hud.bb, drawhud function amend the select case criteria.
			Select Objectdef(pu\obj)\image
				Case 2,3,4,5:		frame=Objectdef(pu\obj)\image
				Default:			ok=False
			End Select

On that note, I think we need to worry about a 'proper' inventory sytstem, although I really don't like the idea of an RPG style inventory. To be discussed once we sort other stuff out methinks...

For reference, I've put together some notes on how I see scripting working below.
Scripting Design Notes
======================

Each map has a <mapname>.obj file that is simply a series of strings.
This can be tidied up later on, but allows us to edit objects / scripts
without actually having a script editor.

An object is defined once, and has a unique id (defined by it's position 
in the OBJ file.  This ID is then held in the object layer of the map.
Many locations on the map may reuse the same ID for common objects such
as keys, cash, ammo, doors, etc.  These share the same object definition, 
but the script can be run from many places on the map.

Special objects (such as conversations) are defined in the same way, 
but are generally used only once (although you might have a conversation
that you wish to be triggered by coming near a character, which might 
require several surrounding map tiles to point to the same object, but
the object then does a remove(all) to ensure it's not used again).

The idea is that if you want to add a new scripted object / event, you
simply update the OBJ file (by hand for now) with a new entry (don't
forget to up the count!), write the script in that entry, then load the 
editor, place the object somewhere (it should just appear in the palette),
save and run.  Once the scripting language is complete, that really is all
you'll need to do - well, other than draw an image for it.  Note that an 
image of 0 _should_ in principle be invisible, but I haven't tested it yet...
(may be useful for conversations / traps / scripted events occurring on 
entering a room).

FORMAT
======
Number of Objects, followed by the following fields repeated for each object:
Image       - the image in object_tiles that will be used for display
Trigger     - what will trigger the script to run (see below)
description - a simple description of the object!
script      - The script to run

TRIGGERS
========
trigger is one of:
1 - on passing over (collectable items, traps)
2 - on touching (doors, buttons on walls, intex systems)

SCRIPTS
=======
script is formed of a series of commands, all taking the format:
commandname(params)|commandname(params)

for example:
addammo(100)|remove(me)

Script commands implemented:
addammo(N) - adds N to the players ammo count
checkkey() - checks that they have a key, if not, the script stops
opendoor() - runs the old opendoor code as is
addkey(N)  - adds key type N
addhealth(N) - adds N health
remove(me) - removes the object that caused this script to run

TODO
====
CheckKey to look for a specified key type
addcredit(N) - adds N credits
remove(nearby) - removes all nearby objects of the same type (e.g. doors) and change opendoor?
remove(all) - removes all objects of the same type
playsoundonce(sfx) - play a specified sound effect and move sfx for health / keys into scripts
playsoundcont(sfx) - play a continuous sound effect.  No idea how we stop these though...
showdoc(text) - show a document on screen
showconv(x,y,text) - show a speech bubble at specified x,y.
createalien(x,y,type,anim) - create an alien - need creation animations
setcollision(x,y,value) - set the collision layer of the map directly
setobject(x,y,value) - set the object layer of the map directly
settile(x,y,value) - set the base tile layer of the map directly
other triggers, such as onevery(N) for sounds, anims and alien spawn points

Hope that's everything! From now on, scripting changes should just be changes to the scripts held in the OBJ file, and RunScript (although it might well call other existing code), plus a couple of minor additions to other bits of code to support other triggers. But for now, this is it!