Implement this code:

import numpy as np

# Get the cannon gravity and launch speed
GravityScale = np.frombuffer(currentCannon + offsets.Cannon_ProjectileGravityScale, dtype=np.float32)
Gravity = 981 * GravityScale
LaunchSpeed = np.frombuffer(currentCannon + offsets.Cannon_ProjectileSpeed, dtype=np.float32)

# Calculate the correct shooting location by transforming the cannon rotation to a forward vector
Angle = np.frombuffer(currentCannon + offsets.Cannon_ServerPitch, dtype=np.float32)
compAngle = GetRotation(root)
Angle += compAngle

vForward = RotationToVector(Angle)
Pos = GetLocation(root)
Pos[2] += 100
Pos = Pos + (vForward * 150)

# Calculate the correct velocity with the forward vector
Vel = vForward * LaunchSpeed
if my.ship:
    Vel += my.shipvel

# Function to calculate the trajectory
def GetProjectilePath(v: np.ndarray, Vel: np.ndarray, Pos: np.ndarray, Gravity: float, Count: int) -> bool:
    interval = 0.033
    for i in range(Count):
        v[i] = Pos
        Vel[2] -= Gravity * interval
        move = Vel * interval
        nextPos = Pos + move
        res = Trace(Pos, nextPos)
        Pos = nextPos
        if not res:
            return True  # we hit something
    return False

# Store the path into a limited count vector
path = np.zeros((200, 3), dtype=np.float32)
hit = GetProjectilePath(path, Vel, Pos, Gravity, 200)


To this file:

from pyglet.shapes import Circle
from pyglet.text import Label

from Modules.display_object import DisplayObject
from helpers import calculate_distance, object_to_screen, main_batch, \
    TEXT_OFFSET_X, TEXT_OFFSET_Y
from mapping import players

CANNON_PREDICTION_COLOR = (255, 0, 0)  # The color we want the indicator circle to be
CIRCLE_SIZE = 7.5  # The size of the indicator circle we want


class CannonPrediction(DisplayObject):
    """
    Class to generate information for a cannon object in memory
    """

    def __init__(self, memory_reader, actor_id, address, my_coords, raw_name):
        """
        Upon initialization of this class, we immediately initialize the
        DisplayObject parent class as well (to utilize common methods)

        We then set our class variables and perform all of our info collecting
        functions, like finding the actors base address and converting the
        "raw" name to a more readable name per our Mappings. We also create
        a circle and label and add it to our batch for display to the screen.

        All of this data represents a "Cannon". If you want to add more, you will
        need to add another class variable under __init__ and in the update()
        function

        :param memory_reader: The SoT MemoryHelper Object we use to read memory
        :param address: The address in which the AActor begins
        :param my_coords: a dictionary of the local cannons coordinates
        :param raw_name: The raw actor name used to translate w/ mapping.py
        """
        # Initialize our super-class
        super().__init__(memory_reader)

        self.actor_id = actor_id
        self.address = address
        self.actor_root_comp_ptr = self._get_root_comp_address(address)
        self.my_coords = my_coords
        self.raw_name = raw_name

        # Generate our Cannon's info
        self.name = cannons.get(self.raw_name).get("Name")
        self.coords = self._coord_builder(self.actor_root_comp_ptr,
                                          self.coord_offset)
        self.distance = calculate_distance(self.coords, self.my_coords)

        self.screen_coords = object_to_screen(self.my_coords, self.coords)

        # All of our actual display information & rendering
        self.color = CANNON_PREDICTION_COLOR
        self.text_str = self._built_text_string()
        self.text_render = self._build_text_render()
        self.icon = self._build_circle_render()

        # Used to track if the display object needs to be removed
        self.to_delete = False

    def _build_circle_render(self) -> Circle:
        """
        Creates a circle located at the screen coordinates (if they exist).
        Uses the color specified in our globals w/ a size of 10px radius.
        Assigns the object to our batch & group
        """
        if self.screen_coords:
            return Circle(self.screen_coords[0], self.screen_coords[1],
                          CIRCLE_SIZE, color=self.color, batch=main_batch)

        return Circle(0, 0, 10, color=self.color, batch=main_batch)

    def _built_text_string(self) -> str:
        """
        Generates a string used for rendering. Separate function in the event
        you need to add more data (Sunk %, hole count, etc)
        """
        return f"{self.distance}m"

    def _build_text_render(self) -> Label:
        """
        Function to build our actual label which is sent to Pyglet. Sets it to
        be located at the screen coordinated + our text_offsets from helpers.py

        Assigns the object to our batch & group

        :rtype: Label
        :return: What text we want displayed next to the cannon
        """
        if self.screen_coords:
            return Label(self.text_str,
                         x=self.screen_coords[0] + TEXT_OFFSET_X,
                         y=self.screen_coords[1] + TEXT_OFFSET_Y,
                         batch=main_batch)

        return Label(self.text_str, x=0, y=0, batch=main_batch)

    def update(self, my_coords: dict):
        """
        A generic method to update all the interesting data about a cannon
        object, to be called when seeking to perform an update on the
        Actor without doing a full-scan of all actors in the game.

        1. Determine if the actor is what we expect it to be
        2. See if any data has changed
        3. Update the data if something has changed

        In theory if all data is the same, we could *not* update our Label's
        text, therefore saving resources. Not implemented, but a possibility
        """
        if self._get_actor_id(self.address) != self.actor_id:
            self.to_delete = True
            self.icon.delete()
            self.text_render.delete()
            return

        self.my_coords = my_coords
        self.coords = self._coord_builder(self.actor_root_comp_ptr,
                                          self.coord_offset)
        new_distance = calculate_distance(self.coords, self.my_coords)

        self.screen_coords = object_to_screen(self.my_coords, self.coords)

        if self.screen_coords:
            self.text_render.visible = True
            self.icon.visible = True

            # Update the position of our circle and text
            self.icon.x = self.screen_coords[0]
            self.icon.y = self.screen_coords[1]
            self.text_render.x = self.screen_coords[0] + TEXT_OFFSET_X
            self.text_render.y = self.screen_coords[1] + TEXT_OFFSET_Y

            # Update our text to reflect out new distance
            self.distance = new_distance
            self.text_str = self._built_text_string()
            self.text_render.text = self.text_str

        else:
            # if it isn't on our screen, set it to invisible to save resources
            self.text_render.visible = False
            self.icon.visible = False