------------------ application.py ------------------ import pyui, pygame from server import Server from client import Client from pyui.widgets import * from hoop import world, engine import environment, control, constants class Application: """ Creates application with the specified display width and height """ def __init__(self, width, height): print 'Initializing Application' self.defaultIp = constants.defaultIp self.launcher = LobbyFrame(self, width, height) self.displayWidth = width self.displayHeight = height self.width = constants.worldWidth #world width self.height = constants.worldHeight #world height self.world = world.World(self.width, self.height) control.world = self.world #self.controller = control.Controller() self.running = 0 self.backMethod = self.emptyBackMethod engine.initialize(constants.displayWidth, constants.displayHeight) pyui.desktop.getRenderer().setBackMethod(self.render) self.environment = environment.Environment(self.world) environment.Env = self.environment #Perhaps use datamanager.DataManager() ? """ Callback-method for the pyui renderer. Draws borders of the world and invokes the hoop engine rendering routines """ def render(self): engine.clear() width = self.width height = self.height x = environment.Env.localPlayer.avatar.posX y = environment.Env.localPlayer.avatar.posY color = pyui.colors.yellow engine.setView(x,y) #Coordinates of world corners ux = width-x + self.displayWidth/2 lx = -x + self.displayWidth/2 uy = height-y + self.displayHeight/2 ly = -y + self.displayHeight/2 #Might the offset in the above definitions actually express an error #in hoop.engine? It should not be necessary to perform that translation engine.drawLine(lx,ly,ux,ly,color) engine.drawLine(ux,ly,ux,uy,color) engine.drawLine(ux,uy,lx,uy,color) engine.drawLine(lx,uy,lx,ly,color) engine.render() engine.drawText('You : %d' %environment.Env.localPlayer.fragCount, (100,self.displayHeight-30), pyui.colors.yellow) engine.drawText('Enemy : %d' %environment.Env.remotePlayer.fragCount, (self.displayWidth-200,self.displayHeight-30), pyui.colors.red) """ The main loop of PySoldier. Repeats executing until self.running is set to 0. Each repeat will update graphics by means of pyui and hoop, plus the game logic as controlled in hoop. Further, a custom callbackmethod is called, which will handle network events. In the current implementation, the callback method is selected depending on whether the game runs in client or server mode. """ def mainLoop(self): print "main loop started" self.running = 1 frameCount = 0 lastTime = pyui.readTimer() currentTime = lastTime dt = 0 while self.running: pyui.draw() if pyui.update(): currentTime = pyui.readTimer() dt = currentTime-lastTime lastTime = currentTime #self.controller.update() if self.world.update(dt) == 0: self.running = 0 self.backMethod() else: self.running = 0 frameCount += 1 def update(self): self.networkManager.update() def setBackMethod(self, backMethod): self.backMethod = backMethod #def updateControls(self): # self.controller.update() # players.localPlayer.update() # players.remotePlayer.update() """ The main loop callback defaults to this method, which does nothing """ def emptyBackMethod(self): pass """ This frame contains buttons, allowing the user to create or join a server among other things. """ class LobbyFrame(Frame): """ Creates a lobby frame of specified width and height, for controlling the specified application """ def __init__(self, application, width, height): Frame.__init__(self, 10, 10, width/2, height/2, "Lobby") self.createButton = pyui.widgets.Button(" Create Game ", self.onCreate) self.joinButton = pyui.widgets.Button(" Join Game ", self.onJoin) self.exitButton = pyui.widgets.Button(" Exit ", self.onExit) self.networkManager = 'none' self.ipBox = pyui.widgets.Edit(application.defaultIp, 12, self.onJoin) #constants.defaultPlayerName <-- write player name too somewhere self.setLayout(pyui.layouts.TableLayoutManager(2,6)) self.addChild(self.createButton, (0,0,1,1)) self.addChild(self.joinButton, (0,2,1,1)) self.addChild(self.exitButton, (0,4,1,1)) self.addChild(self.ipBox, (1,0,1,1)) self.pack() self.application = application constants.SOUND_THEME.play() """ Create a server """ def onCreate(self, widget): print "Creating Server" constants.MODE = constants.MODE_SERVER server = Server(self.application.world, constants.clientToServerPort, constants.serverToClientPort) self.destroy() self.application.setBackMethod(server.update) """ Listen for network input """ def onJoin(self, widget): print "Creating Client" constants.MODE = constants.MODE_CLIENT client = Client(self.ipBox.text, self.application.world, constants.serverToClientPort, constants.clientToServerPort) self.destroy() self.application.setBackMethod(client.update) """ Quits PySoldier """ def onExit(self, widget): self.application.running = 0 ------------------ clients.py ------------------ from twisted.internet.protocol import DatagramProtocol from twisted.internet import reactor import pygame import constants import xdrlib import pyui import environment """ Class used by the game when running client mode """ class Client(DatagramProtocol): def __init__(self, ip, world, readPort, writePort): self.ip = ip self.world = world self.readPort = readPort self.writePort = writePort self.lastupdate = 0 self.trans = reactor.listenUDP(readPort, self) print "client loaded" print "client listening at port "+str(readPort) print "client will write from port "+str(writePort) """ Makes sure the package received comes from the wanted IP - the address given on joining a game """ def datagramReceived(self, datagram, address): (ip, port) = address if ip == self.ip: self.parseDatagram(datagram) """ Unpacks data sendt from server and unpacks it in the same order as sent from server """ def parseDatagram(self, datagram): unpacker = xdrlib.Unpacker(datagram) self.unpackSoldier(environment.Env.remotePlayer.avatar, unpacker) self.unpackSoldier(environment.Env.localPlayer.avatar, unpacker) env = environment.Env env.remotePlayer.controller.fire = unpacker.unpack_int() env.remotePlayer.controller.throw = unpacker.unpack_int() remoteFrags = unpacker.unpack_int() localFrags = unpacker.unpack_int() if remoteFrags > env.remotePlayer.fragCount: print 'local player died' env.localPlayer.avatar.alive = 0 env.remotePlayer.fragCount = remoteFrags if localFrags > env.localPlayer.fragCount: print 'remote player died' env.remotePlayer.avatar.alive = 0 env.localPlayer.fragCount = localFrags """ Specifically unpacks all data belonging to the soldier class """ def unpackSoldier(self, soldier, unpacker): soldier.posX = unpacker.unpack_float() soldier.posY = unpacker.unpack_float() soldier.gun.facing = unpacker.unpack_int() soldier.velocityX = unpacker.unpack_float() soldier.velocityY = unpacker.unpack_float() soldier.health = unpacker.unpack_int() if soldier.health < 0: #Dying handled by reading frag counts soldier.health = 1 """ Method is used by the client to send updates to the server. All mouse and keyboard input is packed and finall writen to the server """ def writeUpdate(self): pressed = pygame.key.get_pressed() up = pressed[pyui.locals.K_UP] down = pressed[pyui.locals.K_DOWN] left = pressed[pyui.locals.K_LEFT] right = pressed[pyui.locals.K_RIGHT] (fire, b2, throw) = pygame.mouse.get_pressed() facing = environment.Env.localPlayer.avatar.gun.facing packer = xdrlib.Packer() packer.pack_int(up) packer.pack_int(down) packer.pack_int(left) packer.pack_int(right) packer.pack_int(fire) packer.pack_int(throw) packer.pack_int(facing) self.transport.write(packer.get_buffer(), (self.ip, self.writePort)) """ Polls the UDP network for any received datagrams on each iteration. If the time since last game update sent to the server is trespassed an update will be send. """ def update(self): currentTime = pyui.readTimer() reactor.runUntilCurrent() reactor.doSelect(0) environment.Env.localPlayer.controller.processPeripheralsClient() environment.Env.localPlayer.update() environment.Env.remotePlayer.update() if currentTime - self.lastupdate > 0.05: self.writeUpdate() self.lastupdate = currentTime ------------------ constants.py ------------------ import pygame """ This file contains basic constants used in PySoldier """ applicationName = 'PySoldier' version = 'v0.01' authors = 'Michael Francker Christensen & Ask Hjorth Larsen' appInfo = applicationName + ' ' + version + ' by '+authors displayWidth = 800 displayHeight = 600 worldWidth = 1200 worldHeight = 600 clientToServerPort = 8004 serverToClientPort = 8005 sendToAllIP = '224.0.0.1' defaultIp = '127.0.0.1' gravity = 9.82*40 MODE_UNDETERMINED = -1 MODE_SERVER = 0 MODE_CLIENT = 1 MODE = MODE_UNDETERMINED pygame.mixer.init() SOUND_MEIN_LEBEN = pygame.mixer.Sound("sound/meinleben.wav") SOUND_SHOOT = pygame.mixer.Sound("sound/luger.wav") SOUND_THEME = pygame.mixer.Sound("sound/theme.wav") SOUND_EXPLOSION = pygame.mixer.Sound("sound/explosion.wav") ------------------ control.py ------------------ import math import pygame import pyui from hoop import engine from sprites import Bullet world = 'none' """ Keeps track of which keys are presently pressed for a specific soldier. """ class Controller: def __init__(self, soldier): self.soldier = soldier self.up = 0 self.down = 0 self.left = 0 self.right = 0 self.fire = 0 self.throw = 0 """ All possible orders are set by this update """ def update(self): if not self.soldier.alive: return if self.up: self.soldier.jump() #self.down not presently used if self.left: self.soldier.moveLeft() if self.right: self.soldier.moveRight() if self.fire: self.soldier.fire() if self.throw: self.soldier.throw() """ Updates keboard input to current use """ def processKeys(self): pressed = pygame.key.get_pressed() self.up = pressed[pyui.locals.K_UP] self.down = pressed[pyui.locals.K_DOWN] self.left = pressed[pyui.locals.K_LEFT] self.right = pressed[pyui.locals.K_RIGHT] """ Update all mouse input to current settings and calculates the angle """ def processMouse(self): #Read mouse cursor position on screen (mxs, mys) = pygame.mouse.get_pos() (button1, button2, button3) = pygame.mouse.get_pressed() #Convert to world coordinates (mx, my) = engine.screenToWorld(mxs, mys) dx = mx - self.soldier.posX dy = my - self.soldier.posY angle = 0 if dx > 0: angle = math.atan(dy/dx) elif dx < 0: angle = math.pi + math.atan(dy/dx) elif dx == 0: if dy > 0: angle = math.pi/2 else: angle = -math.pi/2 self.soldier.gun.facing = angle*180/math.pi self.fire = button1 self.throw = button3 """ Used by server to poll for mouse and keyboard input """ def processPeripheralsServer(self): self.processKeys() self.processMouse() """ Used by client to poll for mouse input """ def processPeripheralsClient(self): self.processMouse() --------------- environment --------------- import pygame from hoop import world, engine from control import Controller import constants, sprites class Player: def __init__(self, name, world): self.name = name self.fragCount = 0 self.avatar = sprites.Soldier(world, self) self.controller = Controller(self.avatar) self.opponent = None def update(self): self.controller.update() class Environment: """ Creates Player objects and spawns soldiers for them in the argument world. Also initializes all terrain in the default PySoldier map. """ def __init__(self, world): print 'Creating Environment' self.world = world self.spawnPoints = [(100,300), (1100,300)] self.nextSpawnPoint = 0 self.localPlayer = Player('John', world) self.remotePlayer = Player('Eric', world) self.localPlayer.opponent = self.remotePlayer self.remotePlayer.opponent = self.localPlayer self.spawn(self.localPlayer.avatar) self.spawn(self.remotePlayer.avatar) terrain1 = [(590,180,1),(610,180,1)] terrain2 = [(100,240,2),(1100,240,2),(450,190,2),(750,190,3),(180,150,2),(960,150,2)] terrain3 = [(450,120,3),(490,120,3),(530,100,3),(530,150,3),(670,150,3), (710,120,3),(770,120,3)] terrain4 = [(340,100,4),(380,100,4),(420,100,4),(860,100,4),(900,100,4),(940,100,4), (340,250,4),(380,250,4),(420,250,4),(860,230,4),(900,230,4),(940,250,4), (600,230,4),(6900,230,4),(510,230,4),(120,180,4),(1080,180,4)] terrain5 = [(30,20,5),(90,30,5),(150,40,5),(210,20,5),(270,20,5), (330,40,5),(390,40,5),(450,30,5),(510,20,5),(570,30,5), (630,30,5),(690,20,5),(750,30,5),(810,40,5),(870,40,5), (930,20,5),(990,20,5),(1050,40,5),(1110,30,5),(1170,20,5)] TotalTerrain = terrain1+terrain2+terrain3+terrain4+terrain5 self.populate(TotalTerrain) """ Takes the totalTerrain list and creates all level objects when launching application. """ def populate(self, points): for p in points: (x,y,dirtType) = p self.world.addToWorld(sprites.Dirt(dirtType), x, y, 0) """ Spawns the 2 soldiers in each end of the world and sets the spawn point to shift between the 2 places for future spawns. """ def spawn(self, soldier): print soldier (x, y) = self.spawnPoints[self.nextSpawnPoint] self.nextSpawnPoint += 1 if self.nextSpawnPoint >= len(self.spawnPoints): self.nextSpawnPoint = 0 soldier.alive = 1 self.world.addToWorld(soldier, x, y, 0) self.world.addToWorld(soldier.gun, x, y, 0) """ When a soldier dies his death is recorded by the angel. This method applies the new score. """ def reportDeath(self, soldier): soldier.player.opponent.fragCount += 1 Env = None --------------- main.py --------------- import pyui import application import constants """ Launches PySoldier """ def run(): print "Starting PySoldier" width = constants.displayWidth height = constants.displayHeight print "Initializing Pyui" pyui.init(width, height, 'p3d') print "Launching Application" app = application.Application(width, height) app.mainLoop() print "Quitting Pyui" pyui.quit() if __name__ == "__main__": run() --------------- server.py --------------- from twisted.internet import protocol, reactor from twisted.internet import reactor import xdrlib import pyui import constants import environment """ This class represents a PySoldier UDP server. Will listen for UDP packets at the specified port and broadcast datagrams on another. """ class Server(protocol.DatagramProtocol): """ Creates a Server using the specified port numbers for reading and writing, respectively. """ def __init__(self, world, readPort, writePort): self.world = world self.readPort = readPort self.writePort = writePort self.sendToAllIP = constants.sendToAllIP self.updateTime = pyui.readTimer() self.lastupdate = 0 self.clients = [] #client IP addresses reactor.listenMulticast(self.readPort, self) print "server loaded" print "server listening at port "+str(readPort) print "server will write from port "+str(writePort) """ Initializes this Server """ def startProtocol(self): print "starting protocol" self.transport.joinGroup(self.sendToAllIP) """ Presently improperly implemented. Read and react to network input """ def datagramReceived(self, datagram, address): #make sure address is registered if not address[0] in self.clients: if len(self.clients) < 1: """ The above if-statement ensures that only one client can join. That exact statement should be removed when more players are allowed to join """ self.clients.append(address[0]) unpacker = xdrlib.Unpacker(datagram) up = unpacker.unpack_int() down = unpacker.unpack_int() left = unpacker.unpack_int() right = unpacker.unpack_int() fire = unpacker.unpack_int() throw = unpacker.unpack_int() facing = unpacker.unpack_int() control = environment.Env.remotePlayer.controller control.up = up control.down = down control.left = left control.right = right control.fire = fire control.throw = throw environment.Env.remotePlayer.avatar.gun.facing = facing """ Helper method for writing the contents of a packer """ def writePacker(self, packer, address): buffer = packer.get_buffer() self.transport.write(buffer, address) """ Makes the packer and initiates different information to be sendt in the correct numbering """ def writeUpdate(self, address): packer = xdrlib.Packer() self.packSoldier(packer, environment.Env.localPlayer.avatar) self.packSoldier(packer, environment.Env.remotePlayer.avatar) packer.pack_int(environment.Env.localPlayer.controller.fire) packer.pack_int(environment.Env.localPlayer.controller.throw) packer.pack_int(environment.Env.localPlayer.fragCount) packer.pack_int(environment.Env.remotePlayer.fragCount) self.writePacker(packer, address) """ Packs all info from soldier """ def packSoldier(self, packer, soldier): packer.pack_float(soldier.posX) packer.pack_float(soldier.posY) packer.pack_int(soldier.gun.facing) packer.pack_float(soldier.velocityX) packer.pack_float(soldier.velocityY) packer.pack_int(soldier.health) """ Perform all instructions received since last update call. """ def update(self): currentTime = pyui.readTimer() reactor.runUntilCurrent() reactor.doSelect(0) environment.Env.localPlayer.controller.processPeripheralsServer() environment.Env.localPlayer.update() environment.Env.remotePlayer.update() if currentTime - self.lastupdate > 0.05: for clientIp in self.clients: self.writeUpdate((clientIp,self.writePort)) self.lastupdate = currentTime --------------- sim.py --------------- from hoop import SimObject class ImprSimObject(SimObject): def update(self, interval, world): """update an object's physical state for an interval. """ if self.threshold and self.uDelay: self.uTimer += interval if self.uTimer < self.uDelay: return else: interval = self.uTimer self.uTimer -= self.uDelay radians = toRadians(self.facing) if self.accel: dx = math.cos(radians) * self.accel * interval dy = math.sin(radians) * self.accel * interval print "dx:", dx self.velocityX += dx self.velocityY += dy newPosX = self.posX + (self.velocityX * interval) newPosY = self.posY + (self.velocityY * interval) if self.turnRate: newFacing = self.facing + self.turnRate*interval newFacing = newFacing % 360 else: newFacing = self.facing moveSuccessful = self.tryMove(world, newPosX, newPosY, self.facing) if not moveSuccessful: moveSuccessful = self.tryMove(world, newPosX, 0, self.facing) if not moveSuccessful: moveSuccessful = self.tryMove(world, 0, newPosY, self.facing) if self.life: self.life -= interval if self.life <= 0: self.alive = 0 # calculate the variable delay if self.threshold: value = max( abs(self.velocityX), abs(self.velocityY), abs(self.turnRate) ) if value < self.threshold: self.uDelay = 1.0 - (value / self.threshold) else: self.uDelay = 0 return self.alive def tryMove(self, world, newPosX, newPosY, newFacing): if world.canMove(self, newPosX, newPosY,newFacing): self.posX = newPosX self.posY = newPosY self.facing = newFacing world.move(self, newPosX, newPosY, newFacing) self.graphicsObject.setState(newPosX, newPosY, newFacing) return 1 else: return 0 --------------- sprites.py --------------- import math, pyui, pygame from hoop.sim import SimObject from imprSim import ImprSimObject import constants, util, environment """ This class specifies the properties of Soldier objects """ class SoldierCategory: def __init__(self): self.mobile = 1 self.collide = 1 self.threshold = 0 self.life = 0 self.image = "spriteSrc/captain_jack.png" self.source = "spriteSrc/SourceSoldier.py" self.frictionConstantGround = 600 self.frictionConstantAir = 10 self.airControl = 0.4 self.motorForce = 120000 self.maxSpeed = self.motorForce/self.frictionConstantGround self.jumpSpeed = 210 self.health = 250 """ This class represents a soldier """ class Soldier(ImprSimObject): """ Creates a Soldier. """ def __init__(self, world, player): print 'Creating soldier' ImprSimObject.__init__(self, SoldierCategory()) self.gun = Gun(self) self.player = player self.world = world self.xForce = 0 self.yForce = 0 self.isGrounded = 0 self.testCollide = 0 self.mass = 80 self.health = self.category.health self.lastReload = 0 self.lastThrow = 0 self.removeCallback = self.onDeath self.radius = util.hypot(self.width, self.height) """ Updates this soldier. Will adjust velocity components according to the forces working on this soldier. """ def update(self, interval, world): if self.isGrounded: self.xForce -= self.category.frictionConstantGround*self.velocityX else: self.xForce -= self.category.frictionConstantAir*self.velocityX self.yForce -= self.category.frictionConstantAir*self.velocityY self.yForce -= constants.gravity*self.mass self.velocityX += self.xForce*interval/self.mass self.velocityY += self.yForce*interval/self.mass self.xForce = 0 self.yForce = 0 self.isAlive = ImprSimObject.update(self, interval, world) self.isGrounded = 0 self.testCollide = 1 world.grid.checkCollide(self, self.posX, self.posY-1, self.facing) #If this check invokes hit, this soldier will now be on the ground self.testCollide = 0 if (self.health < 0) and (constants.MODE == constants.MODE_SERVER): self.alive = 0 self.health = self.category.health return self.alive """ Updates a soldiers left going xForce based on the current motorForce """ def moveLeft(self): if self.isGrounded: self.xForce -= self.category.motorForce else: self.xForce -= self.category.motorForce * self.airControl() """ Updates a soldiers right going xForce based on the current motorForce """ def moveRight(self): if self.isGrounded: self.xForce += self.category.motorForce else: self.xForce += self.category.motorForce * self.airControl() """ When a player has been killed this method updates all relevent game info and plays the al importent "sound of death" """ def onDeath(self, world): print 'mein leben!' constants.SOUND_MEIN_LEBEN.play() self.health = self.category.health self.world.removeFromWorld(self.gun) self.world.addToWorld(Angel(self), self.posX, self.posY, 0) environment.Env.reportDeath(self) """ Used for adding bullets to the world based on a given reloadtimer. """ def fire(self): currentTime = pyui.readTimer() if currentTime - self.lastReload > self.gun.category.reloadTime: constants.SOUND_SHOOT.play() bullet = Bullet(self.gun.facing) self.world.addToWorld( bullet, self.posX+0.54*(self.radius+bullet.radius)*math.cos(self.gun.facing*math.pi/180), self.posY+0.54*(self.radius+bullet.radius)*math.sin(self.gun.facing*math.pi/180), 0) bullet.init() self.lastReload = currentTime """ The throw method generates a grenade object and adds it to the world. Also resets this Soldier's grenate reload timer. """ def throw(self): currentTime = pyui.readTimer() if currentTime - self.lastThrow > self.gun.category.throwTime: grenade = Grenade(self.gun.facing) self.world.addToWorld( grenade, self.posX+0.54*(self.radius+grenade.radius)*math.cos(self.gun.facing*math.pi/180), self.posY+0.54*(self.radius+grenade.radius)*math.sin(self.gun.facing*math.pi/180), 0) grenade.init() self.lastThrow = currentTime """ When a player is not grounded his onGround flag will be false. This enables the airControl degrading a players control """ def airControl(self): return self.category.airControl*(self.category.maxSpeed - abs(self.velocityX))/self.category.maxSpeed """ If this Soldier is located on the ground, this method will set the vertical velocity of this Soldier to jumpSpeed. """ def jump(self): if self.isGrounded: self.isGrounded = 0 self.velocityY = self.category.jumpSpeed """ This methods is called when hoop detects a collision involving this object. If this Soldier's testCollide flag is set to true, this method only updates whether or not this Soldier is situated on the ground. If thos Soldier collides with stationary object such as the edges of the world or a dirt type, either x-, y- or both components of the velocity is set to zero, depending on which directions the collision occurs in. """ def hit(self, other, newPosX, newPosY, newFacing): if self.testCollide: if isinstance(other, SimObject): if not other.category.mobile: #If it is immobile, we can stand on it self.isGrounded = 1 return 0 #We don't really collide elif other == 4: self.velocityY = 0 return 0 elif other == 3 or other == 1: self.velocityX = 0 return 0 elif isinstance(other, Dirt): (hitX, hitY) = self.findHitDirections(other, newPosX, newPosY, newFacing) #print 'dirt ',other.posX,' ',(self.hitX, self.hitY) if hitX: self.velocityX = 0 if hitY: self.velocityY = 0 return 1 """ This class contains static data for the Gun class """ class GunCategory: def __init__(self): self.mobile = 1 self.collide = 0 self.threshold = 0 self.life = 0 self.image = "spriteSrc/gun.png" self.source = "spriteSrc/SourceGun.py" self.reloadTime = 0.2 self.throwTime = 3 """ This object represents a gun. It will automatically follow the Soldier with whom it is associated. """ class Gun(SimObject): """ Creates a gun associated with argument Soldier """ def __init__(self, soldier): SimObject.__init__(self, GunCategory()) self.soldier = soldier """ Updates this Gun object by setting its (x,y) location and repainting. """ def update(self, interval, world): self.posX = self.soldier.posX self.posY = self.soldier.posY self.graphicsObject.setState(self.posX, self.posY, self.facing) return self.alive """ This class contains static data for the Bullet class """ class BulletCategory: def __init__(self): self.mobile = 1 self.collide = 1 self.threshold = 0 self.life = 0 self.image = "spriteSrc/normBullet.png" self.source = "spriteSrc/SourceBullet.py" self.frictionConstant = 0.2 self.mass = 2 """ This class represents a bullet, as may be fired by a soldier. """ class Bullet(SimObject): """ Initializes a Bullet to have a certain facing. Warning: adding this Bullet to the world will reset the facing. Use the init() method after adding to the world will properly set the facing and velocities of this Bullet. """ def __init__(self, facing): SimObject.__init__(self, BulletCategory()) self.speed = 700 self.angle = facing self.alive = 1 self.radius = util.hypot(self.width, self.height) """ Sets facing and velocity components. This is used to circumvent the hoop world overriding the settings when adding this Bullet to the world. """ def init(self): self.facing = self.angle self.velocityX = self.speed*math.cos(self.facing*math.pi/180) self.velocityY = self.speed*math.sin(self.facing*math.pi/180) """ Returns the amount of damage this bullet will deal if it hits now. Damage is proportional to mass times velocity squared. """ def damage(self): return self.category.mass * util.sumSquares(self.velocityX/100, self.velocityY/100) def hit(self, other, newPosX, newPosY, newFacing): if not isinstance(other, Bullet): self.alive = 0 if isinstance(other, Soldier): damage = self.damage() print 'Soldier hit for ',damage,' damage' other.health -= damage other.velocityX += self.velocityX*self.category.mass/other.mass other.velocityY += self.velocityY*self.category.mass/other.mass """ Updates this Bullet by applying gravitational forces. """ def update(self, interval, world): self.velocityX -= self.velocityX*self.category.frictionConstant*interval self.velocityY -= (self.velocityY*self.category.frictionConstant + constants.gravity)*interval return SimObject.update(self, interval, world) """ This class contains static data for the Grenade class """ class GrenadeCategory: def __init__(self): self.mobile = 1 self.collide = 1 self.threshold = 0 self.life = 1.8 self.image = "spriteSrc/grenade.png" self.source = "spriteSrc/SourceGrenade.py" self.frictionConstant = 0.2 self.mass = 8 """ Represents a Grenade which may be thrown by Soldiers. """ class Grenade(Bullet): def __init__(self, facing): SimObject.__init__(self, GrenadeCategory()) self.angle = facing self.radius = util.hypot(self.width, self.height) self.speed = 280 self.turnRate = 180 self.removeCallback = self.explode self.radius = util.hypot(self.width, self.height) def hit(self, other, newPosX, newPosY, newFacing): (hitX, hitY) = self.findHitDirections(other, newPosX, newPosY, newFacing) if hitX: self.velocityX = -self.velocityX*0.7 if hitY: self.velocityY = -self.velocityY*0.7 return 1 def explode(self, world): constants.SOUND_EXPLOSION.play() exp = Explosion() world.addToWorld(exp, self.posX, self.posY, 0) class ExplosionCategory: def __init__(self): self.mobile = 1 self.collide = 1 self.life = 0.8 self.threshold = 0 self.image = "spriteSrc/exp5.png" self.source = "spriteSrc/SourceExplosion.py" self.baseDamage = 550 self.rangeDamageFactor = 0.1 self.rangeBlowFactor = 10 class Explosion(SimObject): def __init__(self): SimObject.__init__(self, ExplosionCategory()) def damage(self, other): dx = other.posX-self.posX dy = other.posY-self.posY dist = util.hypot(dx, dy) other.velocityX += dx*self.category.rangeBlowFactor other.velocityY += dy*self.category.rangeBlowFactor return self.category.baseDamage/(1+dist*self.category.rangeDamageFactor) def hit(self, other, newPosX, newPosY, newFacing): if isinstance(other, Soldier): damage = self.damage(other) print 'Explosion damages for ',damage,' points' other.health -= damage return 0 class dirt1Category: def __init__(self): self.mobile = 0 self.collide = 1 self.life = 0 self.threshold = 0 self.image = "spriteSrc/texture1.png" self.source = "spriteSrc/SourceTexture1.py" class dirt2Category: def __init__(self): self.mobile = 0 self.collide = 1 self.life = 0 self.threshold = 0 self.image = "spriteSrc/texture2.png" self.source = "spriteSrc/SourceTexture2.py" class dirt3Category: def __init__(self): self.mobile = 0 self.collide = 1 self.life = 0 self.threshold = 0 self.image = "spriteSrc/texture3.png" self.source = "spriteSrc/SourceTexture3.py" class dirt4Category: def __init__(self): self.mobile = 0 self.collide = 1 self.life = 0 self.threshold = 0 self.image = "spriteSrc/texture4.png" self.source = "spriteSrc/SourceTexture4.py" class dirt5Category: def __init__(self): self.mobile = 0 self.collide = 1 self.life = 0 self.threshold = 0 self.image = "spriteSrc/texture5.png" self.source = "spriteSrc/SourceTexture5.py" class Dirt(SimObject): def __init__(self, dirtType): if dirtType == 1: SimObject.__init__(self, dirt1Category()) elif dirtType == 2: SimObject.__init__(self, dirt2Category()) elif dirtType == 3: SimObject.__init__(self, dirt3Category()) elif dirtType == 4: SimObject.__init__(self, dirt4Category()) elif dirtType == 5: SimObject.__init__(self, dirt5Category()) def hit(self, other, newPosX, newPosY, newFacing): return 0 class AngelCategory: def __init__(self): self.mobile = 1 self.collide = 0 self.life = 4 self.threshold = 0 self.image = "spriteSrc/angel.png" self.source = "spriteSrc/SourceAngel.py" class Angel(SimObject): def __init__(self, soldier): SimObject.__init__(self,AngelCategory()) self.soldier = soldier def update(self, interval, world): self.velocityY += constants.gravity*interval*0.3 SimObject.update(self,interval,world) if not self.alive: environment.Env.spawn(self.soldier) print 'spawn',(self.soldier.posX, self.soldier.posY) return self.alive --------------- util.py --------------- import math def hypot(dx, dy): return math.sqrt(dx*dx+dy*dy) def sumSquares(dx, dy): return dx*dx+dy*dy