def npc_ai(self, player): global score if player is None or self.health == 0: return #if not self.aicooldown: player = Player_list[0] #Otherwise, look for player to follow. if not player.id == "Player_Basic": return dx = self.x - player.x dy = self.y - player.y self.distance_from_player = sqrt(dx*dx+dy*dy) if (self.distance_from_player < 10): #Keep some distance from player to avoid crowding. player = Player_list[0] #Otherwise, look for player to follow. if not player.id == "Player_Basic": return self.mx=self.my = 0 self.health = 0 sound.npc_pickup.play() player.crew = player.crew + 1 if player.crew > config.get("max_crew"): player.crew = config.get("max_crew") score += config.get("max_npcs_score_boost") shooter.proc.Screen.instance.alert(self.x - (self.sprite.width / 2), self.y + (self.sprite.height / 2), "+{0}% drive".format(config.get("max_npcs_drive_boost")), "Yellow") player.drive += 100 * config.get("max_npcs_drive_boost") player.upgrade() return if (self.distance_from_player < 200): #If player near: follow ai.charge(self,player) return #If player far: if not (self.aicooldown): ai.wander(self) #Seek player or other NPCs (Preference to player) self.aicooldown = config.get("ai_cooldown_seconds") * 30
def draw(self, player): if self.AlertTimeout: self.alert_label.draw() self.AlertTimeout -= 1 if player != None: self.health = player.health self.crew_count = player.crew self.shots_left = player.shots_left self.drive = player.drive if UiManager.show_ui == True: self.health_label.text = "Health: {0}".format(int(self.health)) self.health_label.draw() self.score_label.text = "Score: {0}".format(shooter.obj.score) self.score_label.draw() if player != None and player.is_reloading(): self.ammo_label.text = "Reloading!" else: self.ammo_label.text = "{0} bullets".format(self.shots_left) self.ammo_label.draw() self.crew_label.text = "Crew: {0} / {1}".format( self.crew_count, config.get("max_crew")) self.crew_label.draw() if self.drive == 0: self.drive_label.text = "Jump Drive Disabled" else: self.drive_label.text = "Jump Drive Charging: {0}%".format( self.drive / 100) self.drive_label.draw() if not (config.get('debugging')): return self.debug3.text = "#NPC: {0}".format( len(shooter.obj.Player_list) - 1) self.debug3.draw() self.debug2.text = "#Enemy: {0}".format(len( shooter.obj.Enemy_list)) self.debug2.draw() self.debug1.text = "#Bullets: {0}".format( len(shooter.obj.Bullet_list)) self.debug1.draw() self.debug6.text = "Spawn Income: {0}".format( player.handle.SpawnIncome) self.debug6.draw() self.debug5.text = "Spawn Budget: {0}".format( player.handle.SpawnBudget) self.debug5.draw() self.debug4.text = "Spawn Cost: {0}".format( player.handle.SpawnCost) self.debug4.draw()
def Circle_collision(self, Target_object): if (self.id == Target_object.parent): # ??? return 0 # Make sure we're on screen. No dying off-screen allowed. Points confuse the player. global GAME_WIDTH global GAME_HEIGHT if self.x < 0 or self.x > GAME_WIDTH or self.y < 0 or self.y > GAME_HEIGHT: return 0 #Simple collision detection for rotating sprites. #Abstract circle is appoximated around sprite bounding the maximum sprite overlap area during rotation. #Calculated radius of this circle is accumulated from 2 test objects to detect range of collision #Distance between sprite centroids is calculated and compared to the detection range. dx = self.x - Target_object.x dy = self.y - Target_object.y radius = self.radius + Target_object.radius if(radius>sqrt(dx*dx + dy*dy)): self.health = self.health - 1 if (self.id == "Deflect"): theta = atan2(dx,dy) Target_object.mx = -1*Target_object.speed*sin(theta) Target_object.my = -1*Target_object.speed*cos(theta) if Target_object.type == "Bullet": Target_object.parent = "Player_Basic" Target_object.sprite.rotation = theta * 180/pi Target_object.theta = theta Target_object.rotate() return else: Target_object.health = 0 #If self is still alive after collision, apply kick-back #Apply acceleration proportional to the ratio of mass of colliding objects. if self.health: if config.get("apply_recoil") == True: self.mx += Target_object.mx *(Target_object.size/self.size) self.my += Target_object.my *(Target_object.size/self.size) if(self.id == "Player_Basic"): #If hit, % chance of losing crew if random.randrange(100) < config.get("chance_to_lose_crew_on_hit_percent"): self.crew -= 1 if not (self.crew): self.crew = 1 #Preserve atleast 1 crew member. shooter.proc.Screen.instance.alert(self.x - (self.sprite.width / 2), self.y + (self.sprite.height / 2), "Crew Member Lost", "Red") if self.type == "Player": # Target type is enemy or bullet; with bullet, the explosion sound overtakes. sound.hurt.play() shooter.proc.Screen.instance.alert(self.x - (self.sprite.width / 2), self.y + (self.sprite.height / 2), "Health -1", "Red") return 1 #Hit detected return 0 #No Hit Detected
def spawn_random(self, dt, screen): spawned = 0 if not (config.get('enable_enemies')): return if screen.paused or shooter.tutorials.tutorial_manager.is_showing_tutorial: return self.SpawnBudget += dt * self.SpawnIncome while(self.SpawnBudget >= self.SpawnCost): self.SpawnBudget -= self.SpawnCost type_select_result = { 0: "NPC_Basic", 1: "Enemy_Basic", 2: "Enemy_Coward", 3: "Enemy_Slow", } EnemyID = type_select_result[self.NextEnemy] side = random.randrange(4) rand_x = random.randrange(GAME_WIDTH) rand_y = random.randrange(GAME_HEIGHT) position_generate_x = { 0: GAME_WIDTH + 100, 1: -100, 2: rand_x, 3: rand_x } position_generate_y = { 0: rand_y, 1: rand_y, 2: -100, 3: GAME_HEIGHT + 100 } if(self.NextEnemy>0): self.spawn_enemy(EnemyID, position_generate_x[side],position_generate_y[side]) spawned += 1 else: if config.get("enable_npc") and len(Player_list) <=3 and len(Enemy_list) > 0: self.spawn('NPC',"NPC_Basic", position_generate_x[side], position_generate_y[side], Npc) spawned += 1 self.NextEnemy = random.randrange(4) if(self.NextEnemy>0): list_of_prototypes = prototypes_json["Enemy"] EnemyID = type_select_result[self.NextEnemy] prototype = next((x for x in list_of_prototypes if x['ID'] == EnemyID), None) self.SpawnCost = prototype['Cost'] else: self.SpawnCost = 1
def update(self): #ai.update() for enemy in Enemy_list: if enemy.health: enemy.ai() for NPC in NPC_list: NPC.ai() for bullet in Bullet_list: if bullet.health: bullet.ai() for misc in Misc_list: misc.ai() self.collision() #Attempting to add kick-back to collision. #Collsion must be called after AI, but before move/update() for player in Player_list: # every tick, regenerate health old_health = math.floor(player.health) # Increased by a visible whole number player.health += player.repair if player.health > config.get("max_health"): player.health = config.get("max_health") if math.floor(player.health) > old_health: shooter.proc.Screen.instance.alert(player.x - (player.sprite.width / 2), player.y + (player.sprite.height / 2), "Health +1", "Green") # If we're a full crew, repair the jump drive if player.crew == config.get("max_crew"): player.drive += 1 player.update() player.move() for NPC in NPC_list: NPC.update() NPC.move() for enemy in Enemy_list: enemy.update() enemy.move() for bullet in Bullet_list: bullet.update() bullet.move() for misc in Misc_list: misc.update() misc.move() for pickup in Pickup_list: pickup.ai() pickup.update() pickup.move()
def fire(self, mouse_x, mouse_y): if self.__gun.fire( ): #Check gun is in condition to fire (Has bullets, has cooled down, etc) i = 0 even_angle = 2 * self.__gun.spread / self.__gun.burst_shots for shot in range(self.__gun.burst_shots ): #Repeat for number of bullets / shot. dx = mouse_x - self.x #Calculate shot vector dy = mouse_y - self.y if (self.__gun.spread): if config.get('even_bullet_spread') == True: # center bullet goes straight where you direct it. # create a fan from [-spread .. spread] that's evenly distributed spread = (-self.__gun.spread + (i * even_angle)) * pi / 180 else: spread = random.randrange(-1 * self.__gun.spread, self.__gun.spread) * pi / 180 else: spread = 0 # NOT the best generic solution (the above fix for even shotgun spreads screwed this up) # However, lowest risk (chance of breaking something else) -- release went out yesterday if self.__gun.type == "machine": spread = random.randrange(-1 * self.__gun.spread, self.__gun.spread) * pi / 180 #Calculate attack vector w/Random scatter theta = atan2(dy, dx) + spread target_x = self.x + 100 * cos( theta) #Calculate new attack location including scatter target_y = self.y + 100 * sin(theta) if config.get("apply_recoil") == True: if config.get("control_style") == "relative": self.mx += 0.5 * cos( theta + pi) #Recoil - Obey physics, It's the law. self.my += 0.5 * sin( theta + pi) #Conservation of energy. else: self.mx += 2 * cos( theta + pi) #Recoil - Obey physics, It's the law. self.my += 2 * sin( theta + pi) #Conservation of energy. self.attack( self.__gun.bullet_type, target_x, target_y ) #Spawn attack using randomly varied target location. i += 1
def move(self): #Function name move is misleading: Function responsible for processing movement, rotation & object maintainance self.x = self.x + self.mx self.y = self.y + self.my if not (self.id == "Player_Basic"): #Calculate sprite rotation + position(Sprite centroid, not datum) and update sprite theta = atan2(-self.my,self.mx) #Sprite face direction of movement else: theta = self.theta if self.x < 0: self.x = GAME_WIDTH if self.x > GAME_WIDTH: self.x = 0 if self.y < 0: self.y = GAME_HEIGHT if self.y > GAME_HEIGHT: self.y = 0 if not config.get("control_style") == "relative":theta = atan2(-self.my,self.mx) self.sprite.rotation = theta*180/pi #Sprite rotation in degrees, while trig functions return radians (Like real maths) self.sprite_x = self.x - self.radius * sin(theta+self.theta_offset) #Calculate centroid position given: self.sprite_y = self.y - self.radius * cos(theta+self.theta_offset) # Sprite Datum position/Rotation & # Calculated centroid offsets from sprite aspects self.sprite.set_position(self.sprite_x,self.sprite_y) if self.health < 1: self.heatlh = 0 # die Type_lists[self.type].remove(self) if self.cooldown: self.cooldown = self.cooldown - 1 if self.aicooldown: self.aicooldown -= 1
def move(self): #Function name move is misleading: Function responsible for processing movement, rotation & object maintainance self.x = self.x + self.mx self.y = self.y + self.my theta = self.theta if self.x < 0: self.x = shooter.obj.GAME_WIDTH if self.x > shooter.obj.GAME_WIDTH: self.x = 0 if self.y < 0: self.y = shooter.obj.GAME_HEIGHT if self.y > shooter.obj.GAME_HEIGHT: self.y = 0 if not config.get("control_style") == "relative": theta = atan2(-self.my, self.mx) thrust = self.commandy self.theta += self.commandx if self.commandx == 0: if self.commandy == 0: self.imagebuff = self.shipseq[8] #Idle if self.commandy > 0: self.imagebuff = self.shipseq[7] #Forward if self.commandy < 0: self.imagebuff = self.shipseq[6] #Backward elif self.commandx < 0: if self.commandy < 0: self.imagebuff = self.shipseq[1] #Left back if self.commandy == 0: self.imagebuff = self.shipseq[5] #Left still if self.commandy > 0: self.imagebuff = self.shipseq[3] #Left forward elif self.commandx > 0: if self.commandy < 0: self.imagebuff = self.shipseq[0] #Right back if self.commandy == 0: self.imagebuff = self.shipseq[4] #Right still if self.commandy > 0: self.imagebuff = self.shipseq[2] #Right Forward if not (self.sprite.image == self.imagebuff): self.sprite.image = self.imagebuff self.mx += thrust * cos(self.theta) self.my += thrust * -1 * sin(self.theta) self.mx = self.mx * 0.99 self.my = self.my * 0.99 self.sprite.rotation = theta * 180 / pi #Sprite rotation in degrees, while trig functions return radians (Like real maths) self.sprite_x = self.x - self.radius * sin( theta + self.theta_offset) #Calculate centroid position given: self.sprite_y = self.y - self.radius * cos( theta + self.theta_offset) # Sprite Datum position/Rotation & # Calculated centroid offsets from sprite aspects self.sprite.set_position(self.sprite_x, self.sprite_y) if self.health < 1: self.health = 0 if self.cooldown: self.cooldown = self.cooldown - 1
def Loot(self,chance): self.handle.SpawnIncome += (config.get("difficulty_ramp_up") / 100) global score # obj.score score += self.cost if((random.randrange(100)<chance) and (len(Pickup_list)<3)): Type = random.randrange(5) PickupType = { 0:"Weapon_Pistol", 1:"Weapon_Machine", 2:"Weapon_Shotgun", 3:"Weapon_Rocket", 4:"Weapon_Rail" } self.handle.spawn('Pickup',PickupType[Type],self.x, self.y)
def on_key_press(symbol, modifiers): if not symbol in self._currently_pressed: self._currently_pressed.append(symbol) # Call our one and only subscriber if he's there (tutorial manager) if self.on_press_callback != None: self.on_press_callback(symbol, self._currently_pressed) # game logic to execute when we press a key the first time only (not press+hold) if symbol == key.R and len(shooter.obj.Player_list) > 0: shooter.obj.Player_list[0].reload() elif symbol == key.P: self.paused = not self.paused ui_manager.paused = self.paused elif config.get("enable_cheat_codes") == True and self.is_pressed( key.GRAVE): shooter.debug.ask_and_process_cheat_code( shooter.obj.Player_list[0])
def deflect_ai(self, player): if player is None: return ##player = Player_list[0] #if not (player.id == "Player_Basic"):return shield_radius = config.get("sword_attack_radius") * 2 self.health -=1 self.mx = 0.01 * sin(self.theta) #Apply small movement to ensure sword renders with correct rotation. self.my = 0.01 * cos(self.theta) #Position applied below instead of movement function so position updated before collsion detection self.sprite.rotation = self.theta # +width/2, -height/2 makes the sword perfectly center on the player self.x = player.x +shield_radius * sin(self.theta) #Apply position immediately so factors in collision detection self.y = player.y + shield_radius * cos(self.theta)
def upgrade(self): self.repair = 0 self.speed = self.stock_speed self.attackrate = self.stock_attack self.reloadrate = self.stock_reload self.__gun.reload_time_seconds = self.__gun.stock_reload_time_seconds self.__gun._cooldown_time_seconds = self.__gun.stock_cooldown_time_seconds if self.crew >= 3: self.repair = config.get( 'upgrades')["emergency_repairs_repair_rate"] self.alert_if_necessary("+ Repairing") if self.crew >= 6: self.speed = self.stock_speed * config.get( 'upgrades')["engineering_crew_speed_multiplier"] self.alert_if_necessary("+ Speed") if self.crew >= 9: self.attackrate = config.get( 'upgrades')["tactics_crew_attack_rate"] self.alert_if_necessary("+ Fire Rate") if self.crew >= 12: self.reloadrate = config.get('upgrades')["gunner_crew_reload_rate"] self.alert_if_necessary("+ Reload Speed") if self.crew >= 15: self.repair = config.get('upgrades')["repair_crew_repair_rate"] self.alert_if_necessary("+ Repairing") if self.crew >= 18: self.speed = self.stock_speed * config.get( 'upgrades')["engine_tuning_speed_multiplier"] self.alert_if_necessary("+ Speed") if self.crew >= 21: self.attackrate = config.get( 'upgrades')["weapons_tuning_attack_rate"] self.alert_if_necessary("+ Fire Rate") # Crew is capped at 21 #if self.crew >= 24: # self.reloadrate = config.get('upgrades')["ammo_management_reload_rate"] # self.alert_if_necessary("++ Reload Speed") #if self.crew >= 27: # self.speed = self.stock_speed * config.get('upgrades')["advanced_engine_tuning_speed_multiplier"] # self.alert_if_necessary("+++ Speed") #if self.crew >= 30: # self.reloadrate = config.get('upgrades')["advanced_ammo_management_reload_rate"] # self.alert_if_necessary("+++ Reload Speed") self.__gun.reload_time_seconds = self.__gun.reload_time_seconds / self.reloadrate self.__gun._cooldown_time_seconds = self.__gun._cooldown_time_seconds / self.attackrate
def switch(self, gun_config_prefix): self.type = gun_config_prefix self.__total_shots = config.get( "{0}_bullets".format(gun_config_prefix)) self.burst_shots = config.get("{0}_burst".format(gun_config_prefix)) self.spread = config.get("{0}_spread".format(gun_config_prefix)) self.__shots_left = self.__total_shots self.stock_reload_time_seconds = config.get( "{0}_reload_seconds".format(gun_config_prefix)) self.reload_time_seconds = self.stock_reload_time_seconds self.stock_cooldown_time_seconds = config.get( "{0}_cooldown_seconds".format(gun_config_prefix)) self._cooldown_time_seconds - self.stock_cooldown_time_seconds self.bullet_type = config.get( "{0}_bullet_type".format(gun_config_prefix)) self.__audio_file = "sounds/{0}.wav".format(gun_config_prefix) self.pew = pyglet.media.StaticSource( pyglet.media.load(self.__audio_file, streaming=False)) self.pickup_sound.play
def input(self): if (not self.paused and not shooter.tutorials.tutorial_manager.is_showing_tutorial and len(shooter.obj.Player_list) > 0): player = shooter.obj.Player_list[0] if not (player.id == "Player_Basic"): return player.mousex = self.mouse_x player.mousey = self.mouse_y if config.get("control_style") == "relative": player.commandy = self.is_pressed( key.S) * -0.1 + self.is_pressed(key.W) * 0.2 player.commandx = (self.is_pressed(key.A) * -1 + self.is_pressed(key.D) * 1) * 0.12 player.mx = player.mx * 0.99 player.my = player.my * 0.99 else: player.mx = (self.is_pressed(key.A) * -1 + self.is_pressed(key.D) * 1) * player.speed player.my = (self.is_pressed(key.S) * -1 + self.is_pressed(key.W) * 1) * player.speed if not (abs(player.mx) + abs(player.my) == 1): # If both keys are down, don't move at 1.4x; move at ~sqrt(2)/2 player.mx = player.mx * 0.707 player.my = player.my * 0.707 if config.get("enable_cheat_codes") == True and self.is_pressed( key.GRAVE): shooter.debug.ask_and_process_cheat_code(player) if (self.is_pressed(mouse.RIGHT)): if config.get('melee_enabled'): if not (player.shield): shield = player.handle.spawn('NPC', "Deflect", player.x, player.y) dx = self.mouse_x - player.x dy = self.mouse_y - player.y shield.theta = atan2(dx, dy) shooter.obj.NPC_list.append(shield) player.shield = 1 else: for deflect in shooter.obj.NPC_list: if deflect.id == "Deflect": # center on player dx = self.mouse_x - player.x dy = self.mouse_y - player.y deflect.health = 3 # stay around player deflect.theta = atan2(dx, dy) #Mathy goodness. else: # TODO: duplicate railgun code below (from elif self.is_pressed(mouse.LEFT)) here # If not, railgun just straight-out fires (doesn't charge) with right key if # melee is not enabled player.fire(self.mouse_x, self.mouse_y) else: player.shield = 0 if self.is_pressed(mouse.LEFT) and player.shield == 0: for rail in shooter.obj.Bullet_list: if rail.id == "Bullet_RailCharge": dx = self.mouse_x - player.x dy = self.mouse_y - player.y rail.theta = atan2(dx, dy) rail.health = 20 return player.fire(self.mouse_x, self.mouse_y)
return if game_started: #Check user input Screen_handler.input() if not Screen_handler.paused and not shooter.tutorials.tutorial_manager.is_showing_tutorial: Object_handler.update() Object_handler = obj.Object_handler() Screen_handler = proc.Screen(GAME_WIDTH, GAME_HEIGHT) file_watcher.watch('data/object.json', obj.load_prototype_data) if config.get("skip_splash_screens") != True: screen = Object_handler.spawn('Misc', "MG Splash", 0, 0, splash_screen.SplashScreen) center(screen) screen.on_death = lambda: show_dg_splash() else: start_game() pyglet.clock.schedule_interval(frame_callback, 1 / 30.0) # call frame_callback at 30FPS # Shut down threads cleanly in case of a crash try: pyglet.app.run() except: file_watcher.stop()
def __init__(self): # Persist these values even if the player dies self.health = 0 self.crew_count = 1 self.shots_left = 0 self.drive = 0 self.AlertTimeout = 0 self.health_label = pyglet.text.Label( 'Health: ?', font_name=UiManager.FONT_NAME, x=shooter.obj.GAME_WIDTH - UiManager.RIGHT_PADDING, y=shooter.obj.GAME_HEIGHT - UiManager.SPACE_BETWEEN_LINES) self.ammo_label = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=self.health_label.x, y=self.health_label.y - UiManager.SPACE_BETWEEN_LINES) self.score_label = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=self.health_label.x, y=self.ammo_label.y - UiManager.SPACE_BETWEEN_LINES) self.drive_label = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=UiManager.LEFT_PADDING, y=shooter.obj.GAME_HEIGHT - UiManager.SPACE_BETWEEN_LINES) self.crew_label = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=self.drive_label.x, y=self.drive_label.y - UiManager.SPACE_BETWEEN_LINES) self.alert_label = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=0, y=0, color=(0, 0, 0, 0), align='center') if not (config.get('debugging')): return self.debug1 = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=self.drive_label.x, y=UiManager.SPACE_BETWEEN_LINES) self.debug2 = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=self.drive_label.x, y=self.debug1.y + UiManager.SPACE_BETWEEN_LINES) self.debug3 = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=self.drive_label.x, y=self.debug2.y + UiManager.SPACE_BETWEEN_LINES) self.debug4 = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=self.health_label.x - 300, y=self.debug1.y) self.debug5 = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=self.health_label.x - 300, y=self.debug2.y) self.debug6 = pyglet.text.Label("", font_name=UiManager.FONT_NAME, x=self.health_label.x - 300, y=self.debug3.y)