コード例 #1
0
    def ShootToConstruction(self, target, frompos, targetpos):
        # Shoots to a construction target. Usually by a cannon
        # The targetpos parameter is where attacker points. But the precision factor can modify it
        # In addition, the final shooting point can hit any other construction part (see NOTE bellow)
        # Returns a dictionary with hit result -> {"Hit": True/False, "Intersection": Point3D()}

        ret = {"Hit": False, "Intersection": Point3D()}

        # Aiming consideration: We are going to calculate a shoot in a direction sampled by a cosinus distribution
        # From the aiming point of view, the shooter points to a far point, and if accuracy factor is 100%, it should hit the target.
        # So, the sphere used to sample the cosinus distribution should has the target distance as radius.
        # Then, we can use the accuracy factor to get a percentage of shooting distance. With this consideration, bad accuracy factors means
        # sampling on small spheres, so the shoot can fail
        dist = frompos.Distance(targetpos)
        accuracy = dist * self.__accuracyFactor / 100.0

        direction = Vector3D()
        direction.CreateFrom2Points(frompos, targetpos)
        sph = Sphere(position=frompos, radius=accuracy)
        v = sph.GetRayCosine(direction)

        # Check hit on the castle
        # NOTE : The intersection test over whole castle geometry has a high cost. The process is simplified considering only target.
        #        If shoot doesn't hits target, the shoot fails

        ray = Ray(origin=frompos, direction=v, energy=self.__attack)
        hitpoint = target.RecieveImpact(ray)

        if (hitpoint != None):
            ret["Hit"] = True
            ret["Intersection"] = hitpoint

        return ret
コード例 #2
0
ファイル: Wall.py プロジェクト: neich/BattleField
    def __init__(self):
        Construction.Construction.__init__(self)
        self._thickness = Battles.Utils.Settings.SETTINGS.Get_F('Castle', 'Wall', 'Thickness')
        self._height = Battles.Utils.Settings.SETTINGS.Get_F('Castle', 'Wall', 'InnerHeight')
        self._defenseIncrease = Battles.Utils.Settings.SETTINGS.Get_I('Castle', 'Wall', 'DefenseIncrease')
        self.__slope = None
        self.__joins = [None, None]
        self.__walkway = {
            "altitude": (self._height - Battles.Utils.Settings.SETTINGS.Get_F('Castle', 'Wall', 'MerlonHeight')),
            "width": Battles.Utils.Settings.SETTINGS.Get_F('Castle', 'Wall', 'WalkwayWidth')}

        self._defendingLines = BattalionConstruction.BattalionConstruction(height=self.__walkway["altitude"],
                                                                           cellsize=Battles.Utils.Settings.SETTINGS.Get_F(
                                                                               'Castle', 'Wall',
                                                                               'BattalionGridCellSize'))

        self.__tilesManager = TilesManager.TilesManager(self)

        self._label = 'Wall_' + str(Wall.wallCounter)
        Wall.wallCounter += 1

        self.__rectangle3D = [Point3D(), Point3D(), Point3D(), Point3D()]
        self.__boundingRectangle3D = BoundingBox()
        self.__normalVector = Vector3D()

        self.__heightCanvasObjs = []

        self.__climberStairs = []

        self.__climbedAttacker = {"Battalion": None, "SoldierPosition": Point3D()}
        self.__attachedSiegeTower = {"SiegeTower": None, "Position": Point3D()}
コード例 #3
0
ファイル: Wall.py プロジェクト: neich/BattleField
    def CalculateNormalVector(self, index=0):
        # Returns the wall exterior normal angle
        # Due walls are ever perpendicular to terrain, we can calculate it in 2D
        v = Vector2D()
        v.CreateFrom2Points(self.GetStartPosition(), self.GetEndPosition())

        normal = Vector2D(v.val[1], -v.val[0])
        normal.Normalize()  # Redundant

        self.__normalVector = Vector3D(normal.val[0], normal.val[1], 0.0)
コード例 #4
0
ファイル: Army.py プロジェクト: neich/BattleField
    def GetBestTurtleBattalion(self, target, siegetowerpos, castle):
        # Returns the best battalion to be converted to a siege tower turtle
        # Return the closest battalion to given moat target position and siegetower position. The later condition helps to get a good battalion placement if the avaiable
        # battallions are too far from the siege tower (helping to get a more straight path to the goal)
        # Only those battalions that the path from their positions and target dont intersect with the castle, are considered

        minDist = -1
        closest = None

        b = self.__battalions["Infantry"]
        if (b == None):
            return None
        else:
            for bb in b.battalions:
                if (
                        not bb.IsDefeated()
                ):  # Be aware. Due this method can be called meanwhile a battalion is killed, we have to check if they are defeated
                    com = bb.GetCommand()
                    if (com.IsMove() and not com.IsCoverMoat()):
                        center = bb.GetCenterPosition()
                        dist = center.Distance(target)

                        # Check if turtle path to target intersects the castle (so the turtle is stupid and cannot take a good path avoiding obstacles ..., yeah, another one TODO)
                        ray = Ray(origin=center,
                                  direction=Vector3D().CreateFrom2Points(
                                      center, target))
                        if (not castle.RayHitTest_Closest(
                                ray=ray, exclude=None, distance=dist)):

                            dist2 = center.Distance(siegetowerpos)
                            d = dist + dist2
                            if ((closest == None) or (d < minDist)):
                                minDist = d
                                closest = bb

        return closest
コード例 #5
0
ファイル: Wall.py プロジェクト: neich/BattleField
    def JoinShape(self, obj, invert=False):
        # Creates and join current shape with given object's shape
        # Note the order of objects: "Current wall is going to be joined with given object". This means that only is calculated the ending wall join
        # The invert flag changes this behaviour (only for towers -> TODO: Also for walls)

        factory = ConstructionFactory()

        if obj == None:
            # None object to join. Constructs a simple rectangular wall shape

            del self._shape2D[0:len(self._shape2D)]

            l = self.GetLength()
            vector = Vector2D()
            vector.CreateFrom2Points(self.GetStartPosition(), self.GetEndPosition())
            tvector = vector.Copy()
            tvector.Rotate(-90)

            p = self.GetStartPosition().Copy()
            p.Move(tvector, self._thickness / 2)
            self._shape2D.append(p.Copy())
            p.Move(vector, l)
            self._shape2D.append(p.Copy())
            p.Move(tvector, -self._thickness)
            self._shape2D.append(p.Copy())
            p.Move(vector, -l)
            self._shape2D.append(p.Copy())

            return

        else:

            # Updates the adjacencies

            if invert:
                self._adjacentConstructions[0] = obj
                obj._adjacentConstructions[1] = self
            else:
                self._adjacentConstructions[1] = obj
                obj._adjacentConstructions[0] = self

        if factory.IsWall(obj):

            # Calculate the bisector between both walls
            bisecang = self.GetBisector(obj)

            # The join point will be along the bisector 
            d = (self._thickness / 2.0) / math.cos(
                math.radians(bisecang["angle"] / 2.0))  # WARNING: We consider all walls of same thickness
            p = self.GetEndPosition().Copy()
            p.Move(bisecang["bisector"], d)

            self._shape2D[1] = p.Copy()
            obj._shape2D[0] = p.Copy()

            p.Move(bisecang["bisector"], -(d * 2.0))
            self._shape2D[2] = p.Copy()
            obj._shape2D[3] = p.Copy()

            self._adjacentConstructions[1] = obj
            obj._adjacentConstructions[0] = self




        elif factory.IsSquaredTower(obj):

            # Calculate the intersection points of wall shape with tower   

            # We must consider the invert flag. If it is true, the joined part will be the starting wall. Otherwise, the ending wall
            if invert:
                v = Vector3D().SetFrom2D(self.GetWallVector())
                v.Invert()
                ray1 = Ray(origin=Point3D().SetFrom2D(self._shape2D[1]), direction=v)
                ray2 = Ray(origin=Point3D().SetFrom2D(self._shape2D[2]), direction=v)
            else:
                v = Vector3D().SetFrom2D(self.GetWallVector())
                ray1 = Ray(origin=Point3D().SetFrom2D(self._shape2D[0]), direction=v)
                ray2 = Ray(origin=Point3D().SetFrom2D(self._shape2D[3]), direction=v)

            int1 = obj.RecieveImpact(ray1)
            int2 = obj.RecieveImpact(ray2)

            if int1 != None:
                if invert:
                    self._shape2D[0] = Point2D().SetFrom3D(int1)
                else:
                    self._shape2D[1] = Point2D().SetFrom3D(int1)
            if int2 != None:
                if invert:
                    self._shape2D[3] = Point2D().SetFrom3D(int2)
                else:
                    self._shape2D[2] = Point2D().SetFrom3D(int2)

            # Reshape the wall axis (and all dependant data). Get the medium points of side shape segments
            mp1 = Segment2D(self._shape2D[0], self._shape2D[3]).GetMidPoint()
            mp2 = Segment2D(self._shape2D[1], self._shape2D[2]).GetMidPoint()
            self.SetPosition(mp1, mp2, reshape=False)


        elif factory.IsRoundedTower(obj):

            # Calculate the intersection points of wall shape with tower   

            # We must consider the invert flag. If it is true, the joined part will be the starting wall. Otherwise, the ending wall
            if invert:
                v = Vector3D().SetFrom2D(self.GetWallVector())
                v.Invert()
                ray1 = Ray(origin=Point3D().SetFrom2D(self._shape2D[1]), direction=v)
                ray2 = Ray(origin=Point3D().SetFrom2D(self._shape2D[2]), direction=v)
            else:
                v = Vector3D().SetFrom2D(self.GetWallVector())
                ray1 = Ray(origin=Point3D().SetFrom2D(self._shape2D[0]), direction=v)
                ray2 = Ray(origin=Point3D().SetFrom2D(self._shape2D[3]), direction=v)

            int1 = obj.RecieveImpact(ray1)
            int2 = obj.RecieveImpact(ray2)

            if int1 != None:
                if invert:
                    self._shape2D[0] = Point2D().SetFrom3D(int1)
                else:
                    self._shape2D[1] = Point2D().SetFrom3D(int1)
            if int2 != None:
                if invert:
                    self._shape2D[3] = Point2D().SetFrom3D(int2)
                else:
                    self._shape2D[2] = Point2D().SetFrom3D(int2)

            # Reshape the wall axis (and all dependant data). Get the medium points of side shape segments
            mp1 = Segment2D(self._shape2D[0], self._shape2D[3]).GetMidPoint()
            mp2 = Segment2D(self._shape2D[1], self._shape2D[2]).GetMidPoint()
            self.SetPosition(mp1, mp2, reshape=False)


        elif factory.IsBastion(obj):
            # Fit the wall to the bastion

            if invert:
                self._shape2D[3] = obj.GetEndPosition(exterior=False)
                self._shape2D[0] = obj.GetEndPosition(exterior=True)
            else:
                self._shape2D[1] = obj.GetStartPosition(exterior=True)
                self._shape2D[2] = obj.GetStartPosition(exterior=False)

            # Reshape the wall axis (and all dependant data). Get the medium points of side shape segments
            mp1 = Segment2D(self._shape2D[0], self._shape2D[3]).GetMidPoint()
            mp2 = Segment2D(self._shape2D[1], self._shape2D[2]).GetMidPoint()
            self.SetPosition(mp1, mp2, reshape=False)
コード例 #6
0
 def __GetVector3D(self):
     return Vector3D().CreateFrom2Points(self.__GetStartLine3D(),
                                         self.__GetEndLine3D())
コード例 #7
0
ファイル: TilesManager.py プロジェクト: neich/BattleField
    def GetBestTileToShoot(self, frompos):
        # Returns the tile that is the best suitable tile to shoot from given position
        # Due the goal is to create a gateway for the soldiers, the cannons try to shoot at higher wall positions. Due all tiles are placed at same xy pos, get the best
        # tile "column", and then the highest one

        pos2D = Point2D().SetFrom3D(frompos)

        ret = {"row": -1, "column": -1}
        maxD = -1.0

        normal = self.__construction.GetNormalVector()

        # Do a two pass search. First, search for the best tile to shoot without any hole at left or right. Second, just search the best tile to shoot if previous search failed
        # This avoid launching rubble to left and right of already holes. Otherwise, the rubble could cover the hole, and make it higer
        end = False
        firstpass = True
        while (not end):
            j = 0
            while (j < self.__tilesSize["columns"]):

                # Check for rubble status
                nrubbletiles = self.GetNRubbleCoveredTiles(j)

                # Check if there are any tile to shoot
                i = nrubbletiles
                found = False
                if (firstpass):
                    # Check for left and right hole condition
                    while (not found and (i < self.__tilesSize["rows"])):

                        if (i > 0):
                            left = self.__tiles[i - 1][j].IsHole()
                        else:
                            left = False
                        center = self.__tiles[i][j].IsHole()
                        if (i < (self.__tilesSize["rows"] - 1)):
                            right = self.__tiles[i + 1][j].IsHole()
                        else:
                            right = False

                        if (left or right or center):
                            i += 1
                        else:
                            found = True
                else:
                    while (not found and (i < self.__tilesSize["rows"])):
                        if (not self.__tiles[i][j].IsHole()):
                            found = True
                        else:
                            i += 1

                if (found):

                    center = self.GetTileCenter(self.__tiles[i][j])

                    # Because we don't worry about the cannon height, calculate the distances only in 2D
                    # TODO: Calculate it in 3D space to be more accurate
                    center2D = Point2D().SetFrom3D(center)

                    dist = center2D.Distance(pos2D)

                    invec = Vector3D()
                    invec.CreateFrom2Points(Point3D().SetFrom2D(center2D),
                                            Point3D().SetFrom2D(pos2D))
                    anglefactor = invec.DotProd(normal)

                    # Greater distance -> less effective
                    # Near to wall normal -> more effective
                    factor = anglefactor / dist

                    if (factor > maxD):
                        maxD = factor
                        ret["column"] = j

                j += 1

            if (maxD != -1):
                end = True  # Found the best one (first or second pass)
            else:
                if (firstpass):
                    firstpass = False  # Not found. Launch the second pass
                else:
                    end = True  # Not found. End of second pass

        # Check another amazing case (no wall!)
        if (maxD == -1):
            return None
        else:

            # Get the tallest row. Remember to consider the holes and the rubble
            nrubbletiles = self.GetNRubbleCoveredTiles(ret["column"])

            i = int(self.__tilesSize["rows"] - 1)
            while ((i >= nrubbletiles)
                   and self.__tiles[i][ret["column"]].IsHole()):
                i -= 1

            ret["row"] = i

            return self.__tiles[ret["row"]][ret["column"]]
コード例 #8
0
    def ShootToBattlefield(self, battlefield, frompos, targetpos):
        # Shoots to battlefield. Usually, by a cannon
        # The targetpos parameter is where attacker points. But the precision factor can modify it
        # Returns a dictionary with the hit result and final cell -> {"Hit": True/False, "Intersection": Point3D(), "Cell": GroundCell}

        ret = {"Hit": False, "Intersection": Point3D(), "Cell": None}

        # Aiming consideration: We are going to calculate a shoot in a direction sampled by a cosinus distribution
        # From the aiming point of view, the shooter points to a far point, and if accuracy factor is 100%, it should hit the target.
        # So, the sphere used to sample the cosinus distribution should has the target distance as radius.
        # Then, we can use the accuracy factor to get a percentage of shooting distance. With this consideration, bad accuracy factors means
        # sampling on small spheres, so the shoot can fail
        dist = frompos.Distance(targetpos)
        accuracy = dist * self.__accuracyFactor / 100.0

        direction = Vector3D()
        direction.CreateFrom2Points(frompos, targetpos)
        sph = Sphere(position=frompos, radius=accuracy)
        v = sph.GetRayCosine(direction)

        # Check hit on the battlefield

        ray = Ray(origin=frompos, direction=v, energy=self.__attack)
        hitpoint = battlefield.RayIntersects(ray)

        if (hitpoint != None):
            ret["Intersection"] = hitpoint.Copy()
            ret["Cell"] = battlefield.GetCellFromPoint(hitpoint)
            ret["Battalion"] = None

            if (not ret["Cell"].HasBattalion()):
                ret["Hit"] = False
            else:

                battalion = ret["Cell"].GetBattalion()
                ret["Battalion"] = battalion

                # If battlefield battalion has only 1 unit (cannons, siege towers), the method will be the classic one (attack against defense)
                # Otherwise, we have to calculate the number of killed units into the battalion
                if (battalion.GetNumber() == 1):

                    if (self.ShootDuel(battalion)):
                        ret["Hit"] = True
                        battalion.Kill(1)
                    else:
                        ret["Hit"] = False

                else:

                    # Shooting against battlefield is usually done by cannons. The main difference is that a cannon ball can kill more than one unit. To know how many units are killed
                    # we follow the next algorithm.
                    # When a cannon shoot hits on a cell, and considering that a cannon ball doesnt explode, the ball go trough the cell until it touch the ground. If any soldier is in its
                    # path, dies. But we dont have any predefined soldier position inside the battalion. To solve it, we are going to work with densities.
                    # We can get the cell soldiers density with
                    #     celldensity = battalion_size / cell_volume
                    # The cannon ball path inside the cell can be modelled as a cylinder. So,
                    #     nhits = celldensity * cylinder_volume
                    # But this has a problem, so we are considering that each unit has the same volume in the battlefield cell, and this is not true (think on a cannon)
                    # So we need to consider the occupied volume ratio. Then
                    #    cellratio = (battalion_size * soldier_volume) / cell_volume
                    # Because we want to keep the same ratio for cylinder, we get
                    #    (nkills * soldier_volume) = cellratio * cylinder_volume
                    #    nkills = battalion_size * cylinder_volume / cell_volume

                    bbox = ret["Cell"].GetBoundingBox()
                    ray.Reset()

                    if (ray.HitBox(bbox)):
                        clength = ret["Intersection"].Distance(
                            ray.GetHitPoint())

                        bvol = bbox.GetVolume()
                        if (bvol == 0):
                            ret["Hit"] = False  # Some battalions can have 0 height, such are the siege towers when are in construction phase
                        else:
                            svolume = battalion.GetSoldierBoxVolume()
                            cellratio = (battalion.GetNumber() *
                                         svolume) / bvol
                            cvolume = Battles.Utils.Settings.SETTINGS.Get_F(
                                'Army', 'Cannons', 'BallRadius'
                            ) * Battles.Utils.Settings.SETTINGS.Get_F(
                                'Army', 'Cannons',
                                'BallRadius') * math.pi * clength
                            nhits = (cellratio * cvolume) / svolume

                            if (nhits >= 1):
                                battalion.Kill(math.ceil(nhits))
                                ret["Hit"] = True
                            else:
                                ret["Hit"] = False
                    else:
                        ret["Hit"] = False

        return ret
コード例 #9
0
    def InAttackRange(self,
                      currPos,
                      targetPos,
                      castle,
                      constructionTarget,
                      excludeConstruction=None):
        # Returns true if targetPos is in attack range from currPos
        # Given castle is used to check if shoot should intersect with any castle part, returning False.
        # If the attacker aim to any castle battalion, constructionTarget object is used to discard the target's construction from intersection test
        # If the attacker shoots from a construction (its a defender), excludeConstruction is used to avoid the castle self intersection check

        # Check the distance
        #dist = currPos.Distance(targetPos)
        #dist = math.sqrt(((currPos.x - targetPos.x)**2) + ((currPos.y - targetPos.y)**2))
        #dist = math.hypot(currPos.x - targetPos.x, currPos.y - targetPos.y)
        dist = math.sqrt(((currPos.x - targetPos.x)**2) +
                         ((currPos.y - targetPos.y)**2) +
                         ((currPos.z - targetPos.z)**2))
        if (dist > self.__distance):
            return False
        else:
            # Check the attack direction and angle

            if (not self.__attackVector.IsNull()):

                if (not self.__attackAngle):
                    return False

                # Check horizontal angle
                vec = Vector2D()
                vec.val[0] = targetPos.x - currPos.x
                vec.val[1] = targetPos.y - currPos.y
                l = math.hypot(vec.val[0], vec.val[1])
                if (l == 0):
                    vec.val[0] = 0
                    vec.val[1] = 0
                else:
                    vec.val[0] /= l
                    vec.val[1] /= l

                dotprod = (self.__attackVector.val[0] * vec.val[0]) + (
                    self.__attackVector.val[1] * vec.val[1])
                if (dotprod > 1):
                    dotprod = 1
                if (dotprod < -1):
                    dotprod = -1
                ang = math.degrees(math.acos(dotprod))

                if (ang > (self.__attackAngle['H'] / 2.0)):
                    return False
                else:

                    # Check the vertical angle
                    vec2 = Vector2D()
                    vec2.val[0] = math.fabs(targetPos.x - currPos.x)
                    vec2.val[1] = targetPos.z - currPos.z
                    l2 = math.hypot(vec2.val[0], vec2.val[1])
                    if (l2 == 0):
                        vec2.val[0] = 0
                        vec2.val[1] = 0
                    else:
                        vec2.val[0] /= l2
                        vec2.val[1] /= l2

                    dotprod2 = vec2.val[
                        0]  # The reference vector is a ground parallel vector, that is <1,0>, so the dotprod can be simplified
                    if (dotprod > 1):
                        dotprod = 1
                    if (dotprod < -1):
                        dotprod = -1
                    ang2 = math.degrees(math.acos(dotprod2))

                    if (((targetPos.z <= currPos.z) and
                         (ang2 > self.__attackAngle['V']['bottom']))
                            or ((targetPos.z > currPos.z) and
                                (ang2 > self.__attackAngle['V']['top']))):
                        return False

            attackvec = Vector3D().CreateFrom2Points(currPos, targetPos)

            if (constructionTarget != None):
                # Check the castle intersection when the attacker aims to the castle
                r = Ray(origin=currPos, direction=attackvec)

                # Check the ray intersection on all castle parts and get the closest (the first intersection)
                # If the closest is not the target, means that is occluded
                if (not constructionTarget.RayHitTest(r)):
                    return False
                else:
                    distr = r.GetLength()
                    r.Reset(
                    )  # Resets the previous hitpoint calculated in constructionTarget.RayHitTest(r)
                    if (castle.RayHitTest_Closest(ray=r,
                                                  exclude=constructionTarget,
                                                  distance=distr) != None):
                        return False
                    else:
                        return True

            if (excludeConstruction != None):

                # Check the castle intersection when the attacker shoots from the castle
                r = Ray(origin=currPos, direction=attackvec)
                if (castle.RayHitTest(r, excludeConstruction)):
                    distr = r.GetLength()
                    if (distr < dist):
                        return False
                    else:
                        return True
                else:
                    return True
                """
                constr = castle.RayHitTest_Closest(ray = r, exclude = excludeConstruction, distance = r.GetLength())
                if (constr != None):


                    
                    # We have to check if the intersection is done against an exterior wall or not
                    # The battalion placement could be under or behind the wall
                    factory = ConstructionFactory()
                    if (factory.IsWall(constr)):
                        # The most easy way to check it is to compare the wall normal vector and attack vector to check if both are visible
                        wnorm = constr.GetNormalVector()
                        if (wnorm.DotProd(attackvec) > 0):
                            # Not visible. This should be the wall back
                            return False
                        else:
                            return True     # Front wall hit

                    else:
                        return False    # Tower hit
                        
                else:
                    return True     # None castle part occludes the shot
                """

            return True