From b847fa6928c5e7b2731f54b362b1772f94508d4c Mon Sep 17 00:00:00 2001
From: Ian C <ianc@noddybox.co.uk>
Date: Fri, 7 Oct 2005 01:19:27 +0000
Subject: Added ship.  Idea still doesn't really work!

---
 .cvsignore          |   3 +-
 Default.ppinch      | Bin 1727 -> 423 bytes
 GFX/SHIP.png        | Bin 0 -> 282 bytes
 GFX/SHOCK.png       | Bin 0 -> 173 bytes
 GFX/keys_button.png | Bin 0 -> 6969 bytes
 GFX/sprites.bms     | Bin 3237 -> 4018 bytes
 designer.bmx        | 204 +++++++++++++++++++++++++++++++++++-----------
 game.bmx            | 102 +++++++++++++++++------
 level.bmx           |  57 +++++++++----
 main.bmx            | 124 +++++++++++++++++++++++++---
 types.bmx           | 228 ++++++++++++++++++++++++++++++++++++++++++++++++----
 11 files changed, 604 insertions(+), 114 deletions(-)
 create mode 100644 GFX/SHIP.png
 create mode 100644 GFX/SHOCK.png
 create mode 100644 GFX/keys_button.png

diff --git a/.cvsignore b/.cvsignore
index 953a728..d33eae2 100644
--- a/.cvsignore
+++ b/.cvsignore
@@ -1,2 +1,3 @@
 main.debug.exe
-.bmx
\ No newline at end of file
+.bmx
+ppinch.config
\ No newline at end of file
diff --git a/Default.ppinch b/Default.ppinch
index ab2cd03..2e9ea26 100644
Binary files a/Default.ppinch and b/Default.ppinch differ
diff --git a/GFX/SHIP.png b/GFX/SHIP.png
new file mode 100644
index 0000000..ff9dd2b
Binary files /dev/null and b/GFX/SHIP.png differ
diff --git a/GFX/SHOCK.png b/GFX/SHOCK.png
new file mode 100644
index 0000000..3e8b94c
Binary files /dev/null and b/GFX/SHOCK.png differ
diff --git a/GFX/keys_button.png b/GFX/keys_button.png
new file mode 100644
index 0000000..f712071
Binary files /dev/null and b/GFX/keys_button.png differ
diff --git a/GFX/sprites.bms b/GFX/sprites.bms
index e142fef..1045023 100644
Binary files a/GFX/sprites.bms and b/GFX/sprites.bms differ
diff --git a/designer.bmx b/designer.bmx
index a45034d..ff7db2f 100644
--- a/designer.bmx
+++ b/designer.bmx
@@ -22,7 +22,8 @@ Private
 ' **** Types
 '
 Type TDesObj Abstract
-	Const SELSIZE:Int=3
+	Field selsize:Int
+	Field removable:Int
 	
 	Function Create:TDesObj(x:Int, y:Int) Abstract
 	Function CreateFromLevel:TDesObj(o:Object) Abstract
@@ -36,10 +37,10 @@ Type TDesObj Abstract
 	Method Snap() Abstract
 
 	Method DrawSelBox(x:Int, y:Int)
-		Local x1:Int=x-SELSIZE
-		Local y1:Int=y-SELSIZE
-		Local x2:Int=x+SELSIZE
-		Local y2:Int=y+SELSIZE
+		Local x1:Int=x-selsize
+		Local y1:Int=y-selsize
+		Local x2:Int=x+selsize
+		Local y2:Int=y+selsize
 		
 		SetColor(255,255,255)
 		DrawLine(x1,y1,x2,y1)
@@ -49,10 +50,10 @@ Type TDesObj Abstract
 	End Method
 	
 	Method InSelBox(px:Int, py:Int, x:Int, y:Int)
-		Local x1:Int=x-SELSIZE
-		Local y1:Int=y-SELSIZE
-		Local x2:Int=x+SELSIZE
-		Local y2:Int=y+SELSIZE
+		Local x1:Int=x-selsize
+		Local y1:Int=y-selsize
+		Local x2:Int=x+selsize
+		Local y2:Int=y+selsize
 
 		Return px>=x1 And px<=x2 And py>=y1 And py<=y2
 	End Method
@@ -102,6 +103,8 @@ Type TDesGrav Extends TDesObj
 		o.g.friendly=False
 		o.g.mass=25
 		o.g.repel=False
+		o.selsize=3
+		o.removable=True
 		
 		Return o
 	End Function
@@ -116,6 +119,8 @@ Type TDesGrav Extends TDesObj
 		no.g.friendly=lp.friendly
 		no.g.mass=lp.mass
 		no.g.repel=lp.repel
+		no.selsize=3
+		no.removable=True
 		
 		Return no
 	End Function
@@ -170,6 +175,82 @@ Type TDesGrav Extends TDesObj
 
 End Type
 
+Type TDesShip Extends TDesObj
+	Field x:Int
+	Field y:Int
+	Field a:Int
+	
+	Function Create:TDesShip(x:Int, y:Int)
+		Local o:TDesShip=New TDesShip
+		
+		o.x=x
+		o.y=y
+		o.a=0
+		o.selsize=8
+		o.removable=False
+		
+		Return o
+	End Function
+	
+	Function CreateFromLevel:TDesObj(o:Object)
+		Local lp:TLevel=TLevel(o)
+		Local no:TDesShip=New TDesShip
+		
+		no.x=lp.startx
+		no.y=lp.starty
+		no.a=lp.startang
+		no.selsize=8
+		no.removable=False
+		
+		Return no
+	End Function
+	
+	Method Draw()
+		SetColor(255,255,255)
+		SetRotation(a)
+		DrawImage(GameGFX.ship,x,y)
+		SetRotation(0)
+	End Method
+	
+	Method DrawSelect()
+		DrawSelBox(x,y)
+	End Method
+	
+	Method MouseOver:Int(x:Int, y:Int)
+		Return InSelBox(x,y,self.x,self.y)
+	End Method
+	
+	Method Drag(x:Int, y:Int)
+		DrawCoord(x,y)
+		self.x=x
+		self.y=y
+	End Method
+	
+	Method Edit()
+		Designer.sd_ang.SetFloat(a)
+
+		If GUIDialog(Designer.sdialog,Designer.sd_ok,Designer.sd_cancel,GameGFX.pointer)
+			a = Designer.sd_ang.text.ToInt()
+		EndIf
+	End Method
+
+	Method Save(lev:TLevel)
+		lev.startx=x
+		lev.starty=y
+		lev.startang=a
+	End Method
+
+	Method SetInfo(w:TLabel)
+		w.text="Angle: " + a
+	End Method
+	
+	Method Snap()
+		x=CalcSnap(x)
+		y=CalcSnap(y)
+	End Method
+
+End Type
+
 Type TDesPoint Extends TDesObj
 	Field l:TPointLine
 	Field over_p1:Int
@@ -193,6 +274,9 @@ Type TDesPoint Extends TDesObj
 		o.l.gap=1
 		o.l.v.x=0
 		o.l.v.y=0
+
+		o.selsize=3
+		o.removable=True
 		
 		Return o
 	End Function
@@ -211,6 +295,8 @@ Type TDesPoint Extends TDesObj
 		no.l.v.y=lp.v.y
 		no.l.circle=lp.circle
 		no.l.circvel=lp.circvel
+		no.selsize=3
+		no.removable=True
 		
 		Return no
 	End Function
@@ -415,13 +501,19 @@ Type Designer
 	Global pd_ok:TButton
 	Global pd_cancel:TButton
 
+	Global sdialog:TGUIHandler
+	Global sd_ang:TText
+	Global sd_ok:TButton
+	Global sd_cancel:TButton
+
 	Global ldialog:TGUIHandler
 	Global ld_invert:TCheckbox
-	Global ld_placefriend:TCheckbox
-	Global ld_maxmass:TText
+	Global ld_wrap:TCheckbox
+	Global ld_maxwave:TText
 	Global ld_winpercent:TText
 	Global ld_timer:TText
-	Global ld_placemass:TText
+	Global ld_shipmass:TText
+	Global ld_wavemass:TText
 	Global ld_ok:TButton
 	Global ld_cancel:TButton
 
@@ -479,6 +571,13 @@ Type Designer
 			md_ok =			TButton.Create(mdialog,p.x+5,p.y+p.h-25,p.w/2-10,20,"OK",Null)
 			md_cancel =		TButton.Create(mdialog,p.x+p.w/2+5,p.y+p.h-25,p.w/2-10,20,"Cancel",Null)
 
+			sdialog =			TGUIHandler.Create()
+			p =				TPanel.Create(sdialog,-1,-1,400,100)
+			l =				TLabel.Create(sdialog,p.x+5,p.y+50,"Angle:")
+			sd_ang =			TText.Create(sdialog,p.x+l.w+10,p.y+50,"",30,TText.NUMERIC|TText.POSITIVE)
+			sd_ok =			TButton.Create(sdialog,p.x+5,p.y+p.h-25,p.w/2-10,20,"OK",Null)
+			sd_cancel =		TButton.Create(sdialog,p.x+p.w/2+5,p.y+p.h-25,p.w/2-10,20,"Cancel",Null)
+
 			pdialog =			TGUIHandler.Create()
 			p =				TPanel.Create(pdialog,-1,-1,400,200)
 			l =				TLabel.Create(pdialog,p.x+5,p.y+10,"Gap per point:")
@@ -495,16 +594,18 @@ Type Designer
 
 			ldialog =			TGUIHandler.Create()
 			p =				TPanel.Create(ldialog,-1,-1,400,200)
-			ld_invert =		TCheckbox.Create(ldialog,p.x+5,p.y+10,"Inverse gravity for dropped masses?")
-			ld_placefriend =	TCheckbox.Create(ldialog,p.x+5,p.y+30,"Dropped masses are collectors (friendly)?")
-			l =				TLabel.Create(ldialog,p.x+5,p.y+50,"Max Dropped Masses:")
-			ld_maxmass =		TText.Create(ldialog,l.x+l.w+10,l.y,"",2,TText.NUMERIC|TText.INTEGER|TText.POSITIVE)
+			ld_invert =		TCheckbox.Create(ldialog,p.x+5,p.y+10,"Ship has inverse gravity?")
+			ld_wrap =			TCheckbox.Create(ldialog,p.x+5,p.y+30,"Universe wraps?")
+			l =				TLabel.Create(ldialog,p.x+5,p.y+50,"Gravity Waves:")
+			ld_maxwave =		TText.Create(ldialog,l.x+l.w+10,l.y,"",2,TText.NUMERIC|TText.INTEGER|TText.POSITIVE)
 			l =				TLabel.Create(ldialog,p.x+5,p.y+70,"Win percentage:")
 			ld_winpercent =	TText.Create(ldialog,l.x+l.w+10,l.y,"",3,TText.NUMERIC|TText.INTEGER|TText.POSITIVE)
 			l =				TLabel.Create(ldialog,p.x+5,p.y+90,"Timer (roughly seconds):")
 			ld_timer =		TText.Create(ldialog,l.x+l.w+10,l.y,"",3,TText.NUMERIC|TText.INTEGER|TText.POSITIVE)
-			l =				TLabel.Create(ldialog,p.x+5,p.y+110,"Placed mass:")
-			ld_placemass =	TText.Create(ldialog,l.x+l.w+10,l.y,"",20,TText.NUMERIC|TText.POSITIVE)
+			l =				TLabel.Create(ldialog,p.x+5,p.y+110,"Ship mass:")
+			ld_shipmass =		TText.Create(ldialog,l.x+l.w+10,l.y,"",20,TText.NUMERIC|TText.POSITIVE)
+			l =				TLabel.Create(ldialog,p.x+5,p.y+130,"Gravity Wave mass:")
+			ld_wavemass =		TText.Create(ldialog,l.x+l.w+10,l.y,"",20,TText.NUMERIC|TText.POSITIVE)
 			ld_ok =			TButton.Create(ldialog,p.x+5,p.y+p.h-25,p.w/2-10,20,"OK",Null)
 			ld_cancel =		TButton.Create(ldialog,p.x+p.w/2+5,p.y+p.h-25,p.w/2-10,20,"Cancel",Null)
 
@@ -523,6 +624,8 @@ Type Designer
 		
 		obj.Clear()
 		
+		obj.AddLast(TDesShip.CreateFromLevel(level))
+		
 		For Local o:Object=EachIn level.grav
 			obj.AddLast(TDesGrav.CreateFromLevel(o))
 		Next
@@ -544,19 +647,21 @@ Type Designer
 
 	Function EditLevelSettings()
 		ld_invert.checked = level.invmass
-		ld_placefriend.checked = level.placefriend
-		ld_maxmass.text = level.maxmass
+		ld_wrap.checked = level.wrap
+		ld_maxwave.text = level.maxwave
 		ld_winpercent.text = level.winpercent
 		ld_timer.text = level.timer
-		ld_placemass.SetFloat(level.placemass)
+		ld_shipmass.SetFloat(level.shipmass)
+		ld_wavemass.SetFloat(level.wavemass)
 
 		If GUIDialog(ldialog,ld_ok,ld_cancel,GameGFX.pointer)
 			level.invmass = ld_invert.checked
-			level.placefriend= ld_placefriend.checked
-			level.maxmass = ld_maxmass.text.ToInt()
+			level.wrap = ld_wrap.checked
+			level.maxwave = Min(MAX_WAVE,ld_maxwave.text.ToInt())
 			level.winpercent = Max(0,Min(100,ld_winpercent.text.ToInt()))
 			level.timer = ld_timer.text.ToInt()
-			level.placemass = ld_placemass.text.ToDouble()
+			level.shipmass = ld_shipmass.text.ToDouble()
+			level.wavemass = ld_wavemass.text.ToDouble()
 		EndIf
 	End Function
 End Type
@@ -624,14 +729,23 @@ Function DoDesigner()
 		
 		If KeyHit(KEY_MOUSERIGHT)
 			If sel<>Null
-				Select GUIMenu("Object Menu",["Edit","Snap to grid","Delete"],x,y,GameGFX.pointer)
-					Case 0
-						sel.Edit()
-					Case 1
-						sel.Snap()
-					Case 2
-						Designer.obj.Remove(sel)
-				End Select
+				If sel.removable
+					Select GUIMenu("Object Menu",["Edit","Snap to grid","Delete"],x,y,GameGFX.pointer)
+						Case 0
+							sel.Edit()
+						Case 1
+							sel.Snap()
+						Case 2
+							Designer.obj.Remove(sel)
+					End Select
+				Else
+					Select GUIMenu("Object Menu",["Edit","Snap to grid"],x,y,GameGFX.pointer)
+						Case 0
+							sel.Edit()
+						Case 1
+							sel.Snap()
+					End Select
+				EndIf
 			Else
 				Select GUIMenu("Create Menu",["Create Gravity Point","Create Particle Line","Edit Level Settings"],x,y,GameGFX.pointer)
 					Case 0
@@ -697,8 +811,6 @@ Function TestCallback(w:TWidget)
 	While res<>TGame.LEVEL_FINISHED And res<>TGame.LEVEL_CANCELLED
 		Cls
 		res=g.Play()
-		SetColor(255,255,255)
-		DrawImage(GameGFX.pointer,MouseX(),MouseY())
 		FlushMem
 		Flip
 	Wend
@@ -716,34 +828,32 @@ Function CheckCallback(w:TWidget)
 	
 	Designer.level.CreatePlayfield(m,p)
 	
-	If m.Count()+Designer.level.maxmass>MAX_GRAV
+	If (m.Count()+1)>MAX_GRAV
 		ok=False
-		s:+"|Too many masses (combining placed and dropped)"
+		s:+"|  *  Too many masses (combining placed and ship)"
 	EndIf
 	
-	Local friends:Int=(Designer.level.placefriend And Designer.level.maxmass>0)
+	Local friends:Int=False
 	
-	If Not friends
-		For Local gp:TMass=EachIn m
-			If gp.friend
-				friends=True
-			EndIf
-		Next
-	EndIf
+	For Local gp:TMass=EachIn m
+		If gp.friend
+			friends=True
+		EndIf
+	Next
 	
 	If Not friends
 		ok=False
-		s:+"|No collector masses (no particles can be captured by player)"
+		s:+"|  *  No collector masses (no particles can be captured by player)"
 	EndIf
 	
 	If p.Count()>MAX_POINT
 		ok=False
-		s:+"|Too many points (" + p.Count() + " -- maximum is " + MAX_POINT
+		s:+"|  *  Too many points (" + p.Count() + " -- maximum is " + MAX_POINT + ")"
 	EndIf
 	
 	If p.Count()=0
 		ok=False
-		s:+"|No particles to collect!"
+		s:+"|  *  No particles to collect"
 	EndIf
 	
 	If Not ok
diff --git a/game.bmx b/game.bmx
index ff6ddfa..fb0ada3 100644
--- a/game.bmx
+++ b/game.bmx
@@ -19,6 +19,7 @@ Type TGame
 	Const LEVEL_CANCELLED:Int=	4
 	
 	Field level:TLevel
+	Field ship:TShip
 	Field mass:TList
 	Field point:TList
 	Field timer:Int
@@ -42,13 +43,14 @@ Type TGame
 		o.level=level
 		o.mass=CreateList()
 		o.point=CreateList()
-		o.level.CreatePlayfield(o.mass,o.point)
+		o.ship=o.level.CreatePlayfield(o.mass,o.point)
+		o.mass.AddLast(o.ship)
 		o.timer=o.level.timer
 		o.num=o.point.Count()
 		o.done=LEVEL_NOTOVER
 		o.frame=0
 		o.placed=0
-		o.txtoff=[GameGFX.font.TextWidth("PARTICLES"),GameGFX.font.TextWidth("CAPTURED"),GameGFX.font.TextWidth("TIMER"),GameGFX.font.TextWidth("LEFT")]
+		o.txtoff=[GameGFX.font.TextWidth("PARTICLES"),GameGFX.font.TextWidth("CAPTURED"),GameGFX.font.TextWidth("TIMER"),GameGFX.font.TextWidth("WAVES")]
 		o.playing=False
 		o.col=0
 		o.coli=5
@@ -126,26 +128,31 @@ Type TGame
 		GameGFX.font.CentreColoured("Need "+level.winpercent+"% to clear",y,col/2,col,col)
 		y:+yi
 		
-		Local n:Int=Int(level.placemass)
+		Local n:Int=Int(level.shipmass)
+		Local w:Int=Int(level.wavemass)
 		
 		If level.invmass
 			n=-n
+			w=-w
 		EndIf
 
-		If level.maxmass>1
-			GameGFX.font.CentreColoured("You can place "+level.maxmass+" masses of " + n + " Newtons",y,col/2,col,col)
-		Else
-			GameGFX.font.CentreColoured("You can place 1 mass of " + n + " Newtons",y,col/2,col,col)
+		GameGFX.font.CentreColoured("Your ship has a mass of " + n + " Newtons",y,col/2,col,col)
+		y:+yi
+
+		If level.maxwave>1
+			GameGFX.font.CentreColoured("You can fire "+level.maxwave+" Gravity Waves of " + w + " Newtons",y,col/2,col,col)
+		ElseIf level.maxwave=1
+			GameGFX.font.CentreColoured("You can fire 1 Gravity Wave of " + w + " Newtons",y,col/2,col,col)
 		EndIf
 		y:+yi
 
-		If level.placefriend
-			GameGFX.font.CentreColoured("PLACED MASSES ARE COLLECTORS!",y,col,col/2,col/2)
+		If level.wrap
+			GameGFX.font.CentreColoured("THE UNIVERSE IS WARPED!",y,col,col/2,col/2)
 			y:+yi
 		EndIf
 	
 		y:+yi
-		GameGFX.font.Centre("Press Left Mouse Button",y)
+		GameGFX.font.Centre("Press Space",y)
 		y:+yi
 
 		SetScale(1,1)
@@ -155,8 +162,44 @@ Type TGame
 		captured=0
 		lost=0
 
-		If playing		
+		If playing
+			If done=LEVEL_NOTOVER
+				If KeyDown(GameConfig.kleft)
+					ship.RotateLeft()
+				EndIf
+	
+				If KeyDown(GameConfig.kright)
+					ship.RotateRight()
+				EndIf
+	
+				If KeyDown(GameConfig.kthrust)
+					ship.Thrust()
+				EndIf
+	
+				If KeyDown(GameConfig.kreverse)
+					ship.Reverse()
+				EndIf
+
+				If KeyHit(GameConfig.kblast) And placed<level.maxwave
+					If level.invmass
+						ship.mass=-level.wavemass
+					Else
+						ship.mass=level.wavemass
+					EndIf
+					TParticleMachine.AddShockwave(ship)
+					placed:+1
+				Else
+					If level.invmass
+						ship.mass=-level.shipmass
+					Else
+						ship.mass=level.shipmass
+					EndIf
+				EndIf
+			EndIf
+
 			For Local m:TMass=EachIn mass
+				ship.Attract(m)
+				
 				For Local s:TPoint=EachIn point
 					s.Attract(m)
 				Next
@@ -167,7 +210,7 @@ Type TGame
 		
 		If playing
 			For Local m:TMass=EachIn mass
-				m.Move()
+				m.Move(True)'(level.wrap)
 				m.Draw()
 			Next
 		Else
@@ -178,7 +221,7 @@ Type TGame
 		
 		If playing		
 			For Local s:TPoint=EachIn point
-				s.Move()
+				s.Move(level.wrap)
 				s.Draw()
 				
 				If s.dead
@@ -243,14 +286,12 @@ Type TGame
 			GameGFX.font.DrawColoured(percent+"%",txtoff[1]+210,0,50,255,50)
 		EndIf
 		
-		GameGFX.font.Draw("LEFT",400,0)
+		GameGFX.font.Draw("WAVES",400,0)
 		
-		SetScale(2,2)
 		SetColor(255,255,255)
-		For Local f:Int=0 Until level.maxmass-placed
-			DrawImage(GameGFX.mass,410+txtoff[3]+f*10,3)
+		For Local f:Int=0 Until level.maxwave-placed
+			DrawImage(GameGFX.shock,410+txtoff[3]+f*10,3)
 		Next
-		SetScale(1,1)
 		
 		GameGFX.font.Draw("TIMER",600,0)
 		
@@ -263,28 +304,36 @@ Type TGame
 		Select done
 			Case LEVEL_NOTOVER
 				If playing
+					Rem
 					If placed<level.maxmass And mass.Count()<MAX_GRAV
-						If MouseHit(1)
+						If KeyHit(KEY_SPACE)
 							Local m:TMass=New TMass
 							m.friend=level.placefriend
 							m.inverse=level.invmass
 							m.x=MouseX()
 							m.y=MouseY()
-							m.img=GameGFX.mass
+							If m.friend
+								m.img=GameGFX.collector
+							Else
+								m.img=GameGFX.star
+							EndIf
 							m.mass=level.placemass
 							mass.AddLast(m)
 							placed:+1
 						EndIf
 					EndIf
+					EndRem
 				Else
 					Intro()
-					If MouseHit(1)
+					If KeyHit(KEY_SPACE)
 						playing=True
+						FlushKeys()
 					EndIf
 				EndIf
 				
 				If KeyHit(KEY_ESCAPE)
 					done=LEVEL_CANCELLED
+					FlushKeys()
 				EndIf
 			Case LEVEL_WON
 				If final_percent=100
@@ -294,6 +343,7 @@ Type TGame
 					
 					If col=255 Or col=0
 						TParticleMachine.AddFirework(Rand(0,GraphicsWidth()),Rand(0,GraphicsHeight()))
+						'TParticleMachine.AddShockwave(Rand(0,GraphicsWidth()),Rand(0,GraphicsHeight()))
 						coli=-coli
 					EndIf
 				EndIf
@@ -302,23 +352,25 @@ Type TGame
 				SetAlpha(0.7)
 				GameGFX.font.CentreColoured("LEVEL COMPLETED!",GraphicsHeight()/2+20,255,255,0)
 				GameGFX.font.CentreColoured("You got the pass mark with "+pass_time+" left on the clock",GraphicsHeight()/2+40,255,255,0)
-				GameGFX.font.Centre("Press the Left Mouse Button",GraphicsHeight()/2+80)
+				GameGFX.font.Centre("Press Space",GraphicsHeight()/2+80)
 				SetScale(1,1)
 				SetAlpha(1)
 				
-				If MouseHit(1)
+				If KeyHit(KEY_SPACE)
 					done=LEVEL_FINISHED
+					FlushKeys()
 				EndIf
 			Case LEVEL_LOST
 				SetScale(2,2)
 				SetAlpha(0.7)
 				GameGFX.font.CentreColoured("LEVEL FAILED!",GraphicsHeight()/2+20,255,64,64)
-				GameGFX.font.Centre("Press the Left Mouse Button",GraphicsHeight()/2+60)
+				GameGFX.font.Centre("Press Space",GraphicsHeight()/2+60)
 				SetScale(1,1)
 				SetAlpha(1)
 
-				If MouseHit(1)
+				If KeyHit(KEY_SPACE)
 					done=LEVEL_FINISHED
+					FlushKeys()
 				EndIf
 		EndSelect
 		
diff --git a/level.bmx b/level.bmx
index 254ac5d..4b1c020 100644
--- a/level.bmx
+++ b/level.bmx
@@ -10,10 +10,11 @@ Import noddybox.bitmapfont
 Import noddybox.algorithm
 Import "types.bmx"
 
-Const MAGIC:String="PPINCH_0001"
+Const MAGIC:String="PPINCH_0002"
 
 Const MAX_GRAV:Int=10
 Const MAX_POINT:Int=1000
+Const MAX_WAVE:Int=10
 
 Type TLevelException
 	Field	message:String
@@ -151,34 +152,52 @@ End Type
 
 Type TLevel
 	Field name:String
-	Field maxmass:Int
+	Field maxwave:Int
 	Field grav:TList
 	Field point:TList
 	Field winpercent:Int
 	Field timer:Int
 	Field invmass:Int
-	Field placemass:Double
-	Field placefriend:Int
+	Field shipmass:Double
+	Field wavemass:Double
+	Field wrap:Int
+	Field startx:Double
+	Field starty:Double
+	Field startang:Int
 	
 	Method New()
 		grav=CreateList()
 		point=CreateList()
 		name="Unititled"
-		maxmass=5
+		maxwave=5
 		winpercent=50
-		timer=60
+		timer=120
 		invmass=False
-		placemass=25
-		placefriend=True
+		shipmass=5
+		wavemass=5
+		wrap=False
+		startx=399
+		starty=400
+		startang=0
 	End Method
 	
-	Method CreatePlayfield(masslist:TList, pointlist:TList)
+	Method CreatePlayfield:TShip(masslist:TList, pointlist:TList)
 		For Local gp:TGravPoint=EachIn grav
 			masslist.AddLast(gp.CreateMass())
 		Next
 		For Local lp:TPointLine=EachIn point
 			lp.CreatePoints(pointlist)
 		Next
+		
+		Local s:TShip=New TShip
+		
+		s.x=startx
+		s.y=starty
+		s.a=startang
+		s.mass=shipmass
+		s.img=GameGFX.ship
+		
+		Return s
 	End Method
 	
 	Function FromStream:TLevel(s:TStream)
@@ -186,12 +205,16 @@ Type TLevel
 		Local c:Int
 
 		o.name=s.ReadLine()
-		o.maxmass=s.ReadInt()
+		o.maxwave=s.ReadInt()
 		o.winpercent=s.ReadInt()
 		o.timer=s.ReadInt()
 		o.invmass=s.ReadInt()
-		o.placemass=s.ReadDouble()
-		o.placefriend=s.ReadInt()
+		o.shipmass=s.ReadDouble()
+		o.wavemass=s.ReadDouble()
+		o.wrap=s.ReadInt()
+		o.startx=s.ReadDouble()
+		o.starty=s.ReadDouble()
+		o.startang=s.ReadInt()
 		
 		c=s.ReadInt()
 		For Local f:Int=1 To c
@@ -208,12 +231,16 @@ Type TLevel
 
 	Method ToStream(s:TStream)
 		s.WriteLine(name)
-		s.WriteInt(maxmass)
+		s.WriteInt(maxwave)
 		s.WriteInt(winpercent)
 		s.WriteInt(timer)
 		s.WriteInt(invmass)
-		s.WriteDouble(placemass)
-		s.WriteInt(placefriend)
+		s.WriteDouble(shipmass)
+		s.WriteDouble(wavemass)
+		s.WriteInt(wrap)
+		s.WriteDouble(startx)
+		s.WriteDouble(starty)
+		s.WriteInt(startang)
 
 		s.WriteInt(grav.Count())
 		For Local o:TGravPoint=EachIn grav
diff --git a/main.bmx b/main.bmx
index 07e1048..e44f596 100644
--- a/main.bmx
+++ b/main.bmx
@@ -9,6 +9,7 @@ Strict
 Import noddybox.vector
 Import noddybox.bitmapfont
 Import noddybox.simplegui
+Import noddybox.keysyms
 
 Import "types.bmx"
 Import "level.bmx"
@@ -21,11 +22,12 @@ Import "menu.bmx"
 Incbin "GFX/font.bmf"
 Incbin "GFX/small.bmf"
 Incbin "GFX/STAR.png"
-Incbin "GFX/MASS.png"
+Incbin "GFX/SHIP.png"
 Incbin "GFX/POINT.png"
 Incbin "GFX/PARTICLE.png"
 Incbin "GFX/POINTER.png"
 Incbin "GFX/COLLECTOR.png"
+Incbin "GFX/SHOCK.png"
 Incbin "GFX/play_button.png"
 Incbin "GFX/edit_button.png"
 Incbin "GFX/load_button.png"
@@ -34,12 +36,21 @@ Incbin "GFX/quit_button.png"
 Incbin "GFX/left_button.png"
 Incbin "GFX/right_button.png"
 Incbin "GFX/scores_button.png"
+Incbin "GFX/keys_button.png"
 
 
 ' Initialise
 '
 SeedRnd(MilliSecs())
-SetGraphicsDriver GLMax2DDriver()
+
+?Win32
+If Switch("--directx")
+	SetGraphicsDriver D3D7Max2DDriver()
+Else
+	SetGraphicsDriver GLMax2DDriver()
+EndIf
+?
+
 Graphics 800,600,32,60
 HideMouse
 
@@ -51,11 +62,12 @@ SetAlpha(1.0)
 GameGFX.font=TBitmapFont.Load("incbin::GFX/font.bmf",0)
 GameGFX.smallfont=TBitmapFont.Load("incbin::GFX/small.bmf",0)
 GameGFX.star=LoadAnimImage("incbin::GFX/STAR.png",8,8,0,2)
-GameGFX.mass=LoadAnimImage("incbin::GFX/MASS.png",8,8,0,2)
+GameGFX.ship=LoadImage("incbin::GFX/SHIP.png",FILTEREDIMAGE)
 GameGFX.collector=LoadAnimImage("incbin::GFX/COLLECTOR.png",8,8,0,2)
 GameGFX.point=LoadImage("incbin::GFX/POINT.png",FILTEREDIMAGE)
 GameGFX.particle=LoadImage("incbin::GFX/PARTICLE.png",0)
 GameGFX.pointer=LoadImage("incbin::GFX/POINTER.png",0)
+GameGFX.shock=LoadImage("incbin::GFX/SHOCK.png",0)
 GameGFX.play_button=LoadImage("incbin::GFX/play_button.png",0)
 GameGFX.edit_button=LoadImage("incbin::GFX/edit_button.png",0)
 GameGFX.load_button=LoadImage("incbin::GFX/load_button.png",0)
@@ -64,26 +76,38 @@ GameGFX.quit_button=LoadImage("incbin::GFX/quit_button.png",0)
 GameGFX.left_button=LoadImage("incbin::GFX/left_button.png",0)
 GameGFX.right_button=LoadImage("incbin::GFX/right_button.png",0)
 GameGFX.scores_button=LoadImage("incbin::GFX/scores_button.png",0)
+GameGFX.keys_button=LoadImage("incbin::GFX/keys_button.png",0)
 
 SetImageHandle(GameGFX.star,3,3)
-SetImageHandle(GameGFX.mass,3,3)
+SetImageHandle(GameGFX.ship,7,7)
 SetImageHandle(GameGFX.collector,3,3)
 SetImageHandle(GameGFX.point,3,3)
 SetImageHandle(GameGFX.particle,3,3)
 SetImageHandle(GameGFX.pointer,0,0)
+SetImageHandle(GameGFX.shock,3,3)
 
 TPoint.img=GameGFX.point
 TParticle.img=GameGFX.particle
 
 TGUIFont.font=GameGFX.font
 
+Lookup.Init()
 TParticleMachine.Init()
+GameConfig.Load()
 
 Global quit:Int=False
 Global levelfile:String="Default.ppinch"
 Global levelset:TLevelSet=Null
 Global selected_level:Int=0
 
+' Check designer mode
+'
+If Switch("--design")
+	LevelDesigner()
+	EndGraphics
+	End
+EndIf
+
 ' Initialisation
 '
 Try
@@ -117,7 +141,7 @@ Function Error(s:String, fatal:Int=False)
 	Local t:Int=0
 	FlushKeys()
 	
-	SetScale(3,3)
+	SetScale(2,2)
 	
 	While Not KeyHit(KEY_ESCAPE)
 		Cls
@@ -149,6 +173,20 @@ Function Error(s:String, fatal:Int=False)
 	SetScale(1,1)
 End Function
 
+' ===================================
+' Argument Routines
+' ===================================
+'
+Function Switch:Int(s:String)
+	For Local a:String=EachIn AppArgs
+		If a=s
+			Return True
+		EndIf
+	Next
+	
+	Return False
+End Function
+
 ' ===================================
 ' Menu Routines
 ' ===================================
@@ -162,6 +200,7 @@ Function Menu()
 	Const MENU_LEFT:Int=6
 	Const MENU_RIGHT:Int=7
 	Const MENU_SCORES:Int=8
+	Const MENU_KEYS:Int=9
 	
 	Local done:Int=False
 	Local menu:TMenu=TMenu.Create()
@@ -170,12 +209,16 @@ Function Menu()
 	Local x1:Int=(GraphicsWidth()/2-ImageWidth(GameGFX.play_button))/2
 	Local x2:Int=x1+GraphicsWidth()/2
 	
+	Local defkey:Int=0
+	
 	menu.Add(x1,100,GameGFX.play_button,MENU_PLAY)
 	menu.Add(x2,100,GameGFX.scores_button,MENU_SCORES)
 	menu.Add(x1,200,GameGFX.load_button,MENU_LOAD)
 	menu.Add(x2,200,GameGFX.edit_button,MENU_EDIT)
 	menu.Add(x1,300,GameGFX.toy_button,MENU_TOY)
-	menu.Add(x2,300,GameGFX.quit_button,MENU_QUIT)
+	menu.Add(x2,300,GameGFX.keys_button,MENU_KEYS)
+	menu.Add(-1,400,GameGFX.quit_button,MENU_QUIT)
+
 	menu.Add(64,530,GameGFX.left_button,MENU_LEFT)
 	menu.Add(GraphicsWidth()-96,530,GameGFX.right_button,MENU_RIGHT)
 	
@@ -187,7 +230,62 @@ Function Menu()
 		
 		Cls
 		
-		sel=menu.Render()
+		If defkey>0
+		
+			SetScale(2,2)
+			
+			If defkey=6
+				GameGFX.font.Centre("PRESS A KEY TO GO BACK TO THE MENU",50)
+			Else
+				GameGFX.font.Centre("DEFINE KEYS",50)
+			EndIf
+			
+			GameGFX.font.DrawColoured("Left",250,100,255,255*(defkey=1),0)
+			GameGFX.font.DrawColoured("Right",250,120,255,255*(defkey=2),0)
+			GameGFX.font.DrawColoured("Thrust",250,140,255,255*(defkey=3),0)
+			GameGFX.font.DrawColoured("Reverse",250,160,255,255*(defkey=4),0)
+			GameGFX.font.DrawColoured("Gravity Wave",250,180,255,255*(defkey=5),0)
+			GameGFX.font.DrawColoured(KeySym(GameConfig.kleft),500,100,255,255*(defkey=1),0)
+			GameGFX.font.DrawColoured(KeySym(GameConfig.kright),500,120,255,255*(defkey=2),0)
+			GameGFX.font.DrawColoured(KeySym(GameConfig.kthrust),500,140,255,255*(defkey=3),0)
+			GameGFX.font.DrawColoured(KeySym(GameConfig.kreverse),500,160,255,255*(defkey=4),0)
+			GameGFX.font.DrawColoured(KeySym(GameConfig.kblast),500,180,255,255*(defkey=5),0)
+			
+			Local k:Int=0
+			
+			For Local f:Int=1 To 255
+				If KeyHit(f)
+					k=f
+					Continue
+				EndIf
+			Next
+			
+			If k<>0
+				Select defkey
+					Case 1
+						GameConfig.kleft=k
+					Case 2
+						GameConfig.kright=k
+					Case 3
+						GameConfig.kthrust=k
+					Case 4
+						GameConfig.kreverse=k
+					Case 5
+						GameConfig.kblast=k
+				End Select
+				
+				defkey:+1
+				
+				If defkey=7
+					GameConfig.Save()
+					defkey=0
+				EndIf
+			EndIf
+			
+			SetScale(1,1)
+		EndIf
+
+		sel=menu.Render(defkey>0)
 		
 		Select sel
 			Case MENU_PLAY
@@ -203,6 +301,9 @@ Function Menu()
 			Case MENU_TOY
 				done=True
 				FlushKeys()
+			Case MENU_KEYS
+				FlushKeys()
+				defkey=1
 			Case MENU_QUIT
 				done=True
 				quit=True
@@ -215,11 +316,14 @@ Function Menu()
 			Case MENU_RIGHT
 				selected_level=(selected_level+1) Mod levelset.level.Count()
 		End Select
-		
+	
 		SetScale(2,2)
 		GameGFX.font.CentreColoured("PARTICLE PINCH",0,255,255,0)
-		GameGFX.font.CentreColoured("Start Level",530,0,255,255)
-		GameGFX.font.Centre(levelset.Get(selected_level).name,545)
+		
+		If defkey=0
+			GameGFX.font.CentreColoured("Start Level",528,0,255,255)
+			GameGFX.font.Centre(levelset.Get(selected_level).name,547)
+		EndIf
 
 		SetScale(1,1)
 		GameGFX.font.CentreColoured("Copyright (c) 2005 Ian Cowburn",20,255,0,0)
diff --git a/types.bmx b/types.bmx
index 31cc433..caf62ad 100644
--- a/types.bmx
+++ b/types.bmx
@@ -10,16 +10,34 @@ Import noddybox.bitmapfont
 
 Const MASSSIZE:Int=8
 Const MASSRAD:Int=4
+Const SHIPSIZE:Int=12
+Const SHIPRAD:Int=6
+
+Type Lookup
+	Global si:Double[]
+	Global co:Double[]
+	
+	Function Init()
+		si=New Double[360]
+		co=New Double[360]
+		
+		For Local a:Int=0 To 359
+			si[a]=Sin(a)
+			co[a]=Cos(a)
+		Next
+	End Function
+End Type
 
 Type GameGFX
 	Global font:TBitmapFont
 	Global smallfont:TBitmapFont
 	Global star:TImage
-	Global mass:TImage
+	Global ship:TImage
 	Global collector:TImage
 	Global point:TImage
 	Global particle:Timage
 	Global pointer:TImage
+	Global shock:TImage
 	
 	Global play_button:TImage
 	Global edit_button:TImage
@@ -29,17 +47,71 @@ Type GameGFX
 	Global left_button:TImage
 	Global right_button:TImage
 	Global scores_button:TImage
+	Global keys_button:TImage
 End Type
 
-Type TMass
+Type GameConfig
+	Global kleft:Int
+	Global kright:Int
+	Global kthrust:Int
+	Global kreverse:Int
+	Global kblast:Int
+	
+	Function Load()
+		Local s:TStream=ReadStream("ppinch.config")
+		
+		If s=Null
+			kleft=KEY_O
+			kright=KEY_P
+			kthrust=KEY_Q
+			kreverse=KEY_A
+			kblast=KEY_SPACE
+			Return
+		EndIf
+		
+		s=LittleEndianStream(s)
+		
+		kleft=s.ReadInt()
+		kright=s.ReadInt()
+		kthrust=s.ReadInt()
+		kreverse=s.ReadInt()
+		kblast=s.ReadInt()
+		
+		s.Close()
+	End Function
+	
+	Function Save()
+		Local s:TStream=WriteStream("ppinch.config")
+		
+		If s=Null
+			Return
+		EndIf
+		
+		s=LittleEndianStream(s)
+		
+		s.WriteInt(kleft)
+		s.WriteInt(kright)
+		s.WriteInt(kthrust)
+		s.WriteInt(kreverse)
+		s.WriteInt(kblast)
+		
+		s.Close()
+	End Function
+EndType
+
+Type TGfxObject
 	Field x:Double
 	Field y:Double
+EndType
+
+Type TMass Extends TGfxObject
 	Field v:TVector
 	Field mass:Double
 	Field friend:Int
 	Field inverse:Int
 	Field img:TImage
 	Field swallow:Int
+	Field rad:Int
 	
 	Method New()
 		v=TVector.Create()
@@ -49,6 +121,7 @@ Type TMass
 		friend=True
 		inverse=False
 		swallow=0
+		rad=MASSRAD
 	End Method
 	
 	Method Attract(o:TMass)
@@ -70,15 +143,43 @@ Type TMass
 			
 			v.Add(d)
 			
-			If d.Length()>MASSSIZE
-				d.SetLength(MASSSIZE)
+			If v.Length()>MASSSIZE
+				v.SetLength(MASSSIZE)
 			EndIf
 		EndIf
 	End Method
 	
-	Method Move()
+	Method Move(wrap:Int)
 		x:+v.x
 		y:+v.y
+
+		If wrap
+			If x<0 
+				x:+GraphicsWidth()
+			EndIf
+			If x>GraphicsWidth()
+				x:-GraphicsWidth()
+			EndIf
+			If y<0 
+				y:+GraphicsHeight()
+			EndIf
+			If y>GraphicsHeight() 
+				y:-GraphicsHeight()
+			EndIf
+		Else
+			If x<0 
+				x=0
+			EndIf
+			If x>GraphicsWidth()
+				x=GraphicsWidth()
+			EndIf
+			If y<0 
+				y=0
+			EndIf
+			If y>GraphicsHeight() 
+				y=GraphicsHeight()
+			EndIf
+		EndIf
 	End Method
 
 	Method Draw()
@@ -89,11 +190,61 @@ Type TMass
 End Type
 
 
-Type TPoint
+Type TShip Extends TMass
+	Field a:Int
+	
+	Method New()
+		v=TVector.Create()
+		x=-1
+		y=-1
+		mass=25
+		friend=False
+		inverse=False
+		swallow=0
+		rad=SHIPRAD
+		a=0
+	End Method
+	
+	Method RotateLeft()
+		a:-2
+		If a<0
+			a:+359
+		EndIf
+	End Method
+	
+	Method RotateRight()
+		a=(a+2) Mod 360
+	End Method
+	
+	Method Thrust()
+		v.x:+Lookup.si[a]*0.1
+		v.y:+Lookup.co[a]*-0.1
+		If v.Length()>MASSSIZE
+			v.SetLength(MASSSIZE)
+		EndIf
+	End Method
+	
+	Method Reverse()
+		v.x:+Lookup.si[a]*-0.05
+		v.y:+Lookup.co[a]*0.05
+		If v.Length()>MASSSIZE
+			v.SetLength(MASSSIZE)
+		EndIf
+	End Method
+	
+	Method Draw()
+		SetColor(255,255,255)
+		SetRotation(a)
+		DrawImage(img,x,y)
+		SetRotation(0)
+		swallow=0
+	End Method
+End Type
+
+
+Type TPoint Extends TGfxObject
 	Global img:TImage
 	
-	Field x:Double
-	Field y:Double
 	Field lx:Double
 	Field ly:Double
 	Field v:TVector
@@ -124,7 +275,7 @@ Type TPoint
 		Local d:TVector=TVector.Create(o.x-x,o.y-y)
 		Local l:Double=d.Length()
 		
-		If l<MASSRAD
+		If l<o.rad
 			If o.friend
 				dead=True
 			Else
@@ -143,24 +294,39 @@ Type TPoint
 			
 			v.Add(d)
 			
-			If d.Length()>MASSRAD
-				d.SetLength(MASSRAD)
+			If v.Length()>MASSRAD
+				v.SetLength(MASSRAD)
 			EndIf
 		EndIf
 		
 		Return dead
 	End Method
 	
-	Method Move()
+	Method Move(wrap:Int=False)
 		If (Not dead) And (Not lost)
 			lx=x
 			ly=y
 			x:+v.x
 			y:+v.y
 			
-			If x<0 Or y<0 Or x>GraphicsWidth() Or y>GraphicsHeight()
-				lost=True
-				TParticleMachine.AddLost(Self)
+			If wrap
+				If x<0 
+					x:+GraphicsWidth()
+				EndIf
+				If x>GraphicsWidth()
+					x:-GraphicsWidth()
+				EndIf
+				If y<0 
+					y:+GraphicsHeight()
+				EndIf
+				If y>GraphicsHeight() 
+					y:-GraphicsHeight()
+				EndIf
+			Else
+				If x<0 Or y<0 Or x>GraphicsWidth() Or y>GraphicsHeight()
+					lost=True
+					TParticleMachine.AddLost(Self)
+				EndIf
 			EndIf
 		EndIf
 	End Method
@@ -176,6 +342,7 @@ End Type
 
 Type TParticle
 	Global img:TImage
+	Field parent:TGfxObject
 	Field x:Double
 	Field y:Double
 	Field a:Double
@@ -228,6 +395,21 @@ Type TParticle
 		Return o
 	End Function
 	
+	Function Angular:TParticle(x:Int, y:Int, a:Int, sp:Double, r:Int, g:Int, b:Int, al:Double, ali:Double, parent:TGfxObject)
+		Local o:TParticle=New TParticle
+		o.parent=parent
+		o.x=x
+		o.y=y
+		o.dx=sp*Lookup.si[a]
+		o.dy=sp*Lookup.co[a]
+		o.r=r
+		o.g=g
+		o.b=b
+		o.a=al
+		o.ai=ali
+		Return o
+	End Function
+	
 	Method Update()
 		x:+dx
 		y:+dy
@@ -236,7 +418,12 @@ Type TParticle
 		If a>0
 			SetAlpha(a)
 			SetColor(r,g,b)
-			DrawImage(img,x,y)
+			
+			If parent
+				DrawImage(img,parent.x+x,parent.y+y)
+			Else
+				DrawImage(img,x,y)
+			EndIf
 		EndIf
 	End Method
 End Type
@@ -271,6 +458,15 @@ Type TParticleMachine
 		Next
 	End Function
 	
+	Function AddShockwave(o:TGfxObject)
+		For Local a:Int=0 To 359
+			list.AddLast(TParticle.Angular(0,0,a,1.0,255,255,0,1,-0.01,o))
+			list.AddLast(TParticle.Angular(0,0,a,Rnd(0.8,1.2),255,0,0,1,-0.01,o))
+			list.AddLast(TParticle.Angular(0,0,a,Rnd(0.8,1.2),255,0,0,1,-0.01,o))
+			list.AddLast(TParticle.Angular(0,0,a,Rnd(0.8,1.2),255,0,0,1,-0.01,o))
+		Next
+	End Function
+	
 	Function Process()
 		Local l:TLink=list.FirstLink()
 		Local t:TLink
-- 
cgit v1.2.3