""" Copyright 2008 Ben Sarsgard This file is part of ZedZed. ZedZed is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ZedZed is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ZedZed. If not, see . """ from helpers import * from Items import * from pygame.sprite import Sprite import random import math class Actor(object): def __init__(self, pos_x, pos_y, image): self.image_name = image self.image = load_image(image, -1) self.pos_x = pos_x self.pos_y = pos_y self.age = 0 self.wounds = 0 self.fatigue = 0 self.hunger = 0 self.thirst = 0 self.strength = 0 self.accuracy = 0 self.speed = 0 self.concentration = 0 self.visibility = 0 self.action_debt = random.randint(0, 10) self.items = [] self.weapon = None self.armor = None self.weight = 0 self.action_debt_move = 10 self.action_debt_push = 20 self.action_debt_attack = 30 self.action_debt_barricade = 50 def GetName(self): return 'Actor' def IsDead(self): return self.wounds >= 100 or self.thirst >= 100 or self.hunger >= 100 def GetAttackChance(self): if self.weapon is None: return self.GetAccuracy() + self.GetConcentration() else: return self.GetAccuracy() + self.GetConcentration() + self.weapon.accuracy def GetDefendChance(self): if self.armor is None: return self.GetSpeed() + self.GetAccuracy() else: return self.GetSpeed() + self.GetAccuracy() + self.armor.protection def Attack(self, prey): can_attack = True success = False message = "" if self.weapon != None and isinstance(self.weapon, LoadableWeapon): can_attack = False for item in self.items: if isinstance(item, Ammunition) and item.caliber == self.weapon.caliber: if item.quantity > 1: item.quantity -= 1 else: self.RemoveItem(item) can_attack = True break if can_attack: hit_roll = random.randint(0, self.GetAttackChance()) defend_roll = random.randint(0, prey.GetDefendChance()) success = hit_roll > defend_roll return success def GetMaxDamage(self): if self.weapon is None: return self.GetStrength() else: return (self.GetStrength() + self.weapon.damage) * 2 def GetStrength(self): return max(1, math.ceil(self.strength * (float(100 ** 2 - self.wounds ** 2 - self.hunger ** 2) / 100 ** 2))) def GetAccuracy(self): return max(1, math.ceil(self.accuracy * (float(100 ** 2 - self.wounds ** 2 - self.thirst ** 2) / 100 ** 2))) def GetSpeed(self): return max(1, math.ceil(self.speed * (float(100 ** 2 - self.wounds ** 2 - self.fatigue ** 2 - self.weight ** 2) / 100 ** 2))) def GetConcentration(self): return max(1, math.ceil(self.concentration * (float(100 ** 2 - self.wounds ** 2 - self.fatigue ** 2 - self.hunger ** 2 - self.thirst ** 2) / 100 ** 2))) def GetVisibility(self): return int(max(2, math.ceil(self.visibility * (float(100 ** 2 - self.wounds ** 2 - self.fatigue ** 2 - self.hunger ** 2 - self.thirst ** 2) / 100 ** 2)))) def EquipItem(self, index): if len(self.items) > index: item = self.items[index] if isinstance(item, Weapon): self.weapon = item self.items.remove(item) elif isinstance(item, Armor): self.armor = item self.items.remove(item) def ConsumeItem(self, index): if len(self.items) > index: item = self.items[index] if isinstance(item, Food): self.hunger -= min(self.hunger, item.hunger) self.thirst -= min(self.thirst, item.thirst) self.RemoveItem(item) def RemoveWeapon(self): if self.weapon is not None: self.items.append(self.weapon) self.weapon = None def RemoveArmor(self): if self.armor is not None: self.items.append(self.armor) self.armor = None def Age(self): self.action_debt -= min(self.action_debt, self.GetSpeed()) self.age += 1 def CheckTargets(self): return False def AppendItem(self, item): self.weight += item.weight self.items.append(item) def RemoveItem(self, item): self.weight -= item.weight self.items.remove(item) def __getstate__(self): self.image = None return self.__dict__ def __setstate__(self, state): self.__dict__.update(state) self.image = load_image(self.image_name, -1) class Npc(Actor): def __init__(self, pos_x, pos_y, image=None): Actor.__init__(self, pos_x, pos_y, image) self.target = None self.target_plot = None self.target_actor = None self.moved = False def CheckCurrentTarget(self, board): self.target = None # if there's a target, clear if no longer visible or dead if self.target_actor is not None: # we have a target actor if self.target_actor.IsDead(): # dead, clear it self.target_actor = None else: # check if it's visible vis = self.GetVisibility() dist = ((self.pos_x - self.target_actor.pos_x) ** 2) + ((self.pos_y - self.target_actor.pos_y) ** 2) route, found = get_bresen(board.vis_board, self.pos_x, self.pos_y, self.target_actor.pos_x, self.target_actor.pos_y) if len(route) > 1: if dist < vis ** 2 and found: # target in sight, lock on self.target_plot = route[1] self.target = self.target_plot else: # target out of sight, we'll follow it to its last position self.target_actor = None self.target_plot = route[len(route) - 1] self.target = self.target_plot elif self.target_plot is not None: if self.target_plot[0] == self.pos_x and self.target_plot[1] == self.pos_y: # we reached our target plot; give up self.target_plot = None self.target_actor = None else: # follow that plot route, found = get_bresen(board.vis_board, self.pos_x, self.pos_y, self.target_plot[0], self.target_plot[1]) if len(route) > 1: self.target = route[1] else: self.target_plot = None self.target_actor = None def TrimMove(self, xMove, yMove, board): if board.CanMove(self, self.pos_x + xMove, self.pos_y) and board.CanMove(self, self.pos_x, self.pos_y + yMove): # both are valid; choose randomly if random.randint(0, 1) == 0: xMove = 0 else: yMove = 0 elif board.CanMove(self, self.pos_x + xMove, self.pos_y): yMove = 0 elif board.CanMove(self, self.pos_x, self.pos_y + yMove): xMove = 0 else: xMove = 0 yMove = 0 return xMove, yMove def TryMove(self, xMove, yMove, board): self.moved = False # can't move diagonally, we'll have to pick one if xMove != 0 and yMove != 0: xMove, yMove = self.TrimMove(xMove, yMove, board) can_move = board.CanMove(self, self.pos_x + xMove, self.pos_y + yMove) if not can_move: can_move = board.TryOpen(self.pos_x + xMove, self.pos_y + yMove) if (xMove != 0 or yMove != 0) and can_move: self.pos_x += xMove self.pos_y += yMove self.moved = True if not self.moved: move = random.randrange(-1, 2, 2) if random.randint(0, 1) == 0: xMove = move yMove = 0 else: xMove = 0 yMove = move if board.CanMove(self, self.pos_x + xMove, self.pos_y + yMove): self.pos_x += xMove self.pos_y += yMove self.moved = True if self.moved: self.action_debt += self.action_debt_move def IsAggressive(self, target_actor): return abs(self.pos_x - target_actor.pos_x) + abs(self.pos_y - target_actor.pos_y) <= 1 def DoBarricades(self, board): return False class Human(Actor): def __init__(self, pos_x, pos_y, image=None): Actor.__init__(self, pos_x, pos_y, image) self.strength = 5 self.accuracy = 5 self.speed = 5 self.concentration = 5 self.visibility = 12 self.infected = False self.hostile = False def GetName(self): return 'Human' def Age(self): Actor.Age(self) self.hunger += 0.05 self.thirst += 0.05 def Rest(self): self.fatigue -= min(1, self.fatigue) def CheckZombie(self, actor, dist, board): hit = False zombie_vis = actor.GetVisibility() if dist < zombie_vis ** 2: # we're in its range if actor.target_actor is not None: # it has a target, let's check it zombie_target_distance = ((actor.target_actor.pos_x - actor.pos_x) ** 2) + ((actor.target_actor.pos_y - actor.pos_y) ** 2) if zombie_target_distance > dist: # we're closer hit = True else: # we're it hit = True if hit: route, found = get_bresen(board.vis_board, self.pos_x, self.pos_y, actor.pos_x, actor.pos_y) if found: actor.target_actor = self class Survivor(Npc, Human): def __init__(self, pos_x, pos_y, image=None): if image == None: """ rand = random.randrange(0, 4, 1) if rand == 0: image = 'human_tourist.png' elif rand == 1: image = 'human_cheerleader.png' elif rand == 2: image = 'human_baby.png' elif rand == 3: image = 'human_dog.png' elif rand == 4: image = 'human_teacher.png' """ image = 'survivor.png' Npc.__init__(self, pos_x, pos_y, image) Human.__init__(self, pos_x, pos_y, image) self.mission_wander = 0 self.mission_follow = 1 self.mission_kill = 2 self.mission_defend = 3 self.mission = self.mission_wander def GetName(self): return 'Survivor' def CheckTargets(self, board): vis = self.GetVisibility() self.CheckCurrentTarget(board) if self.target is None: # find nearest zombie or the player target_distance = 0 for actor in board.actors: dist = ((self.pos_x - actor.pos_x) ** 2) + ((self.pos_y - actor.pos_y) ** 2) if dist < vis ** 2: is_target = False if isinstance(actor, Zombie): is_target = True self.CheckZombie(actor, dist, board) elif isinstance(actor, Player): is_target = True if is_target and target_distance == 0 or dist < target_distance: route, found = get_bresen(board.vis_board, self.pos_x, self.pos_y, actor.pos_x, actor.pos_y) if found: self.target_actor = actor self.target_plot = route[1] self.target = self.target_plot target_distance = dist def Move(self, board): xMove = 0 yMove = 0 if self.target is not None: if self.target[0] < self.pos_x: xMove -= 1 elif self.target[0] > self.pos_x: xMove += 1 if self.target[1] < self.pos_y: yMove -= 1 elif self.target[1] > self.pos_y: yMove += 1 if isinstance(self.target_actor, Zombie): xMove *= -1 yMove *= -1 self.TryMove(xMove, yMove, board) if self.moved: self.fatigue += 0.1 def IsAggressive(self, target_actor): if Npc.IsAggressive(self, target_actor): if isinstance(target_actor, Zombie): return True elif isinstance(target_actor, Human): return self.hostile class Zombie(Npc): def __init__(self, pos_x, pos_y, image='zombie.png'): Npc.__init__(self, pos_x, pos_y, image) self.strength = 5 self.accuracy = 3 self.speed = 4 self.concentration = 3 self.visibility = 10 self.action_debt = 0 def GetName(self): return 'Zombie' def CheckTargets(self, board): # check if target is still around self.CheckCurrentTarget(board) def Move(self, board): xMove = 0 yMove = 0 if self.target is not None: if self.target[0] < self.pos_x: xMove = -1 elif self.target[0] > self.pos_x: xMove = 1 if self.target[1] < self.pos_y: yMove = -1 elif self.target[1] > self.pos_y: yMove = 1 self.TryMove(xMove, yMove, board) def IsAggressive(self, target_actor): return isinstance(target_actor, Human) and Npc.IsAggressive(self, target_actor) def DoBarricades(self, board): did_barricades = False for barricade in board.barricades: if abs(self.pos_x - barricade[0]) + abs(self.pos_y - barricade[1]) == 1: board.Unbarricade(barricade) did_barricades = True break return did_barricades class Player(Human): def __init__(self, pos_x, pos_y, board_id, image='player.png'): Human.__init__(self, pos_x, pos_y, image) self.strength = 10 self.accuracy = 10 self.speed = 10 self.concentration = 10 self.visibility = 10 self.board_id = board_id def GetName(self): return 'Player' def Move(self, xMove, yMove, board): can_move = board.CanMove(self, self.pos_x + xMove, self.pos_y + yMove) if not can_move: can_move = board.TryOpen(self.pos_x + xMove, self.pos_y + yMove) if can_move: self.pos_x += xMove self.pos_y += yMove self.fatigue += 0.1 return True else: return False def CheckTargets(self, board): vis = self.GetVisibility() # find nearby zombies for actor in board.actors: if isinstance(actor, Zombie): dist = ((self.pos_x - actor.pos_x) ** 2) + ((self.pos_y - actor.pos_y) ** 2) self.CheckZombie(actor, dist, board) def GetTarget(self, key, actors): xMove = 0 yMove = 0 if (key == K_RIGHT): xMove = 1 elif (key == K_LEFT): xMove = -1 elif (key == K_UP): yMove = -1 elif (key == K_DOWN): yMove = 1 # find a target target_actor = None target_distance = 0 for actor in actors: if actor.pos_x - self.pos_x == xMove and actor.pos_y - self.pos_y == yMove: target_actor = actor break return target_actor