# coding: utf-8 import time import typing from synergine2.simulation import SubjectBehaviour from synergine2.simulation import SubjectMechanism from synergine2.simulation import Event from synergine2_xyz.move.intention import MoveToIntention from synergine2_xyz.simulation import XYZSimulation from synergine2_xyz.utils import get_angle from synergine2.simulation import disable_when from synergine2.simulation import config_value from opencombat.const import COLLECTION_ALIVE from opencombat.user_action import UserAction class SubjectStartRotationEvent(Event): def __init__( self, subject_id: int, rotate_relative: float, rotate_absolute: float, duration: float, gui_action: UserAction, ) -> None: self.subject_id = subject_id self.rotate_relative = rotate_relative self.rotate_absolute = rotate_absolute self.duration = duration self.gui_action = gui_action class SubjectContinueRotationEvent(Event): def __init__( self, subject_id: int, rotate_relative: float, duration: float, gui_action: UserAction, ) -> None: self.subject_id = subject_id self.rotate_relative = rotate_relative self.duration = duration self.gui_action = gui_action class SubjectFinishRotationEvent(Event): def __init__( self, subject_id: int, rotation_absolute: float, gui_action: UserAction, ) -> None: self.subject_id = subject_id self.rotation_absolute = rotation_absolute self.gui_action = gui_action class SubjectStartTileMoveEvent(Event): def __init__( self, subject_id: int, move_to: typing.Tuple[int, int], duration: float, gui_action: UserAction, ) -> None: self.subject_id = subject_id self.move_to = move_to self.duration = duration self.gui_action = gui_action class SubjectContinueTileMoveEvent(Event): def __init__( self, subject_id: int, move_to: typing.Tuple[int, int], duration: float, gui_action: UserAction, ) -> None: self.subject_id = subject_id self.move_to = move_to self.duration = duration self.gui_action = gui_action class SubjectFinishTileMoveEvent(Event): def __init__( self, subject_id: int, move_to: typing.Tuple[int, int], gui_action: UserAction, ) -> None: self.subject_id = subject_id self.move_to = move_to self.gui_action = gui_action class SubjectFinishMoveEvent(Event): def __init__( self, subject_id: int, move_to: typing.Tuple[int, int], gui_action: UserAction, ) -> None: self.subject_id = subject_id self.move_to = move_to self.gui_action = gui_action class MoveToMechanism(SubjectMechanism): @disable_when(config_value('_runtime.placement_mode')) def run(self) -> dict: try: # TODO: MoveToIntention doit ĂȘtre configurable move = self.subject.intentions.get(MoveToIntention) # type: MoveToIntention except KeyError: return {} if COLLECTION_ALIVE not in self.subject.collections: return {} return move.get_data() class MoveWithRotationBehaviour(SubjectBehaviour): use = [MoveToMechanism] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.simulation = typing.cast(XYZSimulation, self.simulation) @disable_when(config_value('_runtime.placement_mode')) def run(self, data) -> object: """ Compute data relative to move """ data = data[MoveToMechanism] if not data: return False # Prepare data to = data['to'] # type: typing.Tuple(int, int) return_data = {} now = time.time() # Test if it's first time if not data.get('path'): return_data['path'] = self.simulation.physics.found_path( start=self.subject.position, end=to, subject=self.subject, ) # find path algorithm can skip start position, add it if not in if return_data['path'][0] != self.subject.position: return_data['path'] = [self.subject.position] + return_data['path'] data['path'] = return_data['path'] # Prepare data path = data['path'] # type: typing.List[typing.Tuple(int, int)] path_index = path.index(self.subject.position) next_position = path[path_index + 1] next_position_direction = get_angle(self.subject.position, next_position) rotate_relative = next_position_direction - self.subject.direction # Test if finish move if path_index == len(path) - 1: return { 'move_to_finished': to, 'gui_action': data['gui_action'], } # Check if moving if self.subject.moving_to == next_position: if self.subject.start_move + self.subject.move_duration > now: # Let moving return { 'tile_move_to': next_position, 'gui_action': data['gui_action'], } return_data['tile_move_to_finished'] = self.subject.moving_to # Must consider new position of subject path_index = path.index(return_data['tile_move_to_finished']) if path_index == len(path) - 1: return { 'move_to_finished': to, 'gui_action': data['gui_action'], } next_position = path[path_index + 1] next_position_direction = get_angle( return_data['tile_move_to_finished'], next_position, ) rotate_relative = next_position_direction - self.subject.direction # Check if rotating if self.subject.rotate_to != -1: # If it is not finished if self.subject.start_rotation + self.subject.rotate_duration > now: # Let rotation do it's job return { 'rotate_relative': rotate_relative, 'rotate_absolute': next_position_direction, 'gui_action': data['gui_action'], } # rotation finish return_data['rotate_to_finished'] = self.subject.rotate_to # Check if need to rotate if not return_data.get('rotate_to_finished') \ and self.subject.direction != next_position_direction: return_data.update({ 'rotate_relative': rotate_relative, 'rotate_absolute': next_position_direction, 'gui_action': data['gui_action'], }) return return_data # Need to move to next tile return_data['tile_move_to'] = next_position return_data['gui_action'] = data['gui_action'] return return_data def action(self, data) -> [Event]: events = [] now = time.time() if data.get('path'): move = self.subject.intentions.get(MoveToIntention) move.path = data['path'] self.subject.intentions.set(move) if data.get('tile_move_to_finished'): self.subject.position = data['tile_move_to_finished'] self.subject.moving_to = (-1, -1) self.subject.start_move = -1 self.subject.move_duration = -1 events.append(SubjectFinishTileMoveEvent( subject_id=self.subject.id, move_to=data['tile_move_to_finished'], gui_action=data['gui_action'], )) if data.get('move_to_finished'): self.subject.position = data['move_to_finished'] self.subject.moving_to = (-1, -1) self.subject.start_move = -1 self.subject.move_duration = -1 self.subject.intentions.remove(MoveToIntention) events.append(SubjectFinishMoveEvent( subject_id=self.subject.id, move_to=data['move_to_finished'], gui_action=data['gui_action'], )) if data.get('rotate_to_finished'): self.subject.rotate_to = -1 self.subject.rotate_duration = -1 self.subject.start_rotation = -1 self.subject.direction = data['rotate_to_finished'] events.append(SubjectFinishRotationEvent( subject_id=self.subject.id, rotation_absolute=data['rotate_to_finished'], gui_action=data['gui_action'], )) if data.get('rotate_relative'): # Test if rotation is already started if self.subject.rotate_to == data['rotate_absolute']: # look at progression rotate_since = now - self.subject.start_rotation rotate_progress = rotate_since / self.subject.rotate_duration rotation_to_do = self.subject.rotate_to - self.subject.direction # nopep8 rotation_done = rotation_to_do * rotate_progress self.subject.direction = self.subject.direction + rotation_done rotation_left = self.subject.rotate_to - self.subject.direction duration = abs(self.subject.get_rotate_duration( angle=rotation_left, )) self.subject.rotate_duration = duration self.subject.start_rotation = now return [SubjectContinueRotationEvent( subject_id=self.subject.id, rotate_relative=rotation_left, duration=duration, gui_action=data['gui_action'], )] else: duration = abs(self.subject.get_rotate_duration( angle=data['rotate_relative'], )) self.subject.rotate_to = data['rotate_absolute'] self.subject.rotate_duration = duration self.subject.start_rotation = time.time() events.append(SubjectStartRotationEvent( subject_id=self.subject.id, rotate_relative=data['rotate_relative'], rotate_absolute=data['rotate_absolute'], duration=duration, gui_action=data['gui_action'], )) if data.get('tile_move_to'): # It is already moving ? if self.subject.moving_to == data.get('tile_move_to'): # look at progression move_since = now - self.subject.start_move move_progress = move_since / self.subject.move_duration move_done = self.subject.move_duration * move_progress duration = self.subject.move_duration - move_done self.subject.move_duration = duration return [SubjectContinueTileMoveEvent( subject_id=self.subject.id, move_to=data['tile_move_to'], duration=duration, gui_action=data['gui_action'], )] else: move = self.subject.intentions.get(MoveToIntention) move_type_duration = self.subject.get_move_duration(move) # FIXME: duration depend next tile type, etc # see opencombat.gui.base.Game#start_move_subject duration = move_type_duration * 1 self.subject.moving_to = data['tile_move_to'] self.subject.move_duration = duration self.subject.start_move = time.time() events.append(SubjectStartTileMoveEvent( subject_id=self.subject.id, move_to=data['tile_move_to'], duration=duration, gui_action=data['gui_action'], )) return events class MoveBehaviour(SubjectBehaviour): use = [MoveToMechanism] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.simulation = typing.cast(XYZSimulation, self.simulation) @disable_when(config_value('_runtime.placement_mode')) def run(self, data) -> object: """ Compute data relative to move """ data = data[MoveToMechanism] if not data: return False # Prepare data to = data['to'] # type: typing.Tuple(int, int) return_data = {} now = time.time() # Test if it's first time if not data.get('path'): return_data['path'] = self.simulation.physics.found_path( start=self.subject.position, end=to, subject=self.subject, ) # find path algorithm can skip start position, add it if not in if return_data['path'][0] != self.subject.position: return_data['path'] = [self.subject.position] + return_data['path'] data['path'] = return_data['path'] # Prepare data path = data['path'] # type: typing.List[typing.Tuple(int, int)] path_index = path.index(self.subject.position) next_position = path[path_index + 1] # Test if finish move if path_index == len(path) - 1: return { 'move_to_finished': to, 'gui_action': data['gui_action'], } # Check if moving if self.subject.moving_to == next_position: if self.subject.start_move + self.subject.move_duration > now: # Let moving return { 'tile_move_to': next_position, 'gui_action': data['gui_action'], } return_data['tile_move_to_finished'] = self.subject.moving_to # Must consider new position of subject path_index = path.index(return_data['tile_move_to_finished']) if path_index == len(path) - 1: return { 'move_to_finished': to, 'gui_action': data['gui_action'], } next_position = path[path_index + 1] # Need to move to next tile return_data['tile_move_to'] = next_position return_data['gui_action'] = data['gui_action'] return return_data def action(self, data) -> [Event]: events = [] now = time.time() if data.get('path'): move = self.subject.intentions.get(MoveToIntention) move.path = data['path'] self.subject.intentions.set(move) if data.get('tile_move_to_finished'): self.subject.position = data['tile_move_to_finished'] self.subject.moving_to = (-1, -1) self.subject.start_move = -1 self.subject.move_duration = -1 events.append(SubjectFinishTileMoveEvent( subject_id=self.subject.id, move_to=data['tile_move_to_finished'], gui_action=data['gui_action'], )) if data.get('move_to_finished'): self.subject.position = data['move_to_finished'] self.subject.moving_to = (-1, -1) self.subject.start_move = -1 self.subject.move_duration = -1 self.subject.intentions.remove(MoveToIntention) events.append(SubjectFinishMoveEvent( subject_id=self.subject.id, move_to=data['move_to_finished'], gui_action=data['gui_action'], )) if data.get('tile_move_to'): # It is already moving ? if self.subject.moving_to == data.get('tile_move_to'): # look at progression move_since = now - self.subject.start_move move_progress = move_since / self.subject.move_duration move_done = self.subject.move_duration * move_progress duration = self.subject.move_duration - move_done self.subject.move_duration = duration self.subject.start_move = time.time() return [SubjectContinueTileMoveEvent( subject_id=self.subject.id, move_to=data['tile_move_to'], duration=duration, gui_action=data['gui_action'], )] else: move = self.subject.intentions.get(MoveToIntention) move_type_duration = self.subject.get_move_duration(move) # FIXME: duration depend next tile type, etc # see opencombat.gui.base.Game#start_move_subject duration = move_type_duration * 1 self.subject.moving_to = data['tile_move_to'] self.subject.move_duration = duration self.subject.start_move = time.time() events.append(SubjectStartTileMoveEvent( subject_id=self.subject.id, move_to=data['tile_move_to'], duration=duration, gui_action=data['gui_action'], )) return events