import sys,os
sys.path.append("../CommonFunctions")
from levelWindow import *
from math import *
from actors import *
from utilityFunctions import *
from makeShapeFunctions import *
from interactables import *
#used mostly to grabbing stage specific images
LEVEL_PATH = os.path.dirname(os.path.realpath(__file__))+"/"
[docs]class Hazard(object):
"""
Base object for all hazards.
"""
def __init__(self):
"""
Constructs a Hazards object.
"""
#: Indicates whether or not this hazard is disabled.
self.run=False
[docs] def disable(self):
"""
Disables the hazard.
The effect of disabling the hazard is implementation dependend per
hazard.
"""
self.run=False
[docs] def isDisabled(self):
"""
Indicates whether this hazard is enabled or not.
Return: Returns True if this hazard is disabled, False otherwise.
"""
return not self.run
#Can probably combine FireBall and Thwomp eventually
[docs]class FireBall(Hazard,SpriteActor):
"""
A fireball that moves back and forth.
"""
def __init__(self, start, end, costume, collision, scale=1, speed=10,
run=True, debug=False, startLoc=None, bounce=True):
"""
Creates a FireBall object.
Args:
start: The starting position of the fireball.
end: The end position of the fireball.
costume: Integer indicating the initial costume of the fireball. The
customes come in this order: up, down, left, right.
collision: The collision function called when something hits the
fireball.
scale: The scale of the fireball.
speed: The speed of the fireball.
run: Whether or not the fireball should immediately active.
debug: Whether debug visuals should be enabled (shows the hitbox).
startLoc: Set if the starting position should be different from the
patroll starting point.
bounce: If True, the fireball will move back-and-forth. If False,
the fireball will respawn at the start position when it reaches
the end position.
"""
# scale, collision):lowerBound, upperBound, leftBound, rightBound,
# collision, costume=0):
files = [LEVEL_PATH+"fireballUp.png", LEVEL_PATH+"fireballDown.png",
LEVEL_PATH+"fireballLeft.png", LEVEL_PATH+"fireballRight.png"]
SpriteActor.__init__(self, start, files , makeColCircle(35, "dynamic"),
collision=collision, debug=debug,scale=scale)
self.description = "hazard: fire " + str(start)
self.start=start
self.end=end
self.speed=speed
self.run=run
self.direct=[self.end[0]-self.start[0],self.end[1]-self.start[1]] #direction of fireball
self.vel=norm(self.direct) #normalize the direction to obtain a reasonable baseline velocity
self.changeCostume(costume)
self.bounce=bounce
if startLoc != None:
self.moveTo(startLoc[0],startLoc[1])
[docs] def isDisabled(self):
"""
Indicates whether this hazard is enabled or not.
Return: Returns True if this hazard is disabled, False otherwise.
"""
return not self.run
[docs] def disable(self):
"""
Disables the hazard.
Prevents movement and hides the fireball.
"""
self.run=False
self.shape.body.LinearVelocity = Vector(0,0)
self.hide()
[docs] def step(self):
"""
Move the fireball one step.
Return: Always returns True.
"""
pastEnd=False
if self.run:
#see if fireball has moved beyond the end point
#based on the equation: self.shape.location=self.start + t*self.direction
#t ranges from 0 (at start) to 1 (at end)
#to find t, and whether the shape is beyond the end point
#t=(self.location-self.start)/self.direction
if self.direct[0]==0: #avoid dividing by 0
if ((self.shape.y-self.start[1])/self.direct[1]) >= 1:
pastEnd=True
elif self.direct[1]==0: #avoid divinding by 0
if ((self.shape.x-self.start[0])/self.direct[0]) >= 1:
pastEnd=True
else:
if ((self.shape.x-self.start[0])/self.direct[0]) >=1 and ((self.shape.y-self.start[1])/self.direct[1]) >=1:
pastEnd=True
if pastEnd:
if self.bounce:
#self.speed=-1*self.speed #changes direction of velocity
temp=self.start
self.start=self.end
self.end=temp
self.direct[0]*=-1
self.direct[1]*=-1
self.vel[0]*=-1
self.vel[1]*=-1
if self.currentCostumeIndex==0:
self.changeCostume(1)
elif self.currentCostumeIndex==1:
self.changeCostume(0)
elif self.currentCostumeIndex==2:
self.changeCostume(3)
elif self.currentCostumeIndex==3:
self.changeCostume(2)
else: #wrap around screen
self.moveTo(self.start[0],self.start[1])
else:
self.shape.body.LinearVelocity = Vector(self.speed*self.vel[0], self.speed*self.vel[1])
return True
#JH: I'm trying out a couple of modification to fireball which are not going to
#be backward compatible, hence a new class for now.
[docs]class FireBall2(Hazard,SpriteActor):
"""
Experimental fireball that can use multiple waypoints.
"""
def __init__(self, waypoints=[], costume=0, collision=None, scale=1,
speed=10, run=True, debug=False, startLoc=None, bounce=True):
"""
Creates a FireBall object.
Args:
waypoints: List of waypoints that fireball will follow.
costume: Integer indicating the initial costume of the fireball. The
customes come in this order: up, down, left, right.
collision: The collision function called when something hits the
fireball.
scale: The scale of the fireball.
speed: The speed of the fireball.
run: Whether or not the fireball should immediately active.
debug: Whether debug visuals should be enabled (shows the hitbox).
startLoc: Set if the starting position should be different from the
patroll starting point.
bounce: If True, the fireball will move back-and-forth. If False,
the fireball will respawn at the start position when it reaches
the end position.
"""
if collision is None:
collision = self.defaultCollide
if startLoc != None:
start = startLoc
else:
start=waypoints[0]
SpriteActor.__init__(self, start, LEVEL_PATH+"fireballUp.png" ,
makeColCircle(35, "dynamic"), collision=collision,
debug=debug,scale=scale)
self.description = "hazard: fire " + str(start)
self.currentWaypoint=0
self.waypoints=waypoints
self.speed=speed
self.direction=1
self.run=run
self.changeCostume(costume)
self.atEnd="bounce"
self.moveEvent = None
[docs] def defaultCollide(self, myfixture, otherfixture, contact):
"""
Default collision function that sets a game over if the robot touches
the fireball.
Args:
myfixture: Fixture of the fireball.
otherfixture: Fixture of whatever we collided with.
contact: The contact of the collision.
"""
if self.levelWindow is None: return True
robot = getRobot()
if robot is None: return True
if otherfixture.Body.UserData==robot.frame.body.UserData:
self.levelWindow.setGameOver(-1)
return True
[docs] def reverse(self):
"""
Causes the fireball to start moving in the opposite direction.
"""
self.direction*=-1
[docs] def warp(self):
"""
Warps the fireball back to the starting point.
"""
point = self.waypoints[self.currentWaypoint]
self.moveTo(point[0], point[1])
[docs] def pause(self):
"""
Temporarily stops the fireball from moving.
"""
if self.moveEvent is None: return
self.moveEvent.pause = True
[docs] def unpause(self):
"""
Causes the fireball to continue to move if it was paused.
"""
if self.moveEvent is None: return
self.moveEvent.pause = False
[docs] def stop(self):
"""
Stops the fireball by deleting its move event. Can only be continued by
registering a new move event.
"""
if self.moveEvent is None: return
self.moveEvent.remove = True
[docs] def start(self):
"""
Causes the fireball to start moving.
"""
if self.levelWindow is None: return
self.currentWaypoint += self.direction
if self.currentWaypoint >= len(self.waypoints) or self.currentWaypoint < 0:
if self.atEnd == "bounce":
self.reverse()
self.currentWaypoint += self.direction*2
elif self.atEnd == "circle":
self._wrapWaypoint()
elif self.atEnd == "warp":
self._wrapWaypoint()
self.warp()
elif self.atEnd == "stop":
return
else:
print("Warning: Unknown atEnd action")
point = self.waypoints[self.currentWaypoint]
dx=point[0]-self.getX()
dy=self.getY() - point[1]
if dx != 0 or dy != 0:
angle = getAngleVector(dx, dy)
self.rotateTo(angle)
#print("line 169 (hazards.py): current location:", self.getX(), self.getY(), "target location:", point[0], point[1])
self.moveEvent = MoveToEvent(self, point[0], point[1], self.speed, self.start)
self.levelWindow.addStepEvent(self.moveEvent)
def _wrapWaypoint(self):
"""
Wraps the waypoint counter if it gets outside of the bounds of the
waypoint list.
"""
if self.currentWaypoint < 0:
self.currentWaypoint = len(self.waypoints)-1
elif self.currentWaypoint >= len(self.waypoints):
self.currentWaypoint = 0
[docs] def draw(self, levelWindow):
"""
Draws the fireball to the level.
Args:
levelWindow: The :class:`.LevelWindow` to draw to.
"""
SpriteActor.draw(self, levelWindow)
if self.run: self.start()
[docs]class Missile(Hazard, SpriteActor):
"""
A missle that follows a set of waypoints, and explodes on contact.
"""
def __init__(self, waypoints=[], speed=10, run=True, startLoc=None,
atEnd="bounce", **kwargs):
"""
Creates a Missile object.
Args:
waypoints: List of waypoints that missile will follow.
speed: The speed of the missile.
run: Whether or not the missile should immediately active.
startLoc: Set if the starting position should be different from the
patroll starting point.
atEnd: If set to 'bounce' will cause the missle to move
back-and-forth, if set to 'warp' will cause the missle to warp
back to the start, if set to 'circle' will move to the start
when the end is reached, if set to 'stop' the missle will be
disabled after reaching the end, and if set to 'custom' a
callback function will be called to determine what happens.
kwargs: All other keyword arguments will be passed to the parent
:class:`.SpriteActor`.
"""
if "collision" not in kwargs:
kwargs["collision"] = self.defaultCollide
if startLoc != None:
start = startLoc
else:
start=waypoints[0]
frames = [LEVEL_PATH+"RocketSimple.png",
LEVEL_PATH+"RocketSimpleFire.png",
LEVEL_PATH+"RocketSimpleMoreFire.png"]
hitbox = makeColCircle(35, "dynamic")
SpriteActor.__init__(self, start, frames, hitbox, **kwargs)
self.description = "hazard: fire " + str(start)
self.currentWaypoint=0
self.waypoints=waypoints
self.speed=speed
self.direction=1
self.run=run
self.atEnd=atEnd
self.moveEvent = None
self.customTrackingEvent = None
[docs] def defaultCollide(self, myfixture, otherfixture, contact):
"""
Default collision function that sets a game over if the robot touches
the missle.
Args:
myfixture: Fixture of the missle.
otherfixture: Fixture of whatever we collided with.
contact: The contact of the collision.
"""
if self.levelWindow is None: return True
robot = getRobot()
if robot is None: return True
if otherfixture.Body.UserData==robot.frame.body.UserData:
self.levelWindow.setGameOver(-1)
return True
[docs] def reverse(self):
"""
Causes the missile to start moving in the opposite direction.
"""
self.direction*=-1
[docs] def warp(self):
"""
Warps the missile back to the starting point.
"""
point = self.waypoints[self.currentWaypoint]
self.moveTo(point[0], point[1])
[docs] def pause(self):
"""
Temporarily stops the fireball from moving.
"""
if self.moveEvent is None: return
self.moveEvent.pause = True
[docs] def unpause(self):
"""
Causes the missile to continue to move if it was paused.
"""
if self.moveEvent is None: return
self.moveEvent.pause = False
[docs] def stop(self):
"""
Stops the missile by deleting its move event. Can only be continued by
registering a new move event.
"""
if self.moveEvent is None: return
self.moveEvent.remove = True
[docs] def start(self):
"""
Causes the missile to start moving.
"""
if self.levelWindow is None: return
self.currentWaypoint += self.direction
if self.currentWaypoint >= len(self.waypoints) or self.currentWaypoint < 0:
if self.atEnd == "bounce":
self.reverse()
self.currentWaypoint += self.direction*2
elif self.atEnd == "circle":
self._wrapWaypoint()
elif self.atEnd == "warp":
self._wrapWaypoint()
self.warp()
elif self.atEnd == "custom":
self.levelWindow.addStepEvent(self.customTrackingEvent)
return
elif self.atEnd == "stop":
return
else:
print("Warning: Unknown atEnd action")
point = self.waypoints[self.currentWaypoint]
if len(point) == 3:
point[2](self)
dx=point[0]-self.getX()
dy=self.getY() - point[1]
if dx != 0 or dy != 0:
angle = getAngleVector(dx, dy)
self.rotateTo(angle)
#print("line 169 (hazards.py): current location:", self.getX(), self.getY(), "target location:", point[0], point[1])
self.moveEvent = MoveToEvent(self, point[0], point[1], self.speed, self.start)
self.levelWindow.addStepEvent(self.moveEvent)
[docs] def reset(self):
"""
Moves the missile back to the starting position.
"""
self.stop()
self.currentWaypoint=0
self.warp()
self.start()
def _wrapWaypoint(self):
"""
Wraps the waypoint counter if it gets outside of the bounds of the
waypoint list.
"""
if self.currentWaypoint < 0:
self.currentWaypoint = len(self.waypoints)-1
elif self.currentWaypoint >= len(self.waypoints):
self.currentWaypoint = 0
[docs] def draw(self, levelWindow):
"""
Draws the missile to the level.
Args:
levelWindow: The :class:`.LevelWindow` to draw to.
"""
SpriteActor.draw(self, levelWindow)
if self.run: self.start()
[docs]class Thwomp(Hazard,SpriteActor):
"""
A Mario Thwomp that moves up and down.
"""
def __init__(self, start, end, costume, collision, scale=1, upSpeed=10,
downSpeed=20, run=True, debug=False, startLoc=None,
bounce=True, onStep=None, hitbox=None):
"""
Creates a Thwomp object.
Args:
start: The starting position of the thwomp.
end: The end position of the thwomp.
costume: Integer indicating the initial costume of the thwomp. The
customes come in this order: up, down.
collision: The collision function called when something hits the
thwomp.
scale: The scale of the thwomp.
upSpeed: The upward speed of the thwomp.
downSpeed: The downward speed of the thwomp.
run: Whether or not the fireball should immediately active.
debug: Whether debug visuals should be enabled (shows the hitbox).
startLoc: Set if the starting position should be different from the
patroll starting point.
bounce: If True, the fireball will move back-and-forth. If False,
the fireball will respawn at the start position when it reaches
the end position.
onStep: Function called at every movement step of the thwomp.
hitbox: A custom hitbox for the thwomp, useful if it scalled to a
different size.
"""
# scale, collision):lowerBound, upperBound, leftBound,
# rightBound, collision, costume=0):
files = [LEVEL_PATH+"thwompUp.png", LEVEL_PATH+"thwompDown.png"]
if hitbox is None:
hitbox=makeColRec(90, 110, "dynamic")
SpriteActor.__init__(self, start, files , hitbox, collision=collision, debug=debug,scale=scale)
self.description = "hazard: thwomp " + str(start)
self.start=start
self.end=end
self.upSpeed=upSpeed
self.downSpeed=downSpeed
self.run=run
# direction of thwomp
self.direct=[self.end[0]-self.start[0],self.end[1]-self.start[1]]
# normalize the direction to obtain a reasonable baseline velocity
self.vel=norm(self.direct)
self.changeCostume(costume)
self.bounce=bounce
self.onStep = onStep
self.pastEnd=False
if startLoc != None:
self.moveTo(startLoc[0],startLoc[1])
[docs] def isDisabled(self):
"""
Indicates whether this hazard is enabled or not.
Return: Returns True if this hazard is disabled, False otherwise.
"""
return not self.run
[docs] def disable(self):
"""
Disables the hazard.
Prevents movement and hides the thwomp.
"""
self.run=False
self.shape.body.LinearVelocity = Vector(0,0)
self.hide()
[docs] def step(self):
"""
Move the thwomp one step.
Return: Always returns True.
"""
self.pastEnd=False
if self.run:
#see if fireball has moved beyond the end point
#based on the equation: self.shape.location=self.start + t*self.direction
#t ranges from 0 (at start) to 1 (at end)
#to find t, and whether the shape is beyond the end point
#t=(self.location-self.start)/self.direction
if self.direct[0]==0: #avoid dividing by 0
if ((self.shape.y-self.start[1])/self.direct[1]) >= 1:
self.pastEnd=True
elif self.direct[1]==0: #avoid divinding by 0
if ((self.shape.x-self.start[0])/self.direct[0]) >= 1:
self.pastEnd=True
else:
if ((self.shape.x-self.start[0])/self.direct[0]) >=1 and ((self.shape.y-self.start[1])/self.direct[1]) >=1:
self.pastEnd=True
if self.pastEnd:
if self.bounce:
#self.speed=-1*self.speed #changes direction of velocity
temp=self.start
self.start=self.end
self.end=temp
self.direct[0]*=-1
self.direct[1]*=-1
self.vel[0]*=-1
self.vel[1]*=-1
if self.currentCostumeIndex==0:
self.changeCostume(1)
elif self.currentCostumeIndex==1:
self.changeCostume(0)
elif self.currentCostumeIndex==2:
self.changeCostume(3)
elif self.currentCostumeIndex==3:
self.changeCostume(2)
else: #wrap around screen
self.moveTo(self.start[0],self.start[1])
else:
if self.currentCostumeIndex==0:
self.shape.body.LinearVelocity = Vector(self.upSpeed*self.vel[0], self.upSpeed*self.vel[1])
else:
self.shape.body.LinearVelocity = Vector(self.downSpeed*self.vel[0], self.downSpeed*self.vel[1])
else:
self.shape.body.LinearVelocity = Vector(0, 0)
if self.onStep: self.onStep(self)
return True
[docs]class Grinder(Hazard,SpriteActor):
"""
A spinning blade that may follow a circular path.
"""
def __init__(self, start, collision, scale=1, rotationSpeed=1, debug=False,
orbit=False, orbitCenter=(0,0), orbitRadius=100,
orbitStep=0.25, currentAngle=0,run=True):
"""
Creates a Grinder object.
Args:
start: The starting position of the grinder.
collision: Function called when something hits the grinder.
scale: The scale of the grinder.
rotationSpeed: The rotational speed of the grinder.
debug: Whether debug visuals should be enabled (shows the hitbox).
orbit: Whether or not the grinder should move in a circle.
orbitCenter: The center around which the grinder moves.
orbitRadius: The distance from the center around which the grinder
moves.
orbitStep: The speed at which the grinder moves.
currentAngle: The angle at which the grinder starts its movement.
run: Whether or not the grinder should be immediately active.
"""
files = LEVEL_PATH+"grinder.png"
# JH: Why are there two function calles here that do the same?
if rotationSpeed<0:
SpriteActor.__init__(self, start, files,
makeColCircle(scale*210, "dynamic"),
collision=collision, debug=debug,scale=scale)
else:
SpriteActor.__init__(self, start, files,
makeColCircle(scale*210, "dynamic"),
collision=collision, debug=debug,scale=scale)
self.description = "hazard: grinder " + str(start)
self.start=start
self.rotationSpeed=rotationSpeed
self.run=run
self.orbit=orbit
self.orbitCenter=orbitCenter
self.orbitRadius=orbitRadius
self.orbitStep=orbitStep
self.currentAngle=currentAngle
[docs] def isDisabled(self):
"""
Indicates whether this hazard is enabled or not.
Return: Returns True if this hazard is disabled, False otherwise.
"""
return not self.run
[docs] def disable(self):
"""
Disables the hazard.
Prevents movement and hides the grinder.
"""
self.run=False
self.shape.body.LinearVelocity = Vector(0,0)
self.hide()
[docs] def step(self):
"""
Move the grinder one step.
Return: Always returns True.
"""
if self.run:
self.shape.body.AngularVelocity=self.rotationSpeed
if self.orbit:
self.shape.moveTo(cos(self.currentAngle)*self.orbitRadius+self.orbitCenter[0],sin(self.currentAngle)*self.orbitRadius+self.orbitCenter[1])
self.currentAngle+=self.orbitStep#2*pi*self.orbitRadius*(self.orbitStep/(2*pi))
self.currentAngle %= (2*pi)
return True
[docs]class Boulder(SpriteActor):
"""
A boulder.
Class looks disfunctional.
"""
def __init__(self, start, collision, scale=1, speed=1, debug=False,
obstructs=True):
"""
Creates a Boulder object.
Args:
start: The starting position of the boulder.
collision: Function called when something hits the boulder.
scale: The scale of the boulder.
speed: The speed of the boulder.
debug: Whether debug visuals should be enabled (shows the hitbox).
obstructs: Whether or not the boulder should obstruct movement.
"""
# scale, collision):lowerBound, upperBound, leftBound, rightBound,
# collision, costume=0):
files = LEVEL_PATH+"boulder.png"
SpriteActor.__init__(self, start, files,
makeColCircle(scale*55, "dynamic"),
collision=collision, debug=debug, scale=scale,
obstructs=obstructs)
self.description = "boulder " + str(start)
self.start=start
self.speed=speed
[docs] def step(self):
"""
Should perform one step of the boulder, but the self.orbit attribute is
not an actual attribute of this boulder, so this function would usually
crash.
"""
if not self.orbit:
self.shape.body.AngularVelocity=self.speed
#def draw(self,levelWindow):
# PictureActor.draw(levelWindow)
#################NPC CLASSES###################
#Might be moved to another file npc.py#########
## ############################################
#self.statusBox.closeOnTime=True
#self.statusBox.timer=10
#TODO: More robust follower AI
#The simpleFollowerAI can be fooled b/c it will follow
#the robot's previous location and not necessary make a
#direct line to the robot. Here's one way to make a more
#robustFollowerAI
#Step1: Break the world into grids. Grid size would depend
#on the physical dimensions of the follower AI on screen
#Step2: Create a vertex map based on the gridded world but
#exclude grids that contain obstacles b/c rationally you
#can't move onto those obstacles
#Step3a: Do a simple AStar or route finding to find the grids/vertices
#that lead you to the robot. Periodically recheck the route b/c
#the robot will be moving.
#Step3b: Instead of doing a route finding algorithm and periodically
#rechecking b/c the robot is moving just do sometype
#of dynamic route finding.
#Very simple AI that monitors the past locations of the robot
#and simple follows those past locations to follow the robot.
#TODO: Make a separate called simpleFollowerAI and then
#any character like the Kuka could inherit the step and follow
#procedures
[docs]class Kuka(SpriteActor):
"""
A robot enemy that monitors the past locations of the robot and simple
follows those past locations to follow the robot.
"""
def __init__(self, start, hitMessage=None, hitPause=20,hitAction=None,
speed=10,obstructs=False, visible=True,bodyType="dynamic",
scale=1, debug=False):
"""
Constructs a Kuka object.
Args:
start: The starting position of the Kuka.
hitMessage: A message shown when the Kuka is hit.
hitPause: The amount of time the Kuka waits after it is hit.
hitAction: Function executed when the Kuka is hit.
speed: The speed of the Kuka.
obstructs: Whether or not the Kuka obstructs pathing.
visible: Whether or not the Kuka is visible.
bodyType: Either 'static' or 'dynamic'.
scale: The scale of the Kuka Sprite.
debug: Whether debug visuals should be enabled (shows the hitbox).
"""
# scale, collision):lowerBound, upperBound, leftBound, rightBound,
#collision, costume=0):
files = IMAGE_PATH+"kuka.png"
SpriteActor.__init__(self, start, files , makeColRec(50, 70, "dynamic"),
visible=visible,collision=self.hitCollide,
separation=self.hitSeparation, debug=debug,
scale=scale,obstructs=obstructs, bodyType=bodyType)
self.run=False
self.level=getLevelObject()
self.robot=self.level.robots[0]
self.wayPoints=[]
self.minPointDist=10
self.atEnd=True
self.start=None
self.stop=None
self.speed=speed
self.distance=None
self.direction=None
self.endIndex=0
self.hitPauseCounter=0
self.hitPause=hitPause
self.hitMessage=hitMessage
self.hitAction=hitAction
self.damaged=False
# flag used to make sure Kuka only uses the portal once when it steps on
# it
self.onPortal=False
[docs] def stopMotion(self):
"""
Stops the movement of the Kuka.
"""
self.shape.body.LinearVelocity = Vector(0,0)
[docs] def hitFlash(self):
"""
Function causes the Kuka to blink.
"""
if self.visible():
self.hide()
else:
self.show()
[docs] def findClosestWayPoint(self,p):
"""
Finds the closest waypoint to the provided position.
Args:
p: The point from which we to find the closest waypoint.
Return: The waypoint closest to the point p.
"""
#print("Closest to ",p)
minDist=9999
minIndex=0
for i in range(len(self.wayPoints)):
d=dist(self.wayPoints[i],p)
if d<minDist:
minDist=d
minIndex=i
#print("minDistance is",minDist)
#print("closest way point is",self.wayPoints[minIndex])
return minIndex
[docs] def hitSeparation(self,myfixture,otherfixture):
"""
Records when the Kuka leaves a portal.
Args:
myfixture: The fixture of the Kuka.
otherfixture: The fixture of the other object.
"""
try:
if isinstance(otherfixture.Body.UserData,Portal):
self.onPortal=False
except:
print("error with kuka separation")
[docs] def hitCollide(self, myfixture, otherfixture, contact):
"""
The collision function of the Kuka.
Args:
myfixture: The fixture of the Kuka.
otherfixture: The fixture of the other object.
contact: The contact of the collision.
"""
r=getRobot()
if r:
if otherfixture.Body.UserData==r.frame.body.UserData:
self.level.setGameOver(-1)
else:
try:
if isinstance(otherfixture.Body.UserData,Hazard):
if not otherfixture.Body.UserData.isDisabled():
try:
otherfixture.Body.UserData.disable()
otherfixture.Body.UserData.moveTo(-100,-100)
except:
print("Hit hazard doesn't have disable function.")
hitFlashEvent = RepeatedActionEvent(0.2,4,self.hitFlash)
self.level.addStepEvent(hitFlashEvent)
self.hitPauseCounter=self.hitPause
self.damaged=True
if self.hitMessage:
self.level.printToSpeechBox(self.hitMessage, self.getAvatar())
if self.hitAction:
self.hitAction()
#I'm thinking of giving all interactables a npcAction function where
#if an npc hits them it can pass itselfs (self) or simply invoke the
#action
elif isinstance(otherfixture.Body.UserData,Portal):
if not self.onPortal:
portal=otherfixture.Body.UserData
self.moveTo(portal.end[0],portal.end[1])
#self.wayPoints=[]
self.endIndex=self.findClosestWayPoint(portal.end)
self.atEnd=True
self.stopMotion()
self.onPortal=True
else:
pass
except:
print("Error with Kuka's hit collide.")
return True
[docs] def recordWayPoints(self):
"""
Records the current position of the robot as a waypoint.
"""
newPoint=[self.robot.frame.x,self.robot.frame.y]
#print(newPoint)
if len(self.wayPoints)==0:
self.wayPoints.append(newPoint)
else:
#print(dist(self.wayPoints[-1],newPoint))
if dist(self.wayPoints[-1],newPoint)>5:
self.wayPoints.append(newPoint)
[docs] def step(self):
"""
Move the Kuka one step.
Return: Returns False if the game is over, True otherwise.
"""
if self.run:
#start waypoint recording
self.recordWayPoints()
#if gameOver then return False and stop
if self.level.gameOver!=0:
self.shape.body.LinearVelocity = Vector(0,0)
return False
#print("wayPOints",self.wayPoints)
#potentially pick new start and stop points
if self.atEnd:
#print("wayPoints",self.wayPoints)
if len(self.wayPoints)<=0:
return True
else:
if self.endIndex<len(self.wayPoints):
self.start=[self.getX(),self.getY()]
self.end=self.wayPoints[self.endIndex]
self.endIndex+=1
#self.start=self.wayPoints[self.endIndex-1]
self.atEnd=False
self.distance=[self.end[0]-self.start[0],self.end[1]-self.start[1]] #distance to end of x and y
#print("Distance",self.distance)
if fabs(self.distance[0])<0.01 and fabs(self.distance[1])<0.01: #avoid dividing by 0
self.atEnd=True
self.direction=[0,0]
else:
self.direction=norm(self.distance) #normalize the distance to get a vector of the direction to go
#print("Direction",self.direction)
#move from start to stop
else:
loc=[self.getX(),self.getY()]
#loc=[self.getX(),self.getY()]
#print("Going from ",self.start," to ",self.end)
if fabs(self.distance[0])<0.01: #avoid dividing by 0
if (self.getY()-self.start[1])/self.distance[1] >= 1:
self.atEnd=True
elif fabs(self.distance[1])<0.01: #avoid divinding by 0
if (self.getX()-self.start[0])/self.distance[0] >= 1:
self.atEnd=True
else:
#print("compareY",(self.getY()-self.start[1])/self.distance[1])
#print("compareX",(self.getX()-self.start[0])/self.distance[0])
if (self.getX()-self.start[0])/self.distance[0] >=1 and (self.getY()-self.start[1])/self.distance[1] >=1:
self.atEnd=True
if self.atEnd:
#print("Actual position is ",loc)
#print("Dist from start to actual loc ",dist(self.start,loc))
self.shape.body.LinearVelocity = Vector(0,0)
else:
self.shape.body.LinearVelocity = Vector(self.speed*self.direction[0], self.speed*self.direction[1])
if self.hitPauseCounter>0:
self.hitPauseCounter-=1
self.shape.body.LinearVelocity = Vector(0,0)
if self.hitPauseCounter==0:
self.level.speechBox.hide()
return True
[docs]class Boo(SpriteActor):
"""
The Mario Ghost Boo.
Boo is a hazard that moves straight towards the player, ignoring intervening
terrain.
Class is currently defunct, because speech is not a valid argument for a
SpriteActor.
"""
def __init__(self, start, costume, collision, scale=1, run=True,
debug=False, speech=True):
"""
Constructs a Boo object.
Args:
start: The starting position of Boo.
costume: Interger indicating the starting position of Boo. The
costumes come in the following order: left, right.
collision: The collision function called when something hits Boo.
scale: The scale of Boo's sprite.
run: Whether or not Boo should be immediately active.
debug: Whether debug visuals should be enabled (shows the hitbox).
speech: No longer a valid argument.
"""
# scale, collision):lowerBound, upperBound, leftBound, rightBound,
# collision, costume=0):
files = [LEVEL_PATH+"booLeft.png", LEVEL_PATH+"booRight.png"]
# FIXME: JH - speech is not a valid argument for SpriteActor, so those
# code will fail if executed.
SpriteActor.__init__(self, start, files ,
makeColCircle(scale*100,"dynamic"),
collision=collision, debug=debug, scale=scale,
speech=speech)
self.changeCostume(costume)
self.run=run
[docs] def step(self):
"""
Moves Boo one step.
Return: Always returns True.
"""
#speed of ghost
s=15
if self.run:
rX=self.levelWindow.robot.frame.getX()
rY=self.levelWindow.robot.frame.getY()
bX=self.getX()
bY=self.getY()
if rX-bX==0:
self.move(copysign(s,rX-bX),copysign(s,rY-bY))
else:
#slope of line between robot and ghost
m=(rY-bY)/(rX-bX)
#distance between robot and ghost
D=sqrt(pow(rX-bX,2) + pow(rY-bY,2))
#distance from robot that ghost will be placed at
d=D-s
#solve the equation
#d^2=y^2 + x^2 where y and x are the distance to the
#point the ghost will be placed at
#ans should produce two values which will be
#the x (run) value
ans=quadFormula(pow(m,2)+1,0,-pow(d,2))
if ans[0]==2:
nX1=rX+ans[1][0]
nX2=rX+ans[1][1]
nY1=rY+ans[1][0]*m
nY2=rY+ans[1][1]*m
#there are two possible solutions to the equation
#we want the one closer to the ghost
pD1=sqrt(pow(bX-nX1,2)+pow(bY-nY1,2))
pD2=sqrt(pow(bX-nX2,2)+pow(bY-nY2,2))
if pD1<pD2:
self.moveTo(nX1,nY1)
else:
self.moveTo(nX2,nY2)
if self.getX()<self.levelWindow.robot.frame.getX() and self.currentCostumeIndex==0:
self.changeCostume(1)
elif self.getX()>=self.levelWindow.robot.frame.getX() and self.currentCostumeIndex==1:
self.changeCostume(0)
return True
'''
def nextWayPoint(self):
minDist=9999
minIndex=None
kLoc=[self.getX(),self.getY()]
rLoc=[self.robot.frame.x,self.robot.frame.y]
for i in range(len(self.wayPoints)):
d=dist(self.wayPoints[i],kLoc)+dist(self.wayPoints[i],rLoc)
if d<minDist and i!=self.currentGoal:
minDist=d
minIndex=i
return minIndex
def step3(self):
if self.run:
#if gameOver then return False and stop
if self.level.gameOver!=0:
self.shape.body.LinearVelocity = Vector(0,0)
return False
#start waypoint recording
self.recordWayPoints()
#find closest waypoint
self.currentGoal=self.nextWayPoint()
self.end=self.wayPoints[self.currentGoal]
self.start=[self.getX(),self.getY()]
#find distance and then normalize to a direction
self.distance=[self.end[0]-self.start[0],self.end[1]-self.start[1]] #distance to end of x and y
self.direction=norm(self.distance) #normalize the distance to get a vector of the direction to go
#if self.atEnd:
# self.shape.body.LinearVelocity = Vector(0,0)
#else:
# self.shape.body.LinearVelocity = Vector(self.speed*self.direction[0], self.speed*self.direction[1])
self.shape.body.LinearVelocity = Vector(self.speed*self.direction[0], self.speed*self.direction[1])
return True
else:
return True
def closestWayPoint(self):
minDist=9999
minIndex=None
loc=[self.getX(),self.getY()]
for i in range(len(self.wayPoints)):
d=dist(self.wayPoints[i],loc)
if d<minDist:
minDist=d
minIndex=i
print("minIndex ",minIndex)
return minIndex
'''