Example #1
0
class bullet():
    #tuple pos, tuple target, float damage, float speed, float range, float accuracy -> bullet
    def __init__(self, sender, pos, dir, damage, speed, range, accuracy=1):
        #bullets have a speed, a model, and a damage. Both should be parameters
        #range as well
        self.start = pos
        self.sender = sender
        self.accuracy = accuracy
        self.direction = dir.normalized()
        self.damage = damage
        self.tasks = []
        self.speed = speed
        self.range = range
        self.model = base.loader.loadModel('models/bullet')
        self.model.reparentTo(base.render)
        self.model.setPos(pos)

        #self.target=target
        #create collision sphere
        #TODO account for larger bullet sizes
        cs = CollisionSphere(0, 0, 0, 1)
        self.mainCol = CollisionNode('cNode')
        #set up circular reference so collision volume knows parent
        self.mainCol.setPythonTag('owner', self)
        self.mainCol.setIntoCollideMask(
            CollideMask.bit(1)
        )  # send objects with intoCollideMask bit 1. Relates to most entities
        self.cNode = self.model.attachNewNode(self.mainCol)
        self.cNode.node().addSolid(cs)
        #create collider handler, if one doesn't already exist
        try:
            # attempt to add to global collision traverser
            base.cTrav.addCollider(self.cNode, base.bulletCollider)
        except AttributeError:
            base.bulletCollider = CollisionHandlerEvent()
            # create event called 'bulletCollision' on hit
            base.bulletCollider.addInPattern('bulletCollision')
        finally:
            # retry
            base.cTrav.addCollider(self.cNode, base.bulletCollider)

        #rotate model such that it follows the passed direction argument vector
        self.model.setHpr(helper.vectorToHPR(dir))

        #assign direction based on rotation
        #THIS TOOK WAY TOO MUCH EFFORT TO FIGURE OUT
        #normVec here is the model nodepath taking the passed argument vector (dir)
        #belonging to another node (base.render)
        #and adjusting it to its local coordinate space - what would this vector look like from
        #self.model's perspective. Then it is normalized and assigned.
        normVec = self.model.getRelativeVector(base.render, dir)
        normVec.normalize()
        self.direction = normVec

        #normVec=base.render.getRelativeVector(self.model,Vec3(0,1,0))
        #print(self.model.getHpr())
        #print(normVec)

        #start task to move forward at speed
        self.tasks.append(taskMgr.add(self.accelerate, "bulletAccelerateTask"))

    #task: accelerate
    def accelerate(self, task):
        """
        Task moves bullet forward until it hits an object or range is met
        """
        #range is decremented each tick
        #check to make sure not 0
        if self.range <= 0:
            #if range has ran out kill task and this object
            self.delete()
            return task.done

        #otherwise proceed, move object and decrement range
        dt = globalClock.getDt()

        #distVec=min((self.start-self.target),(self.target-self.start))
        #distVec=distVec.normalized()
        #print(distVec)
        #print(self.direction)
        #take normalized direction vector and apply to transform
        self.model.setFluidX(self.model, self.direction[0] * self.speed * dt)
        self.model.setFluidY(self.model, self.direction[1] * self.speed * dt)
        self.model.setFluidZ(self.model, self.direction[2] * self.speed * dt)
        self.range -= (self.speed * dt)
        return task.cont

    #class deconstructor
    def delete(self):
        self.model.hide()
        #clear tasks
        for task in self.tasks:
            taskMgr.remove(task)
        #clear collision
        self.mainCol.clearPythonTag("owner")
        #remove object
        self.model.removeNode()
Example #2
0
class entity(Actor):
    '''
    Base class for any characters
    Contains methods for movement, collision, model loading
    '''

    #procedure class constructor
    # NodePath model, ShowBase caller, tuple Pos -> class construction
    def __init__(self, model: str, base: ShowBase, pos: tuple, physics=True):
        '''
        constructor for entity. attaches model to calling instance renderer
        also stores size definitions and creates basic collision object
        '''
        Actor.__init__(self)
        DEFAULT_HEALTH = 50

        #reference base for later
        self.base = base

        # set health
        self.health = DEFAULT_HEALTH
        self.damagedSound = base.loader.loadSfx("sounds/oof.ogg")

        # speed
        self.speed = 10
        self.turnSpeed = 5
        # gravity -- is touching ground?
        self.isGrounded = False
        # creates actor object using constructor and parents to passed renderer
        self.loadModel(model)
        self.renderer = base.render
        self.reparentTo(self.renderer)
        # put at specified location
        self.setPos(pos)
        # store dimensions for later
        # https://discourse.panda3d.org/t/getting-the-height-width-and-length-of-models-solved/6504
        # Post describes the output of the Nodepath class's getTightBounds() method. Using this
        # I am able to get an approximation of the dimensions of an object in the global coordinate space
        minimum, maximum = self.getTightBounds()
        # make sure all numbers are positive for best (any) results
        self.bounds = [abs(num) for num in (minimum - maximum)]
        self.width, self.length, self.height = self.bounds[0], self.bounds[
            1], self.bounds[2]

        # COLLISION PROPERTIES
        # create collision ray that is height of model pointing down (will detect ground collisions)

        if physics:
            self.groundRay = CollisionRay()
            self.groundRay.setOrigin(0, 0, 1000)
            self.groundRay.setDirection(0, 0, -1)
            self.groundCol = CollisionNode('groundRay')
            self.groundCol.addSolid(self.groundRay)
            self.groundCol.setFromCollideMask(BitMask32.bit(0))
            self.groundCol.setIntoCollideMask(BitMask32.allOff())
            self.groundColNode = self.attachNewNode(self.groundCol)
            base.cTrav.addCollider(self.groundColNode, base.groundHandler)

            #and another one for everything else
            self.mainCol = CollisionNode('actorCollision' + str(id(self)))
            # create collision sphere as solid for this collision node
            self.mainCol.addSolid(
                CollisionSphere(0, 0, self.height / 2, self.height / 2))
            # specify valid collisions for collision node
            self.mainCol.setFromCollideMask(CollideMask.bit(0))
            self.mainCol.setIntoCollideMask(CollideMask.bit(
                1))  # accepts incoming objects with collideMask bit(1)
            # attach collision node to actor
            self.cNode = self.attachNewNode(self.mainCol)
            # show
            #self.cNode.show()
            # make instance collision traverser aware of this collision node, tell it how to handle (with pusher)
            base.cTrav.addCollider(self.cNode, base.pusher)
            # add collision to pusher collision handler; tell pusher which node to associate with which actor IOT push
            base.pusher.addCollider(self.cNode, self, base.drive.node())

        # add to base.entities (since all allies/enemies created through this constructor, makes sense
        base.entities.append(self)
        # add as client of entity collision handler
        # base.cTrav.addCollider(self.cNode,base.entityCollisionHandler)

        #add to cleanup for deletion later
        base.cleanup.append(self)

        #store reference to self
        #https://discourse.panda3d.org/t/inheriting-from-nodepath/10886/4
        #post describes how to set up a reference from nodepath to itself to retrieve custom properties
        #I use python tags to distinguish collision volume owners
        self.mainCol.setPythonTag("owner", self)

        # TODO if no initial collision, enforce gravity until collision
        #taskMgr.add(self.doGravity, "entityGravity")

    #procedure update
    #do all actions that must be done for this object every frame - called from game loop
    def updateState(self):
        #by default do gravity for all entities
        self.doGravity()
        return

    #procedure entityGravity
    # drops entity by gravity if isGrounded is not true (set True by collision with ground)
    def doGravity(self):
        dt = globalClock.getDt()
        #gravity - if not grounded, make it so
        if not self.isGrounded:
            self.setZ(self.getZ() - 50 * dt)
            return
        else:
            entries = []
            for entry in base.groundHandler.getEntries():
                if entry.getFromNodePath().getParent() == self \
                        or entry.getIntoNodePath().getParent() == self:
                    entries.append(entry)
            if (len(entries) > 0) and (entries[0].getIntoNode().getName()
                                       == "terrain"):
                self.setZ(entries[0].getSurfacePoint(base.render).getZ() + 2)

    #procedure add Damage
    #float dmg -> decrement self.health
    def addDamage(self, sender, dmg):
        '''
        Adds a specified amount to this entity
        Can be negative or positive
        If self.health < 0 call delete self
        '''
        #get shooter for scoring purposes
        shooter = sender.owner
        # TODO make pretty death
        self.health -= dmg
        if self.damagedSound is not None:
            self.damagedSound.play()
        if self.health <= 0:
            #if shooter was player, add to score
            if shooter is base.player:
                if shooter.team is self.team:
                    base.player.score -= 15
                else:
                    base.player.score += 15
            #if player, end game
            if self is base.player:
                messenger.send('Leave-Game')
            self.kill()

    #procedure kill
    # destroys object, but fancy-like
    def kill(self):
        self.pose('death', 0)
        taskMgr.add(self.deleteTask, 'deleteTask')

    #procedure deleteTask
    #destroys object after five seconds of being in death state
    def deleteTask(self, task):
        if task.time < 5.0:
            return task.cont
        else:
            self.delete()
            return task.done

    #procedure class deconstructor
    def delete(self):
        '''
        Class destructor
        Destroy an object cleanly from instance
        '''
        #also remove from base.entities
        if self in base.entities:
            base.entities.remove(self)

        # remove pythontag from collision
        self.mainCol.clearPythonTag("owner")
        # remove collision node from global collider
        base.cTrav.removeCollider(self.cNode)
        #destroy actor
        if not self.is_empty():
            self.hide()
            self.cleanup()
            #self.detachNode()
            del self

    #class deconstructor
    def __del__(self):
        self.removeNode()