npc.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. from sprite_object import *
  2. from random import randint, random
  3. class NPC(AnimatedSprite):
  4. def __init__(self, game, path='resources/sprites/npc/soldier/0.png', pos=(10.5, 5.5),
  5. scale=0.6, shift=0.38, animation_time=180):
  6. super().__init__(game, path, pos, scale, shift, animation_time)
  7. self.attack_images = self.get_images(self.path + '/attack')
  8. self.death_images = self.get_images(self.path + '/death')
  9. self.idle_images = self.get_images(self.path + '/idle')
  10. self.pain_images = self.get_images(self.path + '/pain')
  11. self.walk_images = self.get_images(self.path + '/walk')
  12. self.attack_dist = randint(3, 6)
  13. self.speed = 0.03
  14. self.size = 20
  15. self.health = 100
  16. self.attack_damage = 10
  17. self.accuracy = 0.15
  18. self.alive = True
  19. self.pain = False
  20. self.ray_cast_value = False
  21. self.frame_counter = 0
  22. self.player_search_trigger = False
  23. def update(self):
  24. self.check_animation_time()
  25. self.get_sprite()
  26. self.run_logic()
  27. # self.draw_ray_cast()
  28. def check_wall(self, x, y):
  29. return (x, y) not in self.game.map.world_map
  30. def check_wall_collision(self, dx, dy):
  31. if self.check_wall(int(self.x + dx * self.size), int(self.y)):
  32. self.x += dx
  33. if self.check_wall(int(self.x), int(self.y + dy * self.size)):
  34. self.y += dy
  35. def movement(self):
  36. next_pos = self.game.pathfinding.get_path(self.map_pos, self.game.player.map_pos)
  37. next_x, next_y = next_pos
  38. # pg.draw.rect(self.game.screen, 'blue', (100 * next_x, 100 * next_y, 100, 100))
  39. if next_pos not in self.game.object_handler.npc_positions:
  40. angle = math.atan2(next_y + 0.5 - self.y, next_x + 0.5 - self.x)
  41. dx = math.cos(angle) * self.speed
  42. dy = math.sin(angle) * self.speed
  43. self.check_wall_collision(dx, dy)
  44. def attack(self):
  45. if self.animation_trigger:
  46. self.game.sound.npc_shot.play()
  47. if random() < self.accuracy:
  48. self.game.player.get_damage(self.attack_damage)
  49. def animate_death(self):
  50. if not self.alive:
  51. if self.game.global_trigger and self.frame_counter < len(self.death_images) - 1:
  52. self.death_images.rotate(-1)
  53. self.image = self.death_images[0]
  54. self.frame_counter += 1
  55. def animate_pain(self):
  56. self.animate(self.pain_images)
  57. if self.animation_trigger:
  58. self.pain = False
  59. def check_hit_in_npc(self):
  60. if self.ray_cast_value and self.game.player.shot:
  61. if HALF_WIDTH - self.sprite_half_width < self.screen_x < HALF_WIDTH + self.sprite_half_width:
  62. self.game.sound.npc_pain.play()
  63. self.game.player.shot = False
  64. self.pain = True
  65. self.health -= self.game.weapon.damage
  66. self.check_health()
  67. def check_health(self):
  68. if self.health < 1:
  69. self.alive = False
  70. self.game.sound.npc_death.play()
  71. def run_logic(self):
  72. if self.alive:
  73. self.ray_cast_value = self.ray_cast_player_npc()
  74. self.check_hit_in_npc()
  75. if self.pain:
  76. self.animate_pain()
  77. elif self.ray_cast_value:
  78. self.player_search_trigger = True
  79. if self.dist < self.attack_dist:
  80. self.animate(self.attack_images)
  81. self.attack()
  82. else:
  83. self.animate(self.walk_images)
  84. self.movement()
  85. elif self.player_search_trigger:
  86. self.animate(self.walk_images)
  87. self.movement()
  88. else:
  89. self.animate(self.idle_images)
  90. else:
  91. self.animate_death()
  92. @property
  93. def map_pos(self):
  94. return int(self.x), int(self.y)
  95. def ray_cast_player_npc(self):
  96. if self.game.player.map_pos == self.map_pos:
  97. return True
  98. wall_dist_v, wall_dist_h = 0, 0
  99. player_dist_v, player_dist_h = 0, 0
  100. ox, oy = self.game.player.pos
  101. x_map, y_map = self.game.player.map_pos
  102. ray_angle = self.theta
  103. sin_a = math.sin(ray_angle)
  104. cos_a = math.cos(ray_angle)
  105. # horizontals
  106. y_hor, dy = (y_map + 1, 1) if sin_a > 0 else (y_map - 1e-6, -1)
  107. depth_hor = (y_hor - oy) / sin_a
  108. x_hor = ox + depth_hor * cos_a
  109. delta_depth = dy / sin_a
  110. dx = delta_depth * cos_a
  111. for i in range(MAX_DEPTH):
  112. tile_hor = int(x_hor), int(y_hor)
  113. if tile_hor == self.map_pos:
  114. player_dist_h = depth_hor
  115. break
  116. if tile_hor in self.game.map.world_map:
  117. wall_dist_h = depth_hor
  118. break
  119. x_hor += dx
  120. y_hor += dy
  121. depth_hor += delta_depth
  122. # verticals
  123. x_vert, dx = (x_map + 1, 1) if cos_a > 0 else (x_map - 1e-6, -1)
  124. depth_vert = (x_vert - ox) / cos_a
  125. y_vert = oy + depth_vert * sin_a
  126. delta_depth = dx / cos_a
  127. dy = delta_depth * sin_a
  128. for i in range(MAX_DEPTH):
  129. tile_vert = int(x_vert), int(y_vert)
  130. if tile_vert == self.map_pos:
  131. player_dist_v = depth_vert
  132. break
  133. if tile_vert in self.game.map.world_map:
  134. wall_dist_v = depth_vert
  135. break
  136. x_vert += dx
  137. y_vert += dy
  138. depth_vert += delta_depth
  139. player_dist = max(player_dist_v, player_dist_h)
  140. wall_dist = max(wall_dist_v, wall_dist_h)
  141. if 0 < player_dist < wall_dist or not wall_dist:
  142. return True
  143. return False
  144. def draw_ray_cast(self):
  145. pg.draw.circle(self.game.screen, 'red', (100 * self.x, 100 * self.y), 15)
  146. if self.ray_cast_player_npc():
  147. pg.draw.line(self.game.screen, 'orange', (100 * self.game.player.x, 100 * self.game.player.y),
  148. (100 * self.x, 100 * self.y), 2)
  149. class SoldierNPC(NPC):
  150. def __init__(self, game, path='resources/sprites/npc/soldier/0.png', pos=(10.5, 5.5),
  151. scale=0.6, shift=0.38, animation_time=180):
  152. super().__init__(game, path, pos, scale, shift, animation_time)
  153. class CacoDemonNPC(NPC):
  154. def __init__(self, game, path='resources/sprites/npc/caco_demon/0.png', pos=(10.5, 6.5),
  155. scale=0.7, shift=0.27, animation_time=250):
  156. super().__init__(game, path, pos, scale, shift, animation_time)
  157. self.attack_dist = 1.0
  158. self.health = 150
  159. self.attack_damage = 25
  160. self.speed = 0.05
  161. self.accuracy = 0.35
  162. class CyberDemonNPC(NPC):
  163. def __init__(self, game, path='resources/sprites/npc/cyber_demon/0.png', pos=(11.5, 6.0),
  164. scale=1.0, shift=0.04, animation_time=210):
  165. super().__init__(game, path, pos, scale, shift, animation_time)
  166. self.attack_dist = 6
  167. self.health = 350
  168. self.attack_damage = 15
  169. self.speed = 0.055
  170. self.accuracy = 0.25