Enemy logic

This is a snippet from the code used to control an enemy. If you have any expertise in computer science concepts, you can probably recognize this as implementing a finite state machine (FSM). Lots of repetitive code. This is why enemies and NPCs in video games often use domain-specific langauges.

I did it the hard way.

def setup_state(self,*args,**kwargs):
    if self.state == "looking":
        self.to_feet()
        self.role = acting.HoldStill(self)
    elif self.state == "returning":
        self.to_feet()
        self.role = acting.MoveToPoint(self,*self.path.get_pointvec(0.0))
    elif self.state == "patrolling":
        self.to_path(self.path,0.0)
        self.role = acting.Pace(self)
    elif self.state == "charging":
        self.to_feet()
        self.role = acting.Chase(self,game.lance,speed=10.0,nearthresh=0.5)
    elif self.state == "swatting":
        self.to_feet()
        self.role = acting.MeleeAttack(self,self.swat_choreo,"goblin_swat",duration=0.667)
    elif self.state == "recoiling":
        self.to_feet()
        self.role = acting.Recoil(self,*args,**kwargs)
    elif self.state == "dying":
        self.to_feet()
        self.role = acting.Shift(self,self.supine_choreo,0.5)
        self.weakness.targetable = False
    else:
        raise ValueError("unknown state %s" % self.state)

def check_transitions(self):
    strikedir = self.strike
    self.strike = None
    if self.state == "looking":
        if self.life <= 0:
            self.transition("dying")
        elif strikedir is not None:
            self.transition("recoiling",strikedir)
        elif self.timer >= 0.4 and self.hero_is_swattable():
            self.transition("swatting")
        elif self.timer >= 0.4 and self.hero_is_visible():
            self.transition("charging")
        elif self.timer > 2.0:
            self.transition("returning")
    elif self.state == "returning":
        if self.life <= 0:
            self.transition("dying")
        elif strikedir is not None:
            self.transition("recoiling",strikedir)
        elif self.hero_is_swattable():
            self.transition("swatting")
        elif self.hero_is_visible():
            self.transition("charging")
        elif self.role.completed:
            self.transition("patrolling")
    elif self.state == "patrolling":
        if self.life <= 0:
            self.transition("dying")
        elif strikedir is not None:
            self.transition("recoiling",strikedir)
        elif self.hero_is_swattable():
            self.transition("swatting")
        elif self.hero_is_visible():
            self.transition("charging")
    elif self.state == "charging":
        if self.life <= 0:
            self.transition("dying")
        elif game.register["lance.life"] <= 0:
            self.transition("returning")
        elif strikedir is not None:
            self.transition("recoiling",strikedir)
        elif self.hero_is_swattable():
            self.transition("swatting")
    elif self.state == "swatting":
        if self.life <= 0:
            self.transition("dying")
        elif strikedir is not None:
            self.transition("recoiling",strikedir)
        elif self.role.completed:
            self.transition("looking")
    elif self.state == "recoiling":
        if self.life <= 0:
            self.transition("dying")
        elif strikedir is not None:
            self.transition("recoiling",strikedir)
        elif self.role.completed:
            self.transition("looking")
    elif self.state == "dying":
        if self.timer > 5.0:
            game.scene.remove_object(self)

Edit: June 28, 2018: As I reread this I realized that it’s not actually a finite state machine: one of the states (“recoil”) is parameterized with a 3-D real-valued vector, which means it’s an infinite number of states. Though techincally speaking, since real numbers are discretized, it still has a finite number of states, but it’s not really an FSM in spirit.

Tampered Evidence Software © 2015 Frontier Theme