import random import math from Entities.entity import Entity class Marine(Entity): movement = 0 view = 3 def update(self): self.centered_position = (self.iso_x, self.iso_y) # Aggiorna il campo visivo dell'unità (fog of war) self.cast_rays_to_distance(7) self.move() super().update() def set_visibility(self): # Set visibility based on the distance to the cursor distance = self.graphics.get_distance((self.x, self.y), self.engine.cursor_pos) if distance < self.view: self.graphics.set_opacity(self.frame, 1.0) else: self.graphics.set_opacity(self.frame, 0.5) def select_unit(self): self.selected = True # Play a random voice response when selected sound_file = f"marine/tmawht0{random.randint(0, 3)}.wav" if not self.game.cmd_sound_effects: self.graphics.play_sound(sound_file) self.game.cmd_sound_effects = True def move(self): # Aquisisci la posizione dell'unità self.iso_x, self.iso_y = self.graphics.iso_transform(self.x, self.y) self.position = (self.x, self.y) # Controlla se l'unità èha raggiunto la cella di destinazione if self.target_cell != (self.x, self.y): # Contolla che la cella target non sia occupata da una unità che abbia quella cella come target if self.game.entities_positions.get(self.target_cell): if self.game.entities_positions.get(self.target_cell).target_cell == self.target_cell and self.game.entities_positions.get(self.target_cell) != self: #calcola un nuovo target self.target_cell = self.graphics.get_next_cell(self.target_cell, self.position, ignore_units=True) if not self.target_cell: self.target_cell = self.position # Se non ci sono celle disponibili, ferma l'unità return # Aquisisci la posizione a schermo della cella di destinazione e disegna lì un quadrato rosso target_iso_x, target_iso_y = self.graphics.iso_transform(self.target_cell[0], self.target_cell[1]) self.graphics.draw_square(target_iso_x, target_iso_y, color=(255, 0, 0, 255), margin=6) # Se la cella successiva è la stessa posizione dell'unità, calcola la prossima cella # (in caso contrario sarebbe ancora in movimento parziale) if self.position == self.next_cell: self.next_cell = self.graphics.get_next_cell(self.next_cell, self.target_cell) # Se l'algoritmo ha efettivamente trovato una cella, occupa la posizione if self.next_cell: self.game.entities_positions[self.next_cell] = self # Se la cella di destinazione è occupata da un'altra entità, fermati else: self.action = "idle" self.next_cell = self.position return # Set walking animation and direction self.action = "walk" self.direction = self.graphics.get_direction((self.x, self.y), self.next_cell) self.moving = True # Calculate target coordinates target_x, target_y = self.graphics.iso_transform(self.next_cell[0], self.next_cell[1]) # Increment movement counter self.movement += 0.1 # Calculate how far we've moved (0.0 to 1.0) move_progress = min(self.movement, 1.0) # Calculate new position based on progress between cells self.iso_x = self.iso_x + (target_x - self.iso_x) * move_progress self.iso_y = self.iso_y + (target_y - self.iso_y) * move_progress if self.movement >= 1.0: # Reset movement and set to idle self.movement = 0 self.x, self.y = self.next_cell self.iso_x, self.iso_y = self.graphics.iso_transform(self.x, self.y) else: self.action = "idle" def set_target_cell(self, target_cell): # Prima verifica che le coordinate siano all'interno dei limiti della mappa map_height = len(self.engine.map) map_width = len(self.engine.map[0]) x, y = target_cell # Verifica che le coordinate siano valide if not (0 <= y < map_height and 0 <= x < map_width): if not self.game.cmd_sound_effects: self.graphics.play_sound("sounds/perror.wav") self.game.cmd_sound_effects = True return # Ora puoi controllare il contenuto della cella in sicurezza if self.engine.map[y][x]["wall"]: if not self.game.cmd_sound_effects: self.graphics.play_sound("sounds/perror.wav") self.game.cmd_sound_effects = True return self.target_cell = target_cell if not self.game.cmd_sound_effects: self.graphics.play_sound(f"marine/tmayes0{random.randint(0, 3)}.wav") self.game.cmd_sound_effects = True def cast_rays_to_distance(self, view_distance): """ Rivela tutte le celle nel raggio di vista dell'unità usando ray casting. Le celle vengono illuminate indipendentemente dagli ostacoli, ma quelle più lontane hanno maggiore ombreggiatura. """ # Pre-calcolo dei valori trigonometrici per ottimizzazione if not hasattr(self, '_precomputed_angles'): self._precompute_ray_angles() revealed_cells = {} # Lancia raggi in 18 direzioni (ogni 20 gradi) for angle_idx, (sin_val, cos_val) in enumerate(self._ray_directions): if angle_idx == 0: self._cast_visibility_ray(angle_idx, sin_val, cos_val, view_distance+1, revealed_cells) else: self._cast_visibility_ray(angle_idx, sin_val, cos_val, view_distance, revealed_cells) def _precompute_ray_angles(self): """Pre-calcola i valori sin/cos per evitare calcoli ripetuti ogni frame""" self._ray_directions = [] for angle in range(0, 360, 20): radians = math.radians(angle) self._ray_directions.append((math.sin(radians), math.cos(radians))) self._precomputed_angles = True def _cast_visibility_ray(self, angle_idx, sin_val, cos_val, max_distance, revealed_cells): """ Lancia un singolo raggio di visibilità dalla posizione dell'unità. Rivela tutte le celle lungo il percorso, applicando ombreggiatura crescente con la distanza. """ cell_size_x = int(self.engine.graphics.cell_size/3) cell_size_y = int(self.engine.graphics.cell_size/6) # Calcola le posizioni lungo il raggio partendo dalla distanza massima verso il centro for step in range(max_distance, 0, -1): current_distance = max_distance - step # Calcola la posizione del punto sul raggio ray_x = int(self.centered_position[0] + current_distance * cell_size_x * sin_val) ray_y = int(self.centered_position[1] - current_distance * cell_size_y * cos_val) # Converte le coordinate schermo in coordinate griglia grid_x, grid_y = self.graphics.inv_iso_transform(ray_x, ray_y) # Verifica che le coordinate siano valide if self._is_valid_grid_position(grid_x, grid_y): self._reveal_cell(grid_x, grid_y, step, revealed_cells) # Disegna il raggio solo per il punto più distante (visualizzazione debug) # if step == 1: # self.graphics.draw_line( # (self.centered_position[0], self.centered_position[1], ray_x, ray_y), # color=(0, 255, 0, 255) # ) def _is_valid_grid_position(self, grid_x, grid_y): """Controlla se le coordinate della griglia sono valide""" map_width = len(self.engine.map[0]) map_height = len(self.engine.map) return 0 <= grid_x < map_width and 0 <= grid_y < map_height def _reveal_cell(self, grid_x, grid_y, distance_step, revealed_cells): """ Rivela una singola cella della mappa e applica l'ombreggiatura appropriata. Le celle più vicine (distance_step == 1) hanno ombra ridotta. """ cell_key = (grid_x, grid_y) # Salva il valore originale dell'ombra solo se non già processata if cell_key not in revealed_cells: revealed_cells[cell_key] = self.game.map_shadow[grid_y][grid_x] # Marca la cella come visitata e rimuovi l'ombra base self.engine.map[grid_y][grid_x]["visited"] = True self.game.map_shadow[grid_y][grid_x] = 0 # Applica ombreggiatura speciale per le celle ai bordi del campo visivo if distance_step == 1: original_shadow = revealed_cells.get(cell_key, 0) self.game.map_shadow[int(grid_y)][int(grid_x)] = max(original_shadow - 0.5, 0)