def simulate_landing(self): pos = vec3(self.car.position) vel = vec3(self.car.velocity) grav = vec3(0, 0, -650) self.trajectory = [vec3(pos)] self.landing = False collision_normal: Optional[vec3] = None dt = 1 / 60 simulation_duration = 0.8 for i in range(int(simulation_duration / dt)): pos += vel * dt vel += grav * dt if norm(vel) > 2300: vel = normalize(vel) * 2300 self.trajectory.append(vec3(pos)) collision_sphere = sphere(pos, 50) collision_ray = Field.collide(collision_sphere) collision_normal = collision_ray.direction if (norm(collision_normal) > 0.0 or pos[2] < 0) and i > 20: self.landing = True self.landing_pos = pos break if self.landing: u = collision_normal f = normalize(vel - dot(vel, u) * u) l = normalize(cross(u, f)) self.aerial_turn.target = three_vec3_to_mat3(f, l, u) else: target_direction = normalize( normalize(self.car.velocity) - vec3(0, 0, 3)) self.aerial_turn.target = look_at(target_direction, vec3(0, 0, 1))
def simulate_landing(self): dummy = Car(self.car) self.trajectory = [vec3(dummy.position)] self.landing = False collision_normal: Optional[vec3] = None dt = 1 / 60 simulation_duration = 0.8 for i in range(int(simulation_duration / dt)): dummy.step(Input(), dt) self.trajectory.append(vec3(dummy.position)) collision_sphere = sphere(dummy.position, 50) collision_ray = Field.collide(collision_sphere) collision_normal = collision_ray.direction if (norm(collision_normal) > 0.0 or dummy.position[2] < 0) and i > 20: self.landing = True self.landing_pos = dummy.position break if self.landing: u = collision_normal f = normalize(dummy.velocity - dot(dummy.velocity, u) * u) l = normalize(cross(u, f)) self.aerial_turn.target = mat3(f[0], l[0], u[0], f[1], l[1], u[1], f[2], l[2], u[2]) else: target_direction = normalize( normalize(self.car.velocity) - vec3(0, 0, 3)) self.aerial_turn.target = look_at(target_direction, vec3(0, 0, 1))
def intercept_predicate(self, car: Car, ball: Ball): if ball.position[2] > 200 or abs( ball.position[1]) > Arena.size[1] - 400: return False contact_ray = Field.collide( sphere(ball.position, self.max_distance_from_wall)) return norm(contact_ray.start) > 0 and abs( dot(ball.velocity, contact_ray.direction)) < 150
def intercept_predicate(self, car: Car, ball: Ball): # max_height = align(car, ball, self.target) * 60 + self.max_base_height max_height = 300 contact_ray = Field.collide(sphere(ball.position, max_height)) return (norm(contact_ray.direction) > 0 and ball.position[2] < max_height + 50 and (Arena.inside(ball.position, 100) or distance(ball, self.target) < 1000) and abs(car.position[0]) < Arena.size[0] - 300)
def find_landing_pos(self, num_points=200, dt=0.0333) -> vec3: '''Simulate car falling until it hits a plane and return it's final position''' dummy = Car(self.car) for i in range(0, num_points): dummy.step(Input(), dt) dummy.time += dt n = Field.collide(sphere(dummy.position, 40)).direction if norm(n) > 0.0 and i > 10: return dummy.position return self.car.position
def find_landing_orientation(self, num_points): f = vec3(0, 0, 0) l = vec3(0, 0, 0) u = vec3(0, 0, 0) dummy = Car(self.car) self.trajectory = [vec3(dummy.position)] found = False for i in range(0, num_points): dummy.step(Input(), 0.01633) self.trajectory.append(vec3(dummy.position)) u = Field.collide(sphere(dummy.position, 40)).direction if norm(u) > 0.0 and i > 40: f = normalize(dummy.velocity - dot(dummy.velocity, u) * u) l = normalize(cross(u, f)) found = True break if found: self.turn.target = mat3(f[0], l[0], u[0], f[1], l[1], u[1], f[2], l[2], u[2]) else: self.turn.target = self.car.orientation
def tick(self, controls: SimpleControllerState, dt: float): self.count += 1 self.mark_location() was_ground = self.is_on_ground() if not was_ground: self.airtime += dt else: self.airtime = 0 # we will now find actual normal result = Field.collide(make_obb(self.physics)) normal = rlu_to_Vec3(result.direction) on_ground = True if normal.length() < 0.1: # normally should be one. This just means there is no collision last_distance = (self.last_base - self.physics.location).length() if last_distance > (36.16 / 2) * 1.01: on_ground = False self.last_base.z = 1e5 normal = self.last_normal else: self.last_base = clamp_location(rlu_to_Vec3(result.start)) self.last_normal = normal if not on_ground or controls.jump: self._rlu_step(controls, dt, on_ground and not self.last_was_jump) self.last_was_jump = controls.jump clamp(self.physics) return self.physics # TODO: Special landing logic to maintain speed if on_ground and self.airtime > 0: # we are landing! lets give ourselves a nice landing # NOT REALISTIC but that's not our goal anyways # we want up = normal, and maintain all momentum in the appropriate direction orientation = Orientation(self.physics.rotation) self.physics.rotation = rotate_by_axis(orientation, orientation.up, normal) orientation = Orientation(self.physics.rotation) # now lets update the velocities remove_velocity_against_normal(self.physics.velocity, normal) self.last_was_jump = controls.jump self.is_rlu_updated = False orientation = Orientation(self.physics.rotation) # compare orientations if normal.ang_to(orientation.up) > pi / 6: # 30 degrees seems like an awful a lot return stuck_on_ground(self.physics, controls, dt, result, self.renderer) self.physics.rotation = rotate_by_axis(orientation, orientation.up, normal) self.physics.angular_velocity = Vec3(0, 0, 0) rotate_ground_and_move(self.physics, controls, dt, normal) clamp(self.physics) return self.physics
def start(self): if TWITCH_CHAT_INTERACTION: port = find_usable_port(9097) Thread(target=run_action_server, args=(port, ), daemon=True).start() set_bot_action_broker( self.action_broker ) # This seems to only work after the bot hot reloads once, weird. Thread(target=self.heartbeat_connection_attempts_to_twitch_broker, args=(port, ), daemon=True).start() while True: sleep(0) packet = self.wait_game_tick_packet() raw_players = [ self.game_tick_packet.game_cars[i] for i in range(packet.num_cars) ] self.known_players = [p for p in raw_players if p.name] if self.last_seconds_elapsed == packet.game_info.seconds_elapsed: continue elapsed_now = packet.game_info.seconds_elapsed - self.last_seconds_elapsed self.last_seconds_elapsed = packet.game_info.seconds_elapsed ball_pos = Vec3(packet.game_ball.physics.location) ball_vel = Vec3(packet.game_ball.physics.velocity) ball_ang = Vec3(packet.game_ball.physics.angular_velocity) self.ticksThisSecond += 1 if int(packet.game_info.seconds_elapsed) != self.lastFullSecond: print("ticks this second:", self.ticksThisSecond) self.ticksThisSecond = 0 self.lastFullSecond = int(packet.game_info.seconds_elapsed) if TWITCH_CHAT_INTERACTION: self.car_lasers = { k: v for k, v in self.car_lasers.items() if v.time_remaining >= 0 } else: self.car_lasers = {} for i in range(packet.num_cars): self.car_lasers[i] = Laser(0, math.inf) if packet.teams[0].score - packet.teams[1].score != self.lastScore: self.isPaused = True self.lastScore = packet.teams[0].score - packet.teams[1].score self.isKickoff = 0 elif packet.game_ball.physics.location.x == 0 and packet.game_ball.physics.location.y == 0 and packet.game_ball.physics.velocity.x == 0 and packet.game_ball.physics.velocity.y == 0: self.isKickoff += elapsed_now if self.isKickoff >= 4: self.isPaused = False ballTouchers = [] random.seed(a=int(packet.game_info.seconds_elapsed / .14)) if DURING_BOOST_ONLY: boosting = {} boostContent = {} for i in range(packet.num_cars): car = packet.game_cars[i] boosting[i] = i in self.boostContent and ( 6 if self.boostContent[i] > car.boost or (self.boostContent[i] < car.boost and self.boosting[i]) else max(0, self.boosting[i] - 1)) boostContent[i] = car.boost self.boosting = boosting self.boostContent = boostContent for index in range(packet.num_cars): car = packet.game_cars[index] car_pos = Vec3(car.physics.location) car_ori = Orientation(car.physics.rotation) self.renderer.begin_rendering(str(index) + "Lb") if index in self.car_lasers: laser = self.car_lasers[index] if not packet.game_cars[index].is_demolished and ( not DURING_BOOST_ONLY or self.boosting[index]): # and not self.isPaused: if not self.isPaused: laser.time_remaining -= elapsed_now if laser.time_remaining >= 0: for leftRight in (-1, 1): startPoint = car_pos + car_ori.forward * 63 - leftRight * car_ori.right * 26 + car_ori.up * 3 direction = car_ori.forward.orthogonalize( Vec3(0, 0, 1)).normalized( ) if car.has_wheel_contact and abs( car_ori.up.dot(Vec3(0, 0, 1)) ) > 0.999 else car_ori.forward for bounce in range(BOUNCE_SEGMENTS): closest = math.inf closestTarget = None toBall = Vec3(packet.game_ball.physics. location) - car_pos toBallProj = toBall.project(direction) toBallOrth = toBall - toBallProj toCollisionOrth = toBallOrth endVector = direction if toBallOrth.length( ) <= BALL_RADIUS and toBallProj.dot( direction) > 0: closestTarget = -1 closest = toBallProj.length( ) - math.sqrt(BALL_RADIUS**2 - toBallOrth.length()**2) ballTouchers.append(index) for otherIndex in range(packet.num_cars): if otherIndex == index: continue other_car = packet.game_cars[ otherIndex] other_car_pos = Vec3( other_car.physics.location) other_car_ori = Orientation( other_car.physics.rotation) v_local = other_car_ori.dot2( startPoint - (other_car_pos + other_car_ori.dot1(HITBOX_OFFSET) ) + 15 * other_car_ori.up) d_local = other_car_ori.dot2(direction) def lineFaceCollision(i): offset = Vec3(0, 0, 0) offset[i] = math.copysign( HITBOX_HALF_WIDTHS[i], -d_local[i]) collisionPoint = v_local - offset try: distance = -collisionPoint[ i] / d_local[i] except ZeroDivisionError: return None if distance < 0: return None collisionPoint += d_local * distance for j in range( i == 0, 3 - (i == 2), 1 + (i == 1)): if abs( collisionPoint[j] ) > HITBOX_HALF_WIDTHS[j]: return None collisionPoint[i] = offset[i] return distance distance = lineFaceCollision( 0) or lineFaceCollision( 1) or lineFaceCollision(2) if distance is not None: collisionPoint = startPoint + distance * direction toCollisionOrth = ( collisionPoint - startPoint ).orthogonalize(direction) if distance < closest: closest = distance closestTarget = otherIndex if closestTarget is not None: if closestTarget not in self.forces: self.forces[closestTarget] = Push() self.forces[ closestTarget].velocity += direction * elapsed_now try: self.forces[ closestTarget].angular_velocity += toCollisionOrth * -1 * direction / toCollisionOrth.length( )**2 * elapsed_now except ZeroDivisionError: pass pass else: # simulate raycast closest length = 100000 startPointRLU = toRLU(startPoint) directionRLU = toRLU(direction) ray = Ray(startPointRLU, directionRLU * length) while closest >= length + .2: closest = length newStartPointRLU, mirrorDirectionRLU = ray.start, ray.direction ray = Field.raycast_any( Ray( startPointRLU, directionRLU * (length - .1))) length = norm(ray.start - startPointRLU) mirrorDirection = fromRLU( mirrorDirectionRLU) newStartPoint = fromRLU( newStartPointRLU) newDirection = direction - 2 * direction.dot( mirrorDirection) * mirrorDirection endVector = direction * 0.6 - mirrorDirection * 0.4 R = 4 COLORSPIN = 2 SCATTERSPIN = 0.75 dir_ori = look_at_orientation( direction, Vec3(0, 0, 1)) dir_ori.right *= R dir_ori.up *= R end_ori = look_at_orientation( endVector, Vec3(0, 0, 1)) scatterStartFirst = startPoint + closest * direction for i in range(LASERLINES): i = i / LASERLINES * 2 * math.pi offset = dir_ori.right * math.sin( i) + dir_ori.up * math.cos(i) color = self.renderer.create_color( 255, int(255 * (0.5 + 0.5 * math.sin( car.physics.rotation.roll + leftRight * i + (COLORSPIN * packet.game_info. seconds_elapsed)))), int(255 * (0.5 + 0.5 * math.sin( car.physics.rotation.roll + leftRight * i + (COLORSPIN * packet.game_info. seconds_elapsed + 2 / 3 * math.pi)))), int(255 * (0.5 + 0.5 * math.sin( car.physics.rotation.roll + leftRight * i + (COLORSPIN * packet.game_info. seconds_elapsed + 4 / 3 * math.pi))))) self.renderer.native_draw_line_3d( self.renderer.builder, color, toDrawVector3(startPoint + offset), toDrawVector3(scatterStartFirst + offset)) for _ in range(SCATTERLINES): r = random.uniform(0, 2 * math.pi) c = leftRight * r - ( SCATTERSPIN - COLORSPIN ) * packet.game_info.seconds_elapsed i = car.physics.rotation.roll + r - leftRight * ( SCATTERSPIN ) * packet.game_info.seconds_elapsed # c = random.uniform(0, 2 * math.pi) color = self.renderer.create_color( 255, int(255 * (0.5 + 0.5 * math.sin(c))), int(255 * (0.5 + 0.5 * math.sin(c + 2 / 3 * math.pi)) ), int(255 * (0.5 + 0.5 * math.sin(c + 4 / 3 * math.pi)) )) length = 15 * random.expovariate(1) scatterStart = scatterStartFirst + dir_ori.right * math.sin( i) + dir_ori.up * math.cos(i) scatterEnd = scatterStart + end_ori.dot1( Vec3(-length, length * math.sin(i), length * math.cos(i))) self.renderer.native_draw_line_3d( self.renderer.builder, color, toDrawVector3(scatterStart), toDrawVector3(scatterEnd)) if closestTarget is not None: break else: startPoint, direction = newStartPoint + 0.1 * newDirection, newDirection self.renderer.end_rendering() ballState = None if -1 in self.forces: if not self.isPaused: ballState = BallState( # latest_touch=Touch(player_name=packet.game_cars[random.choice(ballTouchers)].name), physics=Physics(velocity=toVector3( ball_vel + self.forces[-1].velocity * PUSH_STRENGTH_BALL), angular_velocity=toVector3( ball_ang + self.forces[-1].angular_velocity * PUSH_STRENGTH_BALL_ANGULAR))) del self.forces[-1] carStates = {} for i, force in self.forces.items(): carStates[i] = CarState(physics=Physics( velocity=toVector3( Vec3(packet.game_cars[i].physics.velocity) + self.forces[i].velocity * PUSH_STRENGTH_CAR), angular_velocity=toVector3( Vec3(packet.game_cars[i].physics.angular_velocity) + self.forces[i].angular_velocity * PUSH_STRENGTH_CAR_ANGULAR))) self.forces.clear() self.set_game_state(GameState(cars=carStates, ball=ballState))