Code archives/Miscellaneous/Project PLASMA FPS 2004: Bot.bb

This code has been declared by its author to be Public Domain code.

Download source code

Project PLASMA FPS 2004: Bot.bb by Techlord2004
WARNING!!! This is code is still under development and not yet suitable for use. Once complete I will remove this warning.

The Bot Module is designed to support the following requirements:

1. Modular Code Design. Currently there are three objects in the Bot.bb file: Bot, Waypoint, Path.
2. Non-geometry Properties stored in *.csv comma delimited file. I will write the Level Loader portion for bots.
3. Single or MultiMesh geometry w/Bone animation stored in *.b3d format.
Geometry can be loaded from within the level or independently. Standard Blitz Animation.
4. Finite State Machine AI Model
a) Bots must be able to 'see' and 'hear' players and or other bots within their field of view/range.
b) Bots must be able to attack, evade, find ammo, and find health dependent on their health and ammo status.
c) Bots must be able to navigate indoor/outdoor geometry avoiding collision with static and moving objects.


Finite State Machine

The function of the FSM for bots is divided into 3 functions: Sensor, Reaction, and Motor.
A Sensor is a conditional check (linepicks, health compares, etc) that compares the bot
against the environment setting its AI state. A Reaction is a 'action function' selected dependent
on the bots ai state. The Motor controls movement and animation dependent on the Reaction.

The PathFinding System

The Pathfinding System can support the use of Precalculated PathTables, Real-time A* Multi-tier Pathfinding,
CollisionMaps w/ variable Terrain Cost for Realtime pathfinding, and Precalculated and Realtime Hybrid.
Precalculated PathTables offer the greatest performance advantage and are ideal for indoor level maps.
Realtime Pathfinding is ideal for avoiding moving obstacles.

Summary of functions:


Last Update 02/03/04
Check out the Wip Zip for demos and more code!
;============================
;WAYPOINT
;============================
Const WAYPOINT_MAX=2048
Dim waypointId.waypoint(WAYPOINT_MAX)
Global waypointIndex.stack=stackIndexCreate(WAYPOINT_MAX)
Global waypointAvail.stack=stackIndexCreate(WAYPOINT_MAX)
Global waypoint0.waypoint=New waypoint
Global waypoints%

Type waypoint
	Field id%
	Field typeid%
	Field parent.waypoint
	Field e% ;terrain cost
	Field f% ;fcost
	Field g% ;parent+cost
	Field h% ;heuristic manhattan
	Field state%
	Field position.vector
	Field entity
End Type

Function waypointStart()
	entity%=CreateCube()
	HideEntity entity%
	For loop = 1 To  WAYPOINT_MAX
		this.waypoint=waypointNew()
		this\entity%=CopyEntity(entity%)
		EntityPickMode this\entity,1,False 
		ScaleEntity this\entity%,.1,.1,.1
		HideEntity this\entity%
	Next
	DebugLog("Waypoint Initialized ["+Str(WAYPOINT_MAX)+"]")
End Function

Function waypointStop()
	For this.waypoint=Each waypoint
		waypointDelete(this)
	Next
End Function

Function waypointNew.waypoint()
	this.waypoint=New waypoint
	this\id%=0
	this\typeid%=0
	this\e%=0
	this\f%=0
	this\g%=0
	this\h%=0
	this\state%=0
	this\position.vector=vectorNew()
	this\id%=StackPop(waypointIndex.stack)
	waypointId(this\id)=this
	Return this
End Function

Function waypointDelete(this.waypoint)
	waypointId(this\id)=Null
	StackPush(waypointIndex.stack,this\id%)
	vectorDelete(this\position.vector)
	this\state%=0
	Delete this
End Function

Function waypointRead.waypoint(file)
	this.waypoint=New waypoint
	this\id%=ReadInt(file)
	this\typeid%=ReadInt(file)
	;this\parent.waypoint=waypointRead(file)
	this\e%=ReadInt(file)
	this\f%=ReadInt(file)
	this\g%=ReadInt(file)
	this\h%=ReadInt(file)
	this\state%=ReadByte(file)
	this\position.vector=vectorRead(file)
	Return this
End Function

Function waypointWrite(file,this.waypoint)
	WriteInt(file,this\id%)
	WriteInt(file,this\typeid%)
	;waypointWrite(file,this\parent.waypoint)
	WriteInt(file,this\e%)
	WriteInt(file,this\f%)
	WriteInt(file,this\g%)
	WriteInt(file,this\h%)
	WriteByte(file,this\state%)
	vectorWrite(file,this\position.vector)
End Function

Function waypointSave(filename$="Default")
	file=WriteFile(filename$+".waypoint")
	For this.waypoint= Each waypoint
		waypointWrite(file,this)
	Next
	CloseFile(file)
End Function

Function waypointOpen(filename$="Default")
	file=ReadFile(filename+".waypoint")
	Repeat
		waypointRead(file)
	Until Eof(file)
	CloseFile(file)
End Function

Function waypointCopy.waypoint(this.waypoint)
	copy.waypoint=New waypoint
	copy\id%=this\id%
	copy\typeid%=this\typeid%
	copy\parent.waypoint=waypointCopy(this\parent.waypoint)
	copy\e%=this\e%	
	copy\f%=this\f%
	copy\g%=this\g%
	copy\h%=this\h%
	copy\state%=this\state%
	copy\position.vector=vectorCopy(this\position.vector)
	Return copy
End Function

Function waypointMimic(mimic.waypoint,this.waypoint)
	mimic\id%=this\id%
	mimic\typeid%=this\typeid%
	waypointMimic(mimic\parent.waypoint,this\parent.waypoint)
	mimic\e%=this\e%	
	mimic\f%=this\f%
	mimic\g%=this\g%
	mimic\h%=this\h%
	mimic\state%=this\state%
	vectorMimic(mimic\position.vector,this\position.vector)
End Function

Function waypointCreate.waypoint(typeid%,parent.waypoint,g%,startx#,starty#,startz#,goalx#,goaly#,goalz#)
	this.waypoint=waypointNew()
	this\typeid%=typeid%
	this\parent.waypoint=parent
	this\position\x#=startx#
	this\position\y#=starty#
	this\position\z#=startz#
	this\g%=g%+this\parent\g%;find g
	this\h%=10*(Abs(goalx#-this\position\x#)+Abs(goalz#-this\position\z#));find h (manhattan)
	;this\h%=10*(Abs(goalx#-this\position\x#)+Abs(goaly#-this\position\y#)+Abs(goalz#-this\position\z#));find h (manhattan)
	this\f%=this\g%+this\h%;find f%
	Return this
End Function

Function waypointSet(this.waypoint,typeid%,parent.waypoint,g%,startx#,starty#,startz#,goalx#,goaly#,goalz#)
	this\typeid%=typeid%
	this\parent.waypoint=parent.waypoint
	this\position\x#=startx#
	this\position\y#=starty#
	this\position\z#=startz#
	this\g%=g%+this\parent\g%;find g
	this\h%=10*(Abs(goalx#-this\position\x#)+Abs(goalz#-this\position\z#));find h (manhattan)
	;this\h%=10*(Abs(goalx#-this\position\x#)+Abs(goaly#-this\position\y#)+Abs(goalz#-this\position\z#));find h (manhattan)
	this\f%=this\g%+this\h%;find f%
	this\state%=0;set/reset
End Function

;============================
;PATH
;============================
Const PATH_MAX=255
Dim pathId.path(PATH_MAX)
Global pathIndex.stack=stackIndexCreate(PATH_MAX)
Global pathAvail.stack=stackIndexCreate(PATH_MAX);return id of available queue
Dim pathTable(WAYPOINT_MAX,WAYPOINT_MAX)
Dim pathCollisionMap(MAP_WIDTH,MAP_HEIGHT,MAP_DEPTH)

;create open & close stack list
;Global pathopen.queue=queueCreate()
;Global pathclose.queue=queueCreate()

Type path
	Field id%
	Field typeid%
	Field waypoints%
	Field route.stack
	Field state% ;1=building, 2=destroying ;process slicing
	Field waypoint.waypoint
	Field goal.vector
	Field radius# ;tier worker radius
	Field distance# ;tier increment
	Field worker.worker[8]
	Field waypointadjacentg[8]
	Field open.queue
	Field close.queue
	Field waypointadjacent
End Type

Function pathStop()
	For this.path=Each path
		pathDelete(this)
	Next
End Function

Function pathNew.path()
	this.path=New path
	this\id%=0
	this\typeid%=0
	this\waypoints%=0
	this\id%=StackPop(pathIndex.stack)
	pathId(this\id)=this
	Return this
End Function

Function pathDelete(this.path)
	pathId(this\id)=Null
	StackPush(pathIndex.stack,this\id%)
	stackDelete(this\route)
	Delete this
End Function

Function pathUpdate()
	For this.path=Each path
		Select this\state%
			;Path Building
			Case 1 pathWaypointAdjacentBuild(this)
			Case 12 pathWaypointAdjacentCollisionBuild(this)
			Case 13 pathWaypointNextBuild(this)
			;Path Created
			Case 2 pathRouteBuild(this)
			;Path Destroy
			Case 3 pathOpenClean(this)
			Case 4 pathCloseClean(this)
			Case 5 pathDestroy(this)
		End Select
	Next
End Function

;A* path generation
;http://www.gamedev.net/reference/articles/article2003.asp
;http://www.policyalmanac.org/games/twoTiered.htm
	
Function pathCreate.path(pathstart.vector,pathgoal.vector,pathdistance#=2.0,pathradius#=1.0) ;two-tier A* reduce distance and worker radius for finer searches
	this.path=pathNew()
	this\open.queue=queueId(stackPop(queueAvail)) ;get available queues id
	this\close.queue=queueId(stackPop(queueAvail))
	this\goal.vector=pathgoal.vector
	this\distance#=pathdistance#
	this\radius#=pathradius#
	this\route.stack=stackCreate(127)
	;create startnode at start vector
	this\waypoint.waypoint=waypointId(stackPop(waypointAvail))
	waypointSet(this\waypoint,1,waypoint0,0,pathstart\x#,pathstart\y#,pathstart\z#,pathgoal\x#,pathgoal\y#,pathgoal\z#)
	;place waypointstartid id close queue
	queuePush(this\close,this\waypoint\f%,this\waypoint\id%)
	this\waypointadjacent=8
	pathWaypointAdjacentBuild(this)	
	Return this
End Function

Function pathWaypointAdjacentBuild(this.path)
	this\state%=12
	PositionEntity this\waypoint\entity%,this\waypoint\position\x#,this\waypoint\position\y#,this\waypoint\position\z#:EntityColor this\waypoint\entity%,0,255,0:EntityAlpha this\waypoint\entity%,.9:ShowEntity this\waypoint\entity%
	;For Each of these squares, save point A as its "parent square"
	;Look at all the reachable or walkable squares adjacent to the starting point, 
	;Move worker in 8 adjacent positions  X#,Z# (Y# influenced by gravity) 
	;other configurations: 3-8 star 	
	Restore waypointadjacent
	For loop = 1 To this\waypointadjacent
		this\worker.worker[loop]=workerID(stackPop(workerAvail))
		EntityRadius this\worker[loop]\entity%,this\radius#
		ScaleEntity worker\entity%,this\radius#,this\radius#,this\radius#
	 	;Uses waypointadjacent dataset for adjacent waypoint position & cost		
		Read waypointadjacentx%,waypointadjacentz%,waypointadjacentg%
		;adjust adjacent positions to parent
		this\worker[loop]\vector\x#=this\waypoint\position\x#+(this\distance#*waypointadjacentx%)
		this\worker[loop]\vector\z#=this\waypoint\position\z#+(this\distance#*waypointadjacentz%)
		;floor collision check/gravity adjust this\worker[loop]\vector\y#=waypointgravityoffsety#		
		;navigator wall collsion check
		this\waypointadjacentg%[loop]=waypointadjacentg%

		PositionEntity this\worker%[loop]\entity%,this\worker[loop]\vector\x#,this\worker[loop]\vector\y#,this\worker[loop]\vector\z#
		DebugLog "pathxy workerentity["+Str(this\worker[loop]\entity%)+"]="+Str(EntityX(this\worker[loop]\entity%))+","+Str(EntityY(this\worker[loop]\entity%))+","+Str(EntityZ(this\worker[loop]\entity%))
	Next
End Function 

Function pathWaypointAdjacentBuild2(this.path)
	this\state%=12
	;testing
	PositionEntity this\waypoint\entity%,this\waypoint\position\x#,this\waypoint\position\y#,this\waypoint\position\z#:EntityColor this\waypoint\entity%,0,255,0:EntityAlpha this\waypoint\entity%,.9:ShowEntity this\waypoint\entity%
	;For Each of these squares, save point A as its "parent square"
	;Look at all the reachable or walkable squares adjacent to the starting point, 
	;Move worker in 8 adjacent positions  X#,Z# (Y# influenced by gravity) 
	;other configurations: 3-8 star 
	Restore waypointadjacentang
	For loop = 1 To this\waypointadjacent
		this\worker.worker[loop]=workerID(stackPop(workerAvail))
		EntityRadius this\worker[loop]\entity%,this\radius#
		;ScaleEntity worker\entity%,this\radius#,this\radius#,this\radius#
	 	;Uses waypointadjacent dataset for adjacent waypoint position & cost
		Read waypointadjacentangle#,waypointadjacentg%
		;adjust adjacent positions to parent 
		this\worker[loop]\vector\x#=this\waypoint\position\x#+Cos2#(waypointadjacentangle#)*this\distance#
		this\worker[loop]\vector\z#=this\waypoint\position\z#+Sin2#(waypointadjacentangle#)*this\distance#	
		;floor collision check/gravity adjust this\worker[loop]\vector\y#=waypointgravityoffsety#		
		;navigator wall collsion check
		this\waypointadjacentg%[loop]=waypointadjacentg%
		PositionEntity this\worker%[loop]\entity%,this\worker[loop]\vector\x#,this\worker[loop]\vector\y#,this\worker[loop]\vector\z#
	Next
End Function 

Function pathWaypointAdjacentCollisionBuild(this.path)	
	this\state%=13
	For loop = 1 To this\waypointadjacent
		DebugLog "collision workerentity["+Str(this\worker[loop]\entity%)+"]="+Str(EntityX(this\worker[loop]\entity%))+","+Str(EntityY(this\worker[loop]\entity%))+","+Str(EntityZ(this\worker[loop]\entity%))
	
		;ignore illegal terrain  			
		If Not pathNavigatorCollision2(this\worker%[loop],level\map) ;pathCollisionMap(pathx#,pathy#,pathz#)) ;collision/ collision map array
		
			;ignore illegal positions on the closed list 
			If pathWaypointInQueue(this\close,EntityX(this\worker[loop]\entity%),EntityY(this\worker[loop]\entity%),EntityZ(this\worker[loop]\entity%))=Null ;position does not match a waypoint\position on the close list
				;If an adjacent position is already on the open list, check To see If this path To that square is a better one.
				waypoint.waypoint=pathWaypointInQueue(this\open,EntityX(this\worker[loop]\entity%),EntityY(this\worker[loop]\entity%),EntityZ(this\worker[loop]\entity%))
				;In other words, check To see If the G score For that square is Lower If we use the current square
				;If Not, don't do anything.		
				If waypoint<>Null
					;On the other hand, if the G cost of the new path is lower, change the parent of the adjacent
					;node to the selected square (in the diagram above, change the direction of the pointer 
					;to point at the selected square).
					waypointnewg%=this\waypoint\g%+this\waypointadjacentg%[loop]					
					If waypointnewg%<waypoint\g%
						waypoint\parent=this\waypoint
						;Finally, recalculate both the F And G scores of that square.
						waypoint\g%=waypointnewg%
						waypoint\f%=waypoint\g%+waypoint\h%
						;resort
					EndIf	
				Else		
					;new waypoint
					waypoint.waypoint=waypointId(stackPop(waypointAvail))
					waypointSet(waypoint,1,this\waypoint,this\waypointadjacentg%[loop],EntityX(this\worker[loop]\entity%),EntityY(this\worker[loop]\entity%),EntityZ(this\worker[loop]\entity%),this\goal\x#,this\goal\y#,this\goal\z#)
					;Add them To the open list, too.
					queuePush(this\open,waypoint\f%,waypoint\id%)
					;testing
					PositionEntity waypoint\entity%,waypoint\position\x#,waypoint\position\y#,waypoint\position\z#:EntityColor waypoint\entity%,255,255,0:EntityAlpha waypoint\entity%,.9:ShowEntity waypoint\entity%
				EndIf
			EndIf	
		Else ;testing to be removed?
			navigator%=CreateSphere(8)
			ScaleEntity navigator%,this\radius#,this\radius#,this\radius#
			PositionEntity navigator%,EntityX(this\worker[loop]\entity%),EntityY(this\worker[loop]\entity%),EntityZ(this\worker[loop]\entity%)
			EntityAlpha navigator%,.5
			RenderWorld()
			Flip()
		EndIf
		stackPush(workerAvail,this\worker[loop]\id)
	Next
End Function

Function pathWaypointNextBuild(this.path)	
	this\state%=1	
	;to continue the search
	;we simply choose the waypoint with the lowest F score square from all those that are on the open list
	;Drop it from the open list And add it To the closed list.
	If Not this\open\queueitems RuntimeError("No items in open queue | collisioncount["+Str(entitycollisioncount%)+"]") ;testing
	EntityColor this\waypoint\entity%,0,0,255 ;testing
	this\waypoint=waypointId(queuePop(this\open))	
	queuePush(this\close,this\waypoint\f%,this\waypoint\id%)
	EntityColor this\waypoint\entity,0,255,255;testing	
	entitycollisioncount=reset
	;path found
;	If this\waypoint\h%=0 this\state%=2
	If this\waypoint\position\x#<this\goal\x#+this\distance# And this\waypoint\position\x#>this\goal\x#-this\distance#
;		;If this\waypoint\position\y#<this\goal\y#+this\distance# And this\waypoint\position\y#>this\goal\y#-this\distance#; omit for gravity and collision
			If this\waypoint\position\z#<this\goal\z#+this\distance# And this\waypoint\position\z#>this\goal\z#-this\distance#
				this\state%=2;Path Route
			EndIf
;		;EndIf
	EndIf
End Function

Function pathNavigatorCollision%(worker.worker,typeofentity%);perform non stop collision detection
	DebugLog("worker\entity["+Str(worker\entity%)+"]="+Str$(worker\collision%)+","+Str(EntityX(worker\entity%))+","+Str(EntityY(worker\entity%))+","+Str(EntityZ(worker\entity%)))
	worker\collision%=EntityCollided(worker\entity%,typeofentity%)	
	If worker\collision%
		ResetEntity worker\entity%
		entitycollisioncount%=entitycollisioncount%+1 ;testing
	EndIf
	Return worker\collision%		
End Function

Function pathNavigatorCollision2%(worker.worker,typeofentity%);perform non stop collision detection
	DebugLog("worker\entity["+Str(worker\entity%)+"]="+Str$(worker\collision%)+","+Str(EntityX(worker\entity%))+","+Str(EntityY(worker\entity%))+","+Str(EntityZ(worker\entity%)))
	worker\collision%=MeshesIntersect(worker\entity%,typeofentity%)	
	If worker\collision% entitycollisioncount%=entitycollisioncount%+1 ;testing
	Return worker\collision%		
End Function

Global waypointincqueuecount,waypointincqueuecountmax ;testing

Function pathWaypointInQueue.waypoint(queue.queue,x#,y#,z#)
	For loop = 1 To queue\queueitems%
		waypointincqueuecount=loop;testing
		waypoint.waypoint=waypointId(queue\queueitem[loop]\dat%)
		If waypoint=Null Return waypoint
		If waypoint\position\x#=x# 
			;If waypoint\position\y#=y# 
				If waypoint\position\z#=z#
					Return waypoint
				EndIf	
			;EndIf
		EndIf			
	Next	
End Function

Function pathOpenClean(this.path) 
	waypoint.waypoint=waypointId(queuePopLast(this\open))
	If waypoint<>Null
		stackPush(waypointAvail,waypoint\id%)
		HideEntity waypoint\entity;testing
		If Not this\open\queueitems%
			stackPush(queueAvail,this\open\id%)		
			this\state%=4
		EndIf	
	EndIf	
End Function

Function pathCloseClean(this.path)
	waypoint.waypoint=waypointId(queuePopLast(this\close))
	If waypoint<>Null 
		EntityColor waypoint\entity,255,0,0;testing
		If Not waypoint\state%
			stackPush(waypointAvail,waypoint\id%)
			HideEntity waypoint\entity;testing
		EndIf
	EndIf
	If Not this\close\queueitems%
		stackPush(queueAvail,this\close\id%)		
		this\state%=5
	EndIf
End Function

Function pathDestroy(this.path)
	If Not this\waypoints% pathDelete(this)
End Function

Function pathRouteBuild(this.path)
	this\waypoint\state%=1
	this\waypoints%=this\waypoints%+1
	EntityColor this\waypoint\entity%,0,255,0;testing
	stackPush(this\route,this\waypoint\id%)
	this\waypoint=this\waypoint\parent
	If Not this\waypoint\parent\id this\state%=3 ;path build complete
End Function

Function pathRouteNext(this.path)
	this\waypoint=waypointId(stackPop(this\route))
	DebugLog("pathRouteNext="+Str(this\waypoint\id))
	stackPush(waypointAvail,this\waypoint\id%)				
	HideEntity this\waypoint\entity%;testing
	this\waypoints%=this\waypoints%-1
End Function

Function pathCollisionMapLoad(filename$)
	file=ReadFile(filename$+".collisionmap")
	If file
		While Not Eof(file)
			;				 MAP_WIDTH 		MAP_HIEGHT	   MAP_WIDTH       walkableflg
			pathCollisionMap(ReadByte(file),ReadByte(file),ReadByte(file))=ReadByte(file)
		Wend
		CloseFile(file)
	EndIf
End Function

Function pathCollisionMapBuild(this.path)
End Function
			
.waypointadjacent
;+14    +10    +14 ;diagonal movement g = 14
;  \    |    /
;    \  |  /
;      \|/     
;+10----0------+10 ;horizontal\vertical g  = 10
;      /|\   
;    /  |  \
;  /    |    \	
;+14    +10    +14
;...  x,  z, gcost
Data  0,  1, 10
Data -1,  0, 10
Data  0, -1, 10
Data  1,  0, 10
Data -1,  1, 14
Data -1, -1, 14
Data  1, -1, 14
Data  1,  1, 14
	
.waypointadjacent3D
;horizontal\vertical g  = 10
;diagonal movement g = 14
;...  x,  y,  z, gcost
Data  0,  0,  1, 10
Data -1,  0,  0, 10
Data  0,  0, -1, 10
Data  1,  0,  0, 10
Data -1,  0,  1, 14
Data -1,  0, -1, 14
Data  1,  0, -1, 14
Data  1,  0,  1, 14
Data  0, -1,  0, 10
Data  0,  1,  0, 10
Data  0, -1,  1, 14
Data -1, -1,  0, 14
Data  0, -1, -1, 14
Data  1, -1,  0, 14
Data  0,  1,  1, 14
Data -1,  1,  0, 14
Data  0,  1, -1, 14
Data  1,  1,  0, 14
Data -1, -1,  1, 14
Data -1, -1, -1, 14
Data  1, -1, -1, 14
Data  1, -1,  1, 14
Data -1,  1,  1, 14
Data -1,  1, -1, 14
Data  1,  1, -1, 14
Data  1,  1,  1, 14

.waypointadjacentang
;Data angle,cost
;hort,vert 90 deg
Data 0.0,10
Data 90.0,10
Data 180.0,10
Data 270.0,10
;diagonal 45 deg
Data 45.0,14
Data 135.0,14
Data 225.0,14
Data 315.0,14
;diag 22.5
Data 22.5,12
Data 67.5,12
Data 112.5,12
Data 157.7,12
Data 202.5,12
Data 247.5,12
Data 292.5,12
Data 337.5,12
;diagonal 15
Data 15.0,13
Data 30.0,13
Data 60.0,13
Data 75.0,13
Data 105.0,13
Data 120.0,13
Data 150.0,13
Data 165.0,13
Data 195.0,13
Data 210.0,13
Data 240.0,13
Data 255.0,13
Data 285.0,13
Data 300.0,13
Data 330.0,13
Data 345.0,13

;============================
;WEAPON (temp)
;============================
Type weapon
End Type

;============================
;BOT
;============================
Const BOT_MAX=64
Dim botId.bot(BOT_MAX)
Global botIndex.stack=stackIndexCreate(BOT_MAX)

;Const BOT_STATE_STAND_IDLE=0
;Const BOT_STATE_STAND_FIRE =0
;Const BOT_STATE_STAND_TAUNT=0
;Const BOT_STATE_WALK=0
;Const BOT_STATE_WALK_FIRE =0
;Const BOT_STATE_RUN=0
;Const BOT_STATE_RUN_REVERSE=0
;Const BOT_STATE_RUN_REVERSE_FIRE=0
;Const BOT_STATE_RUN_FIRE=0
;Const BOT_STATE_STRAFE_LEFT=0
;Const BOT_STATE_STRAFE_RIGHT=0
;Const BOT_STATE_STRAFE_LEFT_FIRE=0
;Const BOT_STATE_STRAFE_RIGHT_FIRE=0
;Const BOT_STATE_JUMP=0
;Const BOT_STATE_JUMP_FIRE=0
;Const BOT_STATE_SCAN=0
;Const BOT_STATE_ACQUIRE_WEAPON=0
;Const BOT_STATE_ACQUIRE_AMMO=0
;Const BOT_STATE_ACQUIRE_ARMOR=0
;Const BOT_STATE_ACQUIRE_COVER=0
;Const BOT_STATE_ACQUIRE_TEAMMEMBER=0
;Const BOT_STATE_CALL_TEAMMEMBER=0

Type bot
	Field id%
	Field typeid%
	Field entity%,thinker%; temp
	Field texture.texture
	Field position.vector
	Field old.vector
	Field angle.vector
	;sensors
	Field sight.vector
	Field hearing#
	Field fullhealth#
	Field health#
	Field speed#
	Field turn#
	;motor
	Field direction%
	Field target%
	Field goal.vector
	Field path.path
	Field waypointID%
	Field sequence%
	Field frame%
	Field weapon.weapon
	;ai
	Field state%
	Field action.action
End Type

Function botStop()
	For this.bot=Each bot
		botDelete(this)
	Next
End Function

Function botNew.bot()
	this.bot=New bot
	this\id%=0
	this\typeid%=0
	this\entity%=0
	;this\texture.texture=textureNew()
	this\position.vector=vectorNew()
	this\old.vector=vectorNew()
	this\angle.vector=vectorNew()
	this\sight.vector=vectorNew()
	this\hearing#=0.0
	this\fullhealth#=0.0
	this\health#=0.0
	this\speed#=0.0
	this\goal.vector=vectorNew()
	;this\path.path=pathNew()
	this\frame%=0
	;this\weapon.weapon=weaponNew()
	this\state%=0
	this\action.action=actionNew()
	this\id%=StackPop(botIndex.stack)
	botId(this\id)=this
	Return this
End Function

Function botDelete(this.bot)
	botId(this\id)=Null
	StackPush(botIndex.stack,this\id%)
	actionDelete(this\action.action)
	;weaponDelete(this\weapon.weapon)
	;pathDelete(this\path.path)
	vectorDelete(this\goal.vector)
	this\speed#=0.0
	this\health#=0.0
	this\fullhealth#=0.0
	this\hearing#=0.0
	vectorDelete(this\sight.vector)
	vectorDelete(this\angle.vector)
	vectorDelete(this\old.vector)
	vectorDelete(this\position.vector)
	;textureDelete(this\texture.texture)
	FreeEntity this\entity%
	Delete this
End Function

Function botUpdate()
	For this.bot = Each bot
		Select this\typeid
			Case 1
				botSensor(this)
				botReaction(this)
				botMotor(this)
			Default
				If player_visible  ; this mode will let us watch them walking along the waypoints  
					;botSDSensor(this)	
				Else
					botSDMotor(this)
				EndIf	
		End Select	
	Next
End Function 

Function botRead.bot(file)
	this.bot=New bot
	this\id%=ReadInt(file)
	this\typeid%=ReadInt(file)
	this\entity%=ReadInt(file)
	;this\texture.texture=textureRead(file)
	this\position.vector=vectorRead(file)
	this\old.vector=vectorRead(file)
	this\angle.vector=vectorRead(file)
	this\sight.vector=vectorRead(file)
	this\hearing#=ReadFloat(file)
	this\fullhealth#=ReadFloat(file)
	this\health#=ReadFloat(file)
	this\speed#=ReadFloat(file)
	this\goal.vector=vectorRead(file)
	;this\path.path=pathRead(file)
	this\frame%=ReadInt(file)
	;this\weapon.weapon=weaponRead(file)
	this\state%=ReadInt(file)
	;this\action.action=actionRead(file)
	Return this
End Function

Function botWrite(file,this.bot)
	WriteInt(file,this\id%)
	WriteInt(file,this\typeid%)
	WriteInt(file,this\entity%)
	;textureWrite(file,this\texture.texture)
	vectorWrite(file,this\position.vector)
	vectorWrite(file,this\old.vector)
	vectorWrite(file,this\angle.vector)
	vectorWrite(file,this\sight.vector)
	WriteFloat(file,this\hearing#)
	WriteFloat(file,this\fullhealth#)
	WriteFloat(file,this\health#)
	WriteFloat(file,this\speed#)
	vectorWrite(file,this\goal.vector)
	;pathWrite(file,this\path.path)
	WriteInt(file,this\frame%)
	;weaponWrite(file,this\weapon.weapon)
	WriteInt(file,this\state%)
	;actionWrite(file,this\action.action)
End Function

Function botSave(filename$="Default")
	file=WriteFile(filename$+".bot")
	For this.bot= Each bot
		botWrite(file,this)
	Next
	CloseFile(file)
End Function

Function botOpen(filename$="Default")
	file=ReadFile(filename+".bot")
	Repeat
		botRead(file)
	Until Eof(file)
	CloseFile(file)
End Function

Function botCopy.bot(this.bot)
	copy.bot=New bot
	copy\id%=this\id%
	copy\typeid%=this\typeid%
	copy\entity%=this\entity%
	;copy\texture.texture=textureCopy(this\texture.texture)
	copy\position.vector=vectorCopy(this\position.vector)
	copy\old.vector=vectorCopy(this\old.vector)
	copy\angle.vector=vectorCopy(this\angle.vector)
	copy\sight.vector=vectorCopy(this\sight.vector)
	copy\hearing#=this\hearing#
	copy\fullhealth#=this\fullhealth#
	copy\health#=this\health#
	copy\speed#=this\speed#
	copy\goal.vector=vectorCopy(this\goal.vector)
	;copy\path.path=pathCopy(this\path.path)
	copy\frame%=this\frame%
	;copy\weapon.weapon=weaponCopy(this\weapon.weapon)
	copy\state%=this\state%
	copy\action.action=actionCopy(this\action.action)
	Return copy
End Function

Function botMimic(mimic.bot,this.bot)
	mimic\id%=this\id%
	mimic\typeid%=this\typeid%
	mimic\entity%=this\entity%
	;textureMimic(mimic\texture.texture,this\texture.texture)
	vectorMimic(mimic\position.vector,this\position.vector)
	vectorMimic(mimic\old.vector,this\old.vector)
	vectorMimic(mimic\angle.vector,this\angle.vector)
	vectorMimic(mimic\sight.vector,this\sight.vector)
	mimic\hearing#=this\hearing#
	mimic\fullhealth#=this\fullhealth#
	mimic\health#=this\health#
	mimic\speed#=this\speed#
	vectorMimic(mimic\goal.vector,this\goal.vector)
	;pathMimic(mimic\path.path,this\path.path)
	mimic\frame%=this\frame%
	;weaponMimic(mimic\weapon.weapon,this\weapon.weapon)
	mimic\state%=this\state%
	actionMimic(mimic\action.action,this\action.action)
End Function

Function botCreate.bot(id%,typeid%,entity%,texture.texture,position.vector,old.vector,angle.vector,sight.vector,hearing#,fullhealth#,health#,speed#,goal.vector,path.path,frame%,weapon.weapon,state%,action.action)
	this.bot=botNew()
	this\id%=id%
	this\typeid%=typeid%
	this\entity%=entity%
	;this\texture.texture=texture.texture
	this\position.vector=position.vector
	this\old.vector=old.vector
	this\angle.vector=angle.vector
	this\sight.vector=sight.vector
	this\hearing#=hearing#
	this\fullhealth#=fullhealth#
	this\health#=health#
	this\speed#=speed#
	this\goal.vector=goal.vector
	this\path.path=path.path
	this\frame%=frame%
	;this\weapon.weapon=weapon.weapon
	this\state%=state%
	this\action.action=action.action
	Return this
End Function

Function botSet(this.bot,id%,typeid%,entity%,texture.texture,position.vector,old.vector,angle.vector,sight.vector,hearing#,fullhealth#,health#,speed#,goal.vector,path.path,frame%,weapon.weapon,state%,action.action)
	this\id%=id%
	this\typeid%=typeid%
	this\entity%=entity%
	this\texture.texture=texture.texture
	this\position.vector=position.vector
	this\old.vector=old.vector
	this\angle.vector=angle.vector
	this\sight.vector=sight.vector
	this\hearing#=hearing#
	this\fullhealth#=fullhealth#
	this\health#=health#
	this\speed#=speed#
	this\goal.vector=goal.vector
	this\path.path=path.path
	this\frame%=frame%
	this\weapon.weapon=weapon.weapon
	this\state%=state%
	this\action.action=action.action
End Function

Function botSensor(this.bot);input
;	If LinePick(this\position\x#,this\position\y#,this\position\z#,this\sight\x#,this\sight\y#,this\sight\z#,radius#)=player this\state=1 ;visual check
;	If ChannelPlaying(sound) And EntityDistance(this\entity%,sound)<=this\hear# this\state=1;audio check
;	If this\health<this\fullhealth*.25 And this\state=0 this\state=2;health check
;	If this\health<this\fullhealth*.25 And this\state=1 this\state=3;health check 2
;	If this\weapon[this\weapon\current]\ammo<this\fullammo*.25 this\state=4;ammo check
;	If this\health<0 this\state=5
	
	If this\state=4 ;seek path
		If this\path<>Null
			If this\path\waypoints%=0 this\state%=0;goal achieve
		Else
			this\state=0
		EndIf	
	EndIf
	
	If this\state=3 ;building path
		If this\path\state=3 
			pathRouteNext(this\path)
			this\state%=4;moving						
		EndIf	
	EndIf
			
	If this\state=1 Or this\state=2 this\state=3

	If KeyHit(2) ;create path
		;Delay 2000 
		this\state=1;testing 
	EndIf	
	
	If KeyHit(3) ;create path
		;Delay 2000
		this\state=2;testing
	EndIf	
	
	If KeyHit(4) 
		vectorSet(this\position,Int(Rnd(40)*2),0,Int(Rnd(40)*2))
		PositionEntity this\entity,this\position\x#,this\position\y#,this\position\z#
	EndIf
	
	If KeyHit(5) 
		vectorSet(this\goal,Int(Rnd(40)*2),0,Int(Rnd(40)*2))
		PositionEntity goalentity,this\goal\x#,this\goal\y#,this\goal\z#
	EndIf	
	
	If KeyHit(6) botSpawn()
		
End Function

Function botReaction(this.bot);action
	Select this\state
		;idle long acquisition
		Case 1 this\path.path=pathCreate(this\position,this\goal,2,.2)
		;short acquisition					
		Case 2 this\path.path=pathCreate(this\position,this\goal,1,.2)
	End Select
End Function

Function botMotor(this.bot);movement and animation
;	;follow path
	If this\state=4 botwaypointSeek(this);moving
	If this\state=6 botWaypointSeek2(this);testing
;	If collision regeneratepath
;	If this\animation botAnimate(this)
;	If this\physics botPhysics(this)
End Function

Function botwaypointSeek(this.bot)
	;simple this\path\waypoint AI
	PositionEntity this\path\waypoint\entity%,this\path\waypoint\position\x#,this\path\waypoint\position\y#,this\path\waypoint\position\z#
	PointEntity this\entity,this\path\waypoint\entity%
	MoveEntity this\entity,0,0,this\speed#
	vectorSet(this\position,EntityX(this\entity),EntityY(this\entity),EntityZ(this\entity))
	If this\position\x#<this\path\waypoint\position\x#+.5 And this\position\x#>this\path\waypoint\position\x#-.5
		;If this\position\y#<this\path\waypoint\position\y#+.5 And this\position\y#>this\path\waypoint\position\y#-.5; omit for gravity and collision
			If this\position\z#<this\path\waypoint\position\z#+.5 And this\position\z#>this\path\waypoint\position\z#-.5
				pathRouteNext(this\path)
			EndIf
		;EndIf
	EndIf
	 	
End Function

Function botWaypointSeek2(this.bot)
	;simple waypoint AI
	PositionEntity this\path\waypoint\entity%,this\path\waypoint\position\x#,this\path\waypoint\position\y#,this\path\waypoint\position\z#
	PointEntity this\entity,this\path\waypoint\entity%
	MoveEntity this\entity,0,0,this\speed#
	vectorSet(this\position,EntityX(this\entity),EntityY(this\entity),EntityZ(this\entity))
	If this\position\x#<this\path\waypoint\position\x#+.5 And this\position\x#>this\path\waypoint\position\x#-.5
		;If this\position\y#<this\path\waypoint\position\y#+.5 And this\position\y#>this\path\waypoint\position\y#-.5; omit for gravity and collision
			If this\position\z#<this\path\waypoint\position\z#+.5 And this\position\z#>this\path\waypoint\position\z#-.5
				this\path\waypoint=After this\path\waypoint 
				If this\path\waypoint=Null this\path\waypoint=waypointId(1)
			EndIf
		;EndIf
	EndIf 	
End Function

Function botSpawn.bot()
	this.bot=botNew()
	this\typeid%=typeid%
	this\entity%=CreateCylinder(8,True)
	EntityColor this\entity,Rnd(255),Rnd(255),Rnd(255)
	vectorSet(this\position,Int(Rnd(40)*2),0,Int(Rnd(40)*2))
	PositionEntity this\entity%,this\position\x#,this\position\y#,this\position\z#
	vectorSet(this\goal,Int(Rnd(40)*2),0,Int(Rnd(40)*2))
	PositionEntity goalentity,this\goal\x#,this\goal\y#,this\goal\z#
	this\speed#=Rnd(.01,.5)	
	Return this
End Function

Function botSDSensor(this.bot)
	;PositionEntity worker\entity%,waypointID(this\waypointID%+this\direction)\position\x#,waypointID(this\waypointID%+this\direction)\position\y#,waypointID(this\waypointID+this\direction)\position\z#
	EntityColor this\thinker%,0,255,0
	cur_d#=EntityDistance(this\target%,this\entity%) ;(Abs(EntityX(this\target)-this\position\x#)+Abs(EntityY(this\target)#-this\position\y#)+Abs(EntityZ(this\target)#-this\position\z#));
	If cur_d<1.0 
		this\state%=0
		botSDReaction(this)
		Return
	ElseIf cur_d>10.0 
		this\state%=3
		botSDReaction(this)
		Return
	EndIf
	
	If cur_d<targetrange
		If EntityVisible(this\entity%,this\target%)
			; target% visible from current bot position 
			cur_v=1
		EndIf
	EndIf

	PositionEntity worker\entity%,waypointID(this\waypointID%+this\direction%)\position\x,waypointID(this\waypointID%+this\direction%)\position\y,waypointID(this\waypointID%+this\direction%)\position\z

    nex_d#=EntityDistance(worker\entity%,this\target%)
	If nex_d< targetrange
		If EntityVisible(worker\entity%,this\target%)
			; target% visible from next bot waypoint
			nex_v=1
		EndIf
    EndIf

	PositionEntity worker\entity%,waypointID(this\waypointID%-this\direction%)\position\x,waypointID(this\waypointID%-this\direction%)\position\y,waypointID(this\waypointID%-this\direction%)\position\z

    prev_d#=EntityDistance(this\target%,worker\entity%)
	If prev_d< targetrange
		If EntityVisible(worker\entity%,this\target%)
			; target% visible from prev. bot waypoint
			prev_v=1
		EndIf
	EndIf

	this\state%=3

	If cur_v=1 And nex_v=1 And prev_v=1 ; visible from all 3 points
		If cur_d<nex_d And cur_d<prev_d
			; current pos optimal
			this\state%=0
     	EndIf
		If nex_d<cur_d And nex_d<prev_d
			; next waypoint better
			this\state%=1
		EndIf
		If prev_d<nex_d And prev_d<cur_d
			; prev waypoint better
			this\state%=2
		EndIf
	EndIf

	If cur_v=1 And nex_v=1 And prev_v=0 ; visible from cur and nex
		If cur_d<nex_d
			; current pos optimal
			this\state%=0
     	Else
      		this\state%=1
		EndIf
	EndIf

	If cur_v=1 And nex_v=0 And prev_v=1 ; visible from cur and prev
		If cur_d<prev_d
      		; current pos optimal
			this\state%=0
		Else
			this\state%=2
		EndIf
	EndIf

	If cur_v=0 And nex_v=1 And prev_v=1 ; visible from nex and prev
		If nex_d<prev_d
			; current pos optimal
			this\state%=1
		Else
			this\state%=2
		EndIf
	EndIf

	If cur_v=1 And nex_v=0 And prev_v=0 ; visible only from cur
		this\state%=0
	EndIf

	If cur_v=0 And nex_v=1 And prev_v=0 ; visible only from nex
		this\state%=1
    EndIf

	If cur_v=0 And nex_v=0 And prev_v=1 ; visible only from prev
		this\state%=2
	EndIf

	botSDReaction(this)

	;optimization notes: reduce the number of entitydistance calls, or faster alternative

End Function

Function botSDReaction(this.bot)
	Select this\state%
		Case 0; already in optimal position to attack
	    	EntityColor this\thinker%,255,0,0
		Case 1; next waypoint is better, go there
			EntityColor this\thinker%,255,255,0
			botSDMotor(this)
	    Case 2 ; prev waypoint is better, go there
			EntityColor this\thinker%,255,255,0
			If this\direction=1
				this\direction=-1
			Else
				this\direction=1
			EndIf
			botSDMotor(this)
		Case 3 ; not visible at all - walk waypoints
			EntityColor this\thinker%,0,255,255
			botSDMotor(this)				
	End Select
End Function


Function botSDMotor(this.bot)
	PositionEntity worker\entity%,waypointID(this\waypointID%+this\direction)\position\x#,waypointID(this\waypointID%+this\direction)\position\y#,waypointID(this\waypointID+this\direction)\position\z#
	; rotate
	this\old\y#=EntityYaw(this\entity%)
	PointEntity this\entity%, worker\entity%
	this\angle\y#=EntityYaw(this\entity%)
	MoveEntity this\entity%,0,0,this\speed#
	adist#=this\old\y#-this\angle\y#
	turn#=Abs(this\old\y#-this\angle\y#)
	If turn#>180 turn#=360.0-turn#
	turn=turn*this\turn#
	If (adist <-180) Or (adist >0 And adist <180)
		RotateEntity this\entity%,0,this\old\y#-turn#,0
	Else
		RotateEntity this\entity%,0,this\old\y#+turn#,0
	EndIf
	; move
	If player_visible  ; this mode will let us watch them walking along the waypoints  
		this\speed#=.5
	Else		
		this\speed#=.25
	EndIf	
	If EntityX(this\entity%)>=EntityX(worker\entity%)-this\speed# And EntityX(this\entity%)<=EntityX(worker\entity%)+this\speed#
		If EntityY(this\entity%)>=EntityY(worker\entity%)-this\speed# And EntityY(this\entity%)<=EntityY(worker\entity%)+this\speed#
			If EntityZ(this\entity%)>=EntityZ(worker\entity%)-this\speed# And EntityZ(this\entity%)<=EntityZ(worker\entity%)+this\speed#
				this\waypointID%=this\waypointID%+this\direction
			EndIf
		EndIf
	EndIf
	If this\waypointID%>=waypoints%
		this\direction=-1
	EndIf
	If this\waypointID%<=1
		this\direction=1
	EndIf
	;animate
	Select this\state%
		Case 1
			If AnimSeq(this\entity)<>stillfire Animate this\entity%,1,this\speed#*1.25,stillfire 
		Default 
			If AnimSeq(this\entity)<>RunFire Animate this\entity%,1,this\speed#*1.25,RunFire	
	End Select
End Function

Comments

None.

Code Archives Forum