Exemplo n.º 1
0
class NPC(Entity):
    """Super class for all NPCs"""
    def __init__(self, game, y, x):
        """Initialise the player object"""
        super(NPC, self).__init__(game)
        self.y = y
        self.x = x
        self.colour = Constants.COLOUR_WHITE
        self.path = []
        self.square = None
        self.plan = Plan(self)
        self.currentBehaviour = DefaultBehaviour(self)
        self.alive = True
        self.killer = False

        self.dialogue = dialogue.Dialogue(self)
        standardDialogueChoice1 = dialogue.DialogueChoice("Hello!", 
                                                          "Hello to you too!")
        standardDialogueChoice2 = dialogue.DialogueChoice("My name is Kate!", 
                                                          "Fascinating.")
        def responseFunction3(npc, response):
            npc.game.player.notebook.addToKnownNpcs(self)
            actualResponse = "My name is " + npc.firstName + " " + npc.lastName
            actualResponse += ". I live in house number " + str(npc.square.house.number)
            actualResponse += "."
            return actualResponse
        standardDialogueChoice3 = dialogue.DialogueChoice("Who are you?", 
                                                          "",
                                                          responseFunction3)

        standardDialogueChoice4 = dialogue.DialogueChoice("No, hello to YOU!", 
                                                          "We're done talking, freakshow.")
        secondNode = dialogue.DialogueNode()
        secondNode.addChoice(standardDialogueChoice4)

        choicePredicate3 = lambda: not self.game.player.notebook.isNpcKnown(self)
        dialogueRoot = dialogue.DialogueNode()
        dialogueRoot.addChoice(standardDialogueChoice1, None, secondNode)
        dialogueRoot.addChoice(standardDialogueChoice2)
        dialogueRoot.addChoice(standardDialogueChoice3, choicePredicate3)
        self.dialogue.setRootNode(dialogueRoot)

        # Fluffy, plot stuff
        self.gender = random.choice([Gender.MALE, Gender.FEMALE])
        self.firstName = "Dave"
        if self.gender == Gender.MALE:
            self.firstName = names.getMaleFirstName()
        else:
            self.firstName = names.getFemaleFirstName()
        self.lastName = names.getLastName()

        self.eyeColour = random.choice(["green", "blue", "brown"])
        self.hairColour = random.choice(["brown", "red", "blonde"])
        self.description = "They have " + self.eyeColour + " eyes and " + self.hairColour + " hair."

        # Emotions and states
        # TODO: Something with this?
        self.scared = False
        self.answeringDoor = False

    def die(self):
        self.alive = False
        self.character = '%'
        self.currentBehaviour = Dead(self)

    def beginConversation(self):
        self.dialogue.beginConversation()

    def isAtHome(self):
        # If we need to know that the NPC is at home, regardless of their
        # plan
        if self.square:
            isInX = (self.x > self.square.houseXOffset and
                     self.x < self.square.houseXOffset + self.square.house.width)
            isInY = (self.y > self.square.houseYOffset and
                     self.y < self.square.houseYOffset + self.square.house.height)
            return isInX and isInY

    def update(self):
        """If the NPC is alive, carry out their Plan and Behavaiour"""
        if self.alive:
            # Move randomly, or sometimes actually pick a place to go and go there!
            # If we have a plan and we're not otherwise occupied (to do), execute it
            self.plan.checkForAndExecutePlanEntry()

            # Once we've decided on a plan (or have no plan), the NPC should first
            # go to anywhere they're planning on being before performing their 
            # status action.

            if self.path and self.alive:
                (nextY, nextX) = self.path[0]
                # The algorithm will have avoided walls and fences,
                # so the only obstructions will be the player, doors and NPCs
                blockedByEntity = False
                blockedByDoor = False
                # Check for player..
                if (self.game.player.y, self.game.player.x) == (nextY, nextX):
                    blockedByEntity = True
                # Check for NPC..
                if Constants.NPC_ON_NPC_COLLISIONS:
                    for npc in self.game.npcs:
                        if npc is not self:
                            if (npc.y, npc.x) == (nextY, nextX):
                                blockedByEntity = True
                # Check for Door..
                if (nextY, nextX) in self.game.doors:
                    door = self.game.doors[(nextY, nextX)]
                    if door.closed:
                        blockedByDoor = True
                        door.npcOpen()
                if (not blockedByEntity) and (not blockedByDoor):
                    (self.y, self.x) = (nextY, nextX)
                    self.path.pop(0)
            else:
                if Constants.PATHFINDING_DEBUG:
                    randnum = random.randint(1, 30)
                    if randnum == 25:
                        targetX = targetY = 0
                        while True:
                            targetX = random.randint(1, Constants.MAPWIDTH)
                            targetY = random.randint(1, Constants.MAPHEIGHT)
                            if (targetY, targetX) not in self.game.walls:
                                break
                        self.path = self.findPath(targetY, targetX)
                self.currentBehaviour.execute()

    def findPath(self, targetY, targetX):
        """A big ol' ripoff of the A* algorithm"""
        def sld(y1, x1, y2, x2):
            """Using Euclidean distance as the heuristic"""
            return ((y1-y2) ** 2) + ((x1-x2) ** 2)

        def manhattan(y1, x1, y2, x2):
            """Using Manhattan distance as the heuristic"""
            return (abs(y1 - y2) + abs(x1 - x2))

        def nextCurrent(openSet):
            bestNode = (1,1)
            bestScore = None
            nodeSet = { node: f_score[node] for node in openSet}
            bestNode = min(nodeSet, key=nodeSet.get)
            return bestNode

        def spaceObstructed(y, x, game):
            wallObstruction = (y, x) in game.walls

            # This line WILL cause pathfinding failures if the character is
            # currently inside a fenced area or is attempting to pathfind into
            # one.
            fenceObstruction = (y, x) in game.fences
            #fenceObstruction = False
            return wallObstruction or fenceObstruction

        def returnNeighbours(node, game):
            # Return a list of nodes that aren't walls basically
            neighbours = []
            for nX in [-1, 1]:
                if not spaceObstructed(node[0], node[1] + nX, game):
                    neighbours.append((node[0], node[1] + nX))
                if not spaceObstructed(node[0] + nX, node[1], game):
                    neighbours.append((node[0] + nX, node[1]))
            return neighbours

        def reconstructPath(current):
            if current in came_from:
                rest = reconstructPath(came_from[current])
                rest.append(current)
                return rest
            else:
                return [current,]

        x = self.x
        y = self.y
        goal = (targetY, targetX)
        closedSet = []
        openSet = [(y,x)]
        came_from = {}

        g_score = {}
        f_score = {}
        g_score[(y,x)] = 0
        adjustment = Constants.PATHFINDING_HEURISTIC_ADJUSTMENT
        heuristicScore = manhattan(y, x, targetY, targetX) * adjustment
        f_score[(y,x)] = g_score[(y,x)] + heuristicScore

        count = 100000

        while openSet and count > 0:
            count -= 1
            current = nextCurrent(openSet)
            if current == (targetY, targetX):
                return reconstructPath(current)

            openSet.remove(current)
            closedSet.append(current)
            for neighbour in returnNeighbours(current, self.game):
                if neighbour in closedSet:
                    continue
                tentative_g_score = g_score[current] + 1
                nG_score = -1
                try:
                    nG_score = g_score[neighbour]
                except:
                    pass
                if (neighbour not in openSet or 
                    tentative_g_score < nG_score):
                    came_from[neighbour] = current
                    g_score[neighbour] = tentative_g_score
                    f_score[neighbour] = g_score[neighbour] + sld(neighbour[0], neighbour[1], goal[0], goal[1])
                    if neighbour not in openSet:
                        openSet.append(neighbour)
        return False

    def getDescription(self):
        "Returns the description, modifying it for special cases"
        description = self.description
        if not self.alive:
            description += " " + ("He" if self.gender == Gender.MALE
                                  else "She") + "'s seen better days."
        return description