class DodgeShooter(EnemyUnit): """Dodge with a machine gun unit. Represents an enemy unit, which attacks with long pauses, but also deals a lot of damage. Args: model (actor.Actor): Enemy character model. id_ (int): Enemy unit id. y_positions (list): Free positions along Y. enemy_handler (CollisionHandlerEvent): Enemy collisions handler. class_data (dict): This unit class description. """ def __init__(self, model, id_, y_positions, enemy_handler, class_data): EnemyUnit.__init__( self, id_, "Gun Dodge", class_data, model, y_positions, enemy_handler, ) self._col_node = self._init_col_node( SHOT_RANGE_MASK, MOUSE_MASK, CollisionBox(Point3(-0.04, -0.12, -0.02), Point3(0.04, 0.11, 0.06)), ) base.common_ctrl.traverser.addCollider( # noqa: F821 self._col_node, enemy_handler) self._shoot_seq = self._set_shoot_anim() self._explosion = base.effects_mgr.explosion_big(self) # noqa: F821 self._smoke = base.effects_mgr.burn_smoke(self) # noqa: F821 def _set_shoot_anim(self): """Prepare machine gun shooting animation for this unit. Returns: direct.interval.MetaInterval.Sequence: Shooting animation and sounds sequence. """ shot_snd = base.sound_mgr.loadSfx( # noqa: F821 "sounds/combat/machine_gun_shot1.ogg") base.sound_mgr.attachSoundToObject(shot_snd, self.model) # noqa: F821 fire = loader.loadModel(address("gun_fire2")) # noqa: F821 fire.reparentTo(self.model) fire.setScale(1, 0.0001, 1) if self._y_pos > 0: fire.setPos(-0.055, -0.008, 0.076) fire.setH(180) else: fire.setPos(0.065, -0.008, 0.076) shoot_par = Parallel( Sequence( LerpScaleInterval(fire, 0.07, (1, 1, 1)), LerpScaleInterval(fire, 0.07, (1, 0.0001, 1)), Wait(0.12), ), SoundInterval(shot_snd, duration=0.25), ) return Sequence(*(shoot_par, ) * 20) def capture_train(self): """The Train got critical damage - stop near it.""" EnemyUnit.capture_train(self) self._stop_tasks("_shoot_at_train", "_do_damage_to_train", "_stop_doing_damage") def enter_the_part(self, part): """Start fighting in the given part. Args: part (train.part.TrainPart): Train part this enemy entered. """ self.current_part = part self.model.play("turn_right" if self._y_pos < 0 else "turn_left") taskMgr.doMethodLater( # noqa: F821 2, self._shoot_at_train, self.id + "_shoot_at_train") def leave_the_part(self, _): """Stop fighting in the current part.""" self._stop_tasks("_shoot_at_train", "_do_damage_to_train", "_stop_doing_damage") self.current_part = None def _shoot_at_train(self, task): """Start shooting volley, including logic, animations, sounds.""" self._shoot_seq.start() taskMgr.doMethodLater( # noqa: F821 0.5, self._do_damage_to_train, self.id + "_do_damage_to_train") taskMgr.doMethodLater( # noqa: F821 6, taskMgr.remove, # noqa: F821 self.id + "_stop_doing_damage", extraArgs=[self.id + "_do_damage_to_train"], ) task.delayTime = random.randint(15, 18) return task.again def _do_damage_to_train(self, task): """Deal machine gun damage to the Train.""" if self.current_part.is_covered: if chance(40): base.train.get_damage(2) # noqa: F821 else: base.train.get_damage(2) # noqa: F821 base.train.get_shot(self._y_pos > 0) # noqa: F821 return task.again def _explode(self): """Play explosion sequence of effects and sounds. Also includes explosion physics. """ self._explosion.play() self._smoke.play() self._rb_node = BulletRigidBodyNode(self.id + "_physics") self._rb_node.setMass(100) self._rb_node.addShape(BulletBoxShape(Vec3(0.06, 0.1, 0.028))) self._rb_node.deactivation_enabled = False phys_node = self.node.attachNewNode(self._rb_node) # noqa: F821 self.model.reparentTo(phys_node) self.model.setPos(0, 0, -0.03) base.world.phys_mgr.attachRigidBody(self._rb_node) # noqa: F821 # boom impulse angle = round(self.model.getH(render)) # noqa: F821 x = 0 y = 0 if angle == 0: y = random.randint(6500, 8500) elif angle == 90: x = -random.randint(6500, 8500) elif angle == -90: x = random.randint(6500, 8500) self._rb_node.applyForce(Vec3(x, y, random.randint(1500, 2500)), Point3(0, -0.1, 0)) self._rb_node.applyTorque( Vec3( random.randint(-30, 30), random.randint(-30, 30), random.randint(-30, 30), )) def _die(self): """Die actions for this shooter. Returns: bool: True, if this shooter dies for the first time. """ if EnemyUnit._die(self): self._shoot_seq.finish() self._stop_tasks("_shoot_at_train", "_stop_doing_damage", "_do_damage_to_train")
class EnemyMotorcyclist(EnemyUnit): """Enemy unit on motorcycle base class. Includes a motocycle explosion effect and a collision node appropriate for any motorcyclist. Args: id_ (int): Enemy unit id. class_ (str): Enemy class name. class_data (dict): Enemy class description. model (actor.Actor): Enemy character model. y_positions (list): Free positions along Y. enemy_handler (CollisionHandlerEvent): Enemy collisions handler. """ def __init__(self, id_, class_, class_data, model, y_positions, enemy_handler): EnemyUnit.__init__(self, id_, class_, class_data, model, y_positions, enemy_handler) if chance(50): taskMgr.doMethodLater( # noqa: F821 random.randint(26, 28), self._play_idle_anim, self.id + "_idle") self._cry_snd = base.sound_mgr.loadSfx( # noqa: F821 "sounds/combat/enemy_cry{num}.ogg".format( num=random.randint(1, 3))) self._cry_snd.setVolume(0.4) base.sound_mgr.attachSoundToObject(self._cry_snd, self.model) # noqa: F821 else: self._cry_snd = None taskMgr.doMethodLater( # noqa: F821 random.randint(27, 29), base.world.play_fight_music, # noqa: F821 "play_music", ) self._col_node = self._init_col_node(SHOT_RANGE_MASK, MOUSE_MASK, CollisionSphere(0, 0, 0.05, 0.05)) base.common_ctrl.traverser.addCollider( # noqa: F821 self._col_node, enemy_handler) self._explosion = base.effects_mgr.explosion(self) # noqa: F821 def _explode(self): """Play explosion sequence of effects and sounds. Also includes explosion physics. """ self._explosion.play() self._rb_node = BulletRigidBodyNode(self.id + "_physics") self._rb_node.setMass(80) self._rb_node.addShape(BulletBoxShape(Vec3(0.005, 0.04, 0.028))) self._rb_node.deactivation_enabled = False phys_node = self.node.attachNewNode(self._rb_node) # noqa: F821 self.model.reparentTo(phys_node) self.model.setPos(0, -0.01, -0.025) base.world.phys_mgr.attachRigidBody(self._rb_node) # noqa: F821 # boom impulse angle = self.model.getH(render) # noqa: F821 x = 0 y = 0 if angle == 0: y = random.randint(6500, 8500) elif angle == 90: x = -random.randint(6500, 8500) elif angle == -90: x = random.randint(6500, 8500) self._rb_node.applyForce(Vec3(x, y, random.randint(1500, 2500)), Point3(0)) self._rb_node.applyTorque( Vec3( random.randint(-35, 35), random.randint(-35, 35), random.randint(-35, 35), )) def _play_idle_anim(self, task): """Play enemy unit idle animation.""" self.model.play(random.choice(("idle1", "idle2"))) if self._cry_snd is not None: self._cry_snd.play() return task.done