def checkResyncAcknowledgement(self, msg): ''' Checks whether the player position etc. matches the position encoded in the ResyncPlayerMsg or ResyncAcknowledgedMsg. ''' return (isNear(self.pos[0], msg.xPos) and isNear(self.pos[1], msg.yPos) and isNear(self.yVel, msg.yVel) and isNear(self.angleFacing, msg.angle) and isNear(self.ghostThrust, msg.ghostThrust) and msg.health == max(self.health - self.zombieHits, 0))
def isRouteGoodEnough(self, route): if route.finishPoint is None: # Route isn't yet sure where it will end return True # Don't recalculate the route if we're still far from the target target = self.getTargetPos() error = distance(route.finishPoint, target) total = distance(self.bot.player.pos, target) if isNear(total, 0): return isNear(error, 0) if error / total >= 0.2: return False return True
def getCollision(self, unit, vector, *, ignoreLedges=False): ''' Returns a Colision object that results from moving the given unit by the given vector. ''' if isNear(vector[0], 0) and isNear(vector[1], 0): return None collisions = [] for polygon in self.getCandidatePolygons(unit, vector): if ignoreLedges and polygon.isLedge(): continue collision = self.checkCollision(unit, vector, polygon) if collision: collisions.append(collision) if not collisions: return None return min(collisions, key=lambda c: c.travelDistance)
def getMaxMotionToAxis(self, start, vector, x1, y1, x2, y2): ''' :param start: the start position of a unit of this shape :param vector: the (deltaX, deltaY) that the unit is trying to move :param x1, y1: defines the first point of the axis to test :param x2, y2: defines the second point of the axis to test :return: (dist, contactPoint) where: dist: is the maximum distance the unit could travel in this direction and still lie entirely outside the axis defined by the given points. contactPoint: is the point of collision with the axis. If the unit is already inside the axis, returns (-0.05, None), so that collisions touching the surface are distinguishable from those where the unit is already inside the axis. If even an infinite motion would leave the unit outside the axis, returns (None, None). Note that the parameter vector is only used to to determine the direction of motion, but its magnitude is ignored, so this method may return a distance greater than the length of vector. ''' # Find the point on the ellipse which would collide nx, ny = (x2 - x1), (y2 - y1) x0, y0 = self.getSurfacePointFromTangent(start, (-nx, -ny)) crossProduct1 = nx * (y1 - y0) - ny * (x1 - x0) ellipseTouchesAxis = isNear(crossProduct1, 0) if crossProduct1 < 0 and not ellipseTouchesAxis: # Point on ellipse is already inside the axis return (-0.05, None) mx, my = vector divisor = my * nx - mx * ny if divisor <= 0 or isNear(divisor, 0): # Moving away from or parallel to this axis return (None, None) x = ((y1 - y0) * mx * nx + x0 * my * nx - x1 * mx * ny) / divisor y = -((x1 - x0) * my * ny + y0 * mx * ny - y1 * my * nx) / divisor dist = ((x - x0) ** 2 + (y - y0) ** 2) ** 0.5 return dist, (x, y)
def update_from_player(self, player, show_phaseshift=True): # Consider horizontal movement of player. self.gun_angle = player.angleFacing self.bomber_time = ( player.bomber.timeRemaining if player.bomber else None) self.emote = player.emote self.grabbed_surface_angle = player.grabbedSurfaceAngle self.grappling_hook_attached = player.grapplingHook.isAttached() if player.dead: self.action = PlayerAction.GHOST elif player.grabbedSurfaceAngle is not None: self.action = PlayerAction.GRABBING elif player.getGroundCollision(): if isNear(player.xVel, 0): self.action = PlayerAction.STANDING else: x_motion = player.getXKeyMotion() if x_motion < 0: walking = player.isFacingRight() elif x_motion > 0: walking = not player.isFacingRight() else: walking = False if walking: self.action = PlayerAction.WALKING else: self.action = PlayerAction.RUNNING elif player.yVel > 0: self.action = PlayerAction.FALLING else: self.action = PlayerAction.JUMPING self.has_shield = player.hasVisibleShield() self.has_shoxwave = bool(player.shoxwave) self.has_machine_gun = bool(player.machineGunner) self.has_ricochet = bool(player.hasRicochet) self.has_ninja = bool(player.ninja) self.has_disruption = bool(player.disruptive) self.has_elephant = player.hasElephant() self.flickering = ( (player.phaseshift and show_phaseshift) or player.isInvulnerable()) self.translucent = player.invisible if player.bot: self.head = HEAD_BOT else: self.head = player.head if player.team is None: self.team_colour = (255, 255, 255) else: self.team_colour = player.team.colour self.resyncing = player.resyncing
def checkLedgeCollision(self, start, vector, polygon): ''' Checks whether the a unit of this shape travelling along the given vector would collide with the given one-way open polygon. :param start: the start position of the unit :param vector: the (deltaX, deltaY) that the unit is moving :param polygon: the open polygon to test against :return: a Collision object, or None for no collision ''' vectorDist = (vector[0] ** 2 + vector[1] ** 2) ** 0.5 for x1, y1, x2, y2 in polygon.getEdges(): dist, contactPoint = self.getMaxMotionToAxis( start, vector, x1, y1, x2, y2) if dist is None or dist >= vectorDist or dist < 0: # Does not collide with this infinite axis continue cx, cy = contactPoint if ( x1 <= cx <= x2 or x2 <= cx <= x1 or isNear(cx, x1) or isNear(cx, x2)): if ( y1 <= cy <= y2 or y2 <= cy <= y2 or isNear(cy, y1) or isNear(cy, y2)): if isNear(dist, 0): actualVector = (0.0, 0.0) else: f = dist / vectorDist actualVector = (vector[0] * f, vector[1] * f) surfaceAngle = atan2(y2 - y1, x2 - x1) return Collision( start, actualVector, contactPoint, surfaceAngle, ledge=True) points = list(polygon.getPoints()) dist, vertex, surfaceAngle = self.getVertexCollision( start, vector, points, closed=False) if dist is None or dist >= vectorDist or dist <= 0: # Does not collide with any of the vertices return None if isNear(dist, 0): actualVector = (0.0, 0.0) else: f = dist / vectorDist actualVector = (vector[0] * f, vector[1] * f) return Collision(start, actualVector, vertex, surfaceAngle, ledge=True)
def checkConcavePolygonCollision(self, start, vector, polygon): ''' Checks whether the a unit of this shape travelling along the given vector would collide with the given fixed polygon. :param start: the start position of the unit :param vector: the (deltaX, deltaY) that the unit is moving :param polygon: the polygon to test against :return: a Collision object, or None for no collision ''' # Check if entire polygon is outside of the motion path of this unit points = list(polygon.getPoints()) for axis in self.getMotionBoundaries(start, vector): for point in points: if self.pointLiesInside(point, axis): break else: # All points lie outside this boundary, so no collision occurs return None # Check an infinite line along each polygon edge to see how far the # unit could move before hitting that axis. vectorDist = (vector[0] ** 2 + vector[1] ** 2) ** 0.5 edgeDist, edgeContact, edgeAngle = self.getEdgeCollision( start, vector, polygon) if edgeDist is None: # Entire motion path is separated by an edge axis, # so no collision occurs. return None if edgeDist < 0: # Start position lies partially or fully inside the polygon. Try # starting with a magical nudge. r = min(0.5, 0.1 / vectorDist) nudgeX = vector[0] * r nudgeY = vector[1] * r start2 = (start[0] + nudgeX, start[1] + nudgeY) vector2 = (vector[0] - nudgeX, vector[1] - nudgeY) edgeDist2, edgeContact2, edgeAngle2 = self.getEdgeCollision( start2, vector2, polygon) if edgeDist2 is None: # Entire motion path is separated by an edge axis, # so no collision occurs. return None if edgeDist2 >= 0: # No longer inside the polygon, so go with this calculation edgeDist = edgeDist2 + r * vectorDist edgeContact = edgeContact2 edgeAngle = edgeAngle2 else: # Still inside the polygon. Try nudging the other way. start2 = (start[0] - nudgeX, start[1] - nudgeY) edgeDist2, edgeContact2, edgeAngle2 = self.getEdgeCollision( start2, vector2, polygon) if edgeDist2 is None: return None if edgeDist2 >= 0: edgeDist = edgeDist2 - r * vectorDist edgeContact = edgeContact2 edgeAngle = edgeAngle2 vertexDist, vertex, vertexAngle = self.getVertexCollision( start, vector, points) if vertexDist is None: # Tangent to ellipse does not separate shapes, # so edge collision occurs before vertex collision. finalDist = edgeDist if edgeContact is None: contactPoint = start else: contactPoint = edgeContact if edgeAngle is None: # Already inside polygon. Use normal to direction of travel. surfaceAngle = atan2(-vector[0], vector[1]) else: surfaceAngle = edgeAngle elif vertexDist >= vectorDist: # Vertex collision would occur past end of intended motion path return None elif vertexDist < 0: # Unit has already passed the polygon return None else: # Vertex collision occurs before edge collision finalDist = vertexDist contactPoint = vertex surfaceAngle = vertexAngle if isNear(finalDist, 0): actualVector = (0.0, 0.0) else: f = finalDist / vectorDist actualVector = (vector[0] * f, vector[1] * f) return Collision(start, actualVector, contactPoint, surfaceAngle)
def getVertexCollision(self, start, vector, points, closed=True): ''' Calculates the greatest distance this shaped unit would move along the given path before colliding with a vertex of the given polygon. :param start: the starting point of the unit :param vector: the direction of motion :param points: the points of the polygon :param closed: True for a closed polygon, False for open :return: (dist, vertex, tangentAngle), where dist: the distance moved, or None if none of the vertices have a separating axis that aligns with the tangent of the ellipse (that is, none of the vertices can be hit without first hitting an edge). vertex: the point of collision tangentAngle: the angle of the tangent at the point of collision Note that dist may be negative, in cases where the ellipse has passed the polygon already. ''' sx, sy = start # Transform into a coordinate system where the ellipse is a circle magnitude = ( (vector[0] / self.rx) ** 2 + (vector[1] / self.ry) ** 2) ** 0.5 mx = vector[0] / magnitude / self.rx my = vector[1] / magnitude / self.ry if closed: iterator = self.iterClosedPolygonPoints(points) else: iterator = self.iterOpenPolygonPoints(points) for lastPoint, point, nextPoint in iterator: x1 = (point[0] - sx) / self.rx y1 = (point[1] - sy) / self.ry # Project this vertex onto the circle radicand = 1 - (mx * y1 - my * x1) ** 2 if radicand > 0 and not isNear(radicand, 0): # Entire circle will not miss this vertex k = mx * x1 + my * y1 - radicand ** 0.5 if isNear(k, 0): # Make sure that it's definitely not negative k = 0.0 elif k < 0: # Unit is either inside or past this polygon k2 = mx * x1 + my * y1 + radicand ** 0.5 if isNear(k2, 0): # Make sure that it's definitely negative, for the # sake of later tests that check if the collision is # before or after the polygon. k2 = -0.000001 if k2 > 0: # Unit is inside this polygon return None, None, None # Unit is past this polygon, so use the other side of # the circle for subsequent calculations. k = k2 # Find collision point on circle cx = x1 - k * mx cy = y1 - k * my # Check the slope of the tangent is within allowed bounds tx = -cy * self.rx ty = cx * self.ry prevSegmentProduct = tx * (point[1] - lastPoint[1]) \ - ty * (point[0] - lastPoint[0]) inPrevSegment = prevSegmentProduct > 0 and not isNear( prevSegmentProduct, 0) nextSegmentProduct = (nextPoint[0] - point[0]) * ty \ - (nextPoint[1] - point[1]) * tx inNextSegment = nextSegmentProduct > 0 and not isNear( nextSegmentProduct, 0) if inPrevSegment and inNextSegment: # Because both objects are convex, only one collision is # possible, so no need to check other vertices. dist = k * ((mx * self.rx) ** 2 + (my * self.ry) ** 2) ** 0.5 return dist, point, atan2(-ty, -tx) return None, None, None
def pointLiesInside(self, point, axis): # NOTE: boundary is counted as outside origin, rotatedAxes = axis s, t = rotatedAxes.rotatedFromGlobal(point, origin=origin) return t > 0 and not isNear(t, 0, epsilon=0.01)