Search “apex legends weapon tier list” and you’ll get a different answer from every result. One site puts the R-99 in S-tier because of raw close-range damage. Another ranks the Hemlok higher because it rewards accuracy at range. A third hasn’t been updated since Season 27 and still treats weapons that got reworked months ago as current. None of these sites are lying to you. They’re just measuring different things, on different patches, with no shared method.

This tutorial skips the debate and builds the tool instead. You’ll write a Python project that pulls current Apex Legends weapon data, scores every gun against a transparent formula, and sorts the results into a tier list you can defend with numbers rather than vibes. The worked example uses Season 29 “Overclocked,” the patch that ran from May 5 to August 3, 2026, but the script itself keeps working after Respawn ships the next balance pass. By the end you’ll have a repo you can re-run every time a patch note drops, plus the reasoning to know when a tier list (yours or anyone else’s) is worth trusting.

Twelve build steps cover the full pipeline: environment setup, a weapon data schema, a sourced baseline dataset, a time-to-kill engine, headshot and range handling, ammo-economy scoring, a composite formula, tier sorting, export, and a scheduled job that keeps the output current. A thirteenth step adds a small regression test so a future edit can’t silently break the math. None of it requires a game API key or a paid service. It runs entirely on data you can find in public patch notes and a plain text editor.

What actually changed in Season 29 “Overclocked”

Before scoring a single gun, you need to know what the current patch changed and what it left alone. Season 29 added the Skirmisher legend Axle, along with two new systems called Deathbox Respawns and Chained Healing, according to Dexerto’s patch note coverage. On the weapon side, the headline change wasn’t a single gun getting gutted. It was an ammo-economy adjustment: Respawn cut the maximum light-ammo stack size from 72 down to 60 rounds, according to the official Overclocked patch notes published on ea.com. That single change touches every light-ammo gun in the game without altering a single damage number.

A few individual weapons did move. The L-STAR EMG entered the Care Package rotation with its damage raised from 19 to 20, and it deals extra damage and a bigger projectile when it’s close to overheating. The C.A.R. SMG returned to floor loot, lost its Galvanic Gavel hop-up, and picked up bigger magazines: 26 rounds with a blue mag, up from 25, and 30 with purple or gold, up from 28. Season 29 also introduced a mechanic called the Sliding Shooter, which lets bullets fired while sliding draw directly from your reserve ammo rather than your magazine, capped at half the weapon’s maximum mag size. None of that is a universal TTK (time-to-kill) buff. It’s a set of narrow adjustments that change how specific guns perform in specific situations, which is exactly why a single static ranking stops being useful within a few weeks.

Season 29 factDetail
Season nameOverclocked
Launch dateMay 5, 2026
End dateAugust 3, 2026
New legendAxle (Skirmisher class)
New systemsDeathbox Respawns, Chained Healing, Sliding Shooter
Light ammo max stackCut from 72 to 60
L-STAR Care Package damageRaised from 19 to 20
C.A.R. SMG statusReturned to floor loot, Galvanic Gavel hop-up removed

Why a static weapon tier list breaks by the next patch

Most published tier lists bake in an assumption that never gets stated out loud: the author’s ranking reflects one rank bracket, one sample of matches, and one moment in the patch cycle. Change any of those three things and the ranking shifts. A weapon that tops a Predator-lobby list might sit in the middle for Gold-rank players who miss more headshots and rarely fight at the ranges where that gun shines.

Ammo category matters more than most guides admit, too. Apex splits weapons into five pools: light, heavy, energy, shotgun, and sniper ammo. Light ammo covers the Alternator, R-99, R-301, G7 Scout, P2020, RE-45, and Wingman. Heavy ammo covers the Flatline, Hemlok, 30-30 Repeater, Prowler, C.A.R., and Spitfire. Energy ammo covers the Havoc, Devotion, L-STAR, Nemesis, and Volt. Shotgun ammo covers the EVA-8, Mastiff, Mozambique, and Peacekeeper. Sniper ammo covers the Charge Rifle, Longbow, Sentinel, and Kraber. When Respawn squeezes light-ammo stack size, every gun in that pool gets quietly weaker in sustained fights, even though nobody touched their damage or fire rate. A tier list that only tracks damage numbers misses that entirely, which is the gap this tutorial’s scoring model is built to close.

Rank bracket skews results in a second, less obvious way. A high-recoil, high-damage weapon looks mediocre in a dataset pulled from Bronze and Silver lobbies, where most fights end before anyone lands enough shots to matter, then jumps several tiers once you isolate Diamond-and-up data where fights run longer and reward sustained accuracy. Pick rate alone can mislead you here too. A weapon with a 28 percent pick rate isn’t automatically stronger than one sitting at 15 percent. It might just spawn more often near high-traffic drop points, which has nothing to do with how it performs once picked up. Treat pick rate as a popularity signal, not a power signal, and let your own TTK and sustain math carry the actual ranking.

Ammo typeWeapons
LightAlternator, R-99, R-301, G7 Scout, P2020, RE-45, Wingman
HeavyFlatline, Hemlok, 30-30 Repeater, Prowler, C.A.R., Spitfire
EnergyHavoc, Devotion, L-STAR, Nemesis, Volt
ShotgunEVA-8 Auto, Mastiff, Mozambique, Peacekeeper
SniperCharge Rifle, Longbow, Sentinel, Kraber

How community tier lists actually gather their data

It helps to know what you’re comparing your output against. Community guide sites generally fall into two camps. The first pulls aggregate pick-rate and win-rate numbers from third-party stat trackers, averages them across whatever player sample that tracker has access to, and publishes a ranking with little explanation of the underlying math. The second leans on staff or community playtesting, where a small group of experienced players runs each weapon through scrims and ranks by feel, backed by a handful of supporting numbers. Both approaches produce useful guides. Neither one documents its target HP assumption, its headshot rate, or its rank bracket the way this tutorial’s pipeline does by design.

That difference is why two respected sites can publish opposite rankings for the same weapon in the same week. A stat-aggregation site might show the Wingman underperforming because its sample skews toward lower ranks where fast-TTK automatics dominate, while a playtesting site ranks the Wingman near the top because its testers are consistently landing headshots at a rate the aggregate data doesn’t isolate. Neither site is doing anything wrong. They’re answering a slightly different question. Building your own scorer means you get to pick which question you’re answering and keep that choice consistent from patch to patch, instead of inheriting someone else’s unstated assumptions every time you check a new guide.

Prerequisites: tools and versions you’ll need

This build is deliberately light on dependencies so you can run it on a laptop or inside a free-tier cloud notebook. You’ll need Python 3.11 or newer, pip for package installs, and a text editor. The project uses two third-party libraries: requests for pulling patch note pages and pandas for sorting and exporting the scored weapon list. Both install cleanly with a single pip command and both have stable, well-documented APIs that haven’t shifted in years, so version drift isn’t a real risk here.

  • Python 3.11+ (check with python3 --version)
  • pip, bundled with modern Python installs
  • requests and pandas, installed via pip
  • A current weapon data source: official Apex Legends patch notes or a stats reference such as Apex Weapon Stats / TTKDB
  • About 90 minutes for the full build, including testing

Step 1: Set up the project

Create a project folder and a virtual environment, then install the two dependencies. Keeping the environment isolated means a system-wide package update months from now can’t quietly break your scoring script.

mkdir apex-weapon-tier && cd apex-weapon-tier
python3 -m venv venv
source venv/bin/activate   # Windows: venv\Scripts\activate
pip install requests pandas
mkdir data output

You now have a clean workspace with a data folder for weapon stat files and an output folder for the generated tier list. Everything else in this tutorial lives in a handful of Python files inside this directory.

Step 2: Define a weapon data schema

Before writing any scoring logic, decide what a “weapon” record looks like. Keep the schema flat and explicit so you, or anyone reading the repo later, can see exactly what the formula depends on. Save this as data/schema_example.py.

# data/schema_example.py
# Fill in current numbers from official patch notes or a stats
# reference before trusting any output this project produces.

WEAPONS = {
    "l_star": {
        "name": "L-STAR EMG",
        "ammo_type": "energy",
        "damage_body": 20,          # Care Package value, Season 29
        "rpm": None,                # fill in from current source
        "mag_size": None,           # Care Package weapon, fixed loadout
        "headshot_multiplier": None,
        "source": "EA Overclocked patch notes, May 2026",
    },
    "car_smg": {
        "name": "C.A.R. SMG",
        "ammo_type": "heavy",
        "damage_body": None,        # fill in from current source
        "rpm": None,
        "mag_size_blue": 26,        # up from 25 in Season 29
        "mag_size_purple_gold": 30, # up from 28 in Season 29
        "headshot_multiplier": None,
        "source": "EA Overclocked patch notes, May 2026",
    },
    # Add every weapon you care about. Each entry needs damage_body,
    # rpm, mag_size, and headshot_multiplier pulled from a live
    # source. The two fields above are left as None on purpose
    # because this tutorial does not invent numbers it can't verify.
}

Notice the source field on every entry. That’s not decoration. Any time a teammate, a forum thread, or a future version of you asks where a number came from, the file answers the question without anyone having to remember.

Step 3: Source current baselines from verified patch notes

This is the step most tutorials skip, and it’s the one that matters most. Pull real numbers from the official Apex Legends patch notes on ea.com, cross-check them against a season archive like apexdb.gg, and use a dedicated stats reference for anything the patch notes don’t spell out directly. The Apex Weapon Stats project documents its own TTK methodology clearly: it assumes every shot hits, starts the clock at the first bullet, includes burst and rechamber delays, and normally excludes reload time, accuracy loss, and server-side variance. Borrow that same discipline for your own tool. Decide up front what your TTK function will and won’t account for, write it down in a comment, and keep the assumption consistent across every weapon you score.

For sanity-checking your output later, keep a couple of community tier lists on hand too, such as the Season 29 rankings from Carrylord. You’re not copying their conclusions. You’re checking whether your formula produces wildly different results from what experienced players are reporting, which is usually a sign you got a stat wrong, not a sign your method is broken.

Patch notes from Respawn tend to follow a loose structure: a top section for new content (legends, modes, maps), a legend-balance section organized alphabetically, and a weapons section that often buries ammo-economy changes, like the Season 29 light-ammo stack cut, in a general systems note rather than next to individual weapon entries. Read the full notes top to bottom at least once rather than searching for a specific gun’s name, since the change that affects your scoring the most might not mention that gun by name at all.

Step 4: Write the core TTK function

Time-to-kill is the backbone of any weapon score, so it deserves its own function with clear inputs. Save this as ttk.py.

# ttk.py
import math

def shots_to_kill(damage_per_hit, target_hp):
    """Minimum hits needed to drop a target with the given HP pool."""
    if damage_per_hit <= 0:
        raise ValueError("damage_per_hit must be positive")
    return math.ceil(target_hp / damage_per_hit)


def time_to_kill(damage_per_hit, rpm, target_hp, burst_delay=0.0):
    """
    Seconds to kill, assuming every shot connects.
    rpm: rounds per minute (sustained fire rate)
    burst_delay: extra seconds added for burst weapons between bursts
    """
    hits_needed = shots_to_kill(damage_per_hit, target_hp)
    seconds_per_round = 60.0 / rpm
    # First shot fires at t=0, so only pay the interval
    # for every hit after the first.
    fire_time = (hits_needed - 1) * seconds_per_round
    return fire_time + burst_delay

Keep target_hp as a parameter rather than a hardcoded constant. Apex's health pools change with shield tier (white, blue, purple, gold), and a weapon that looks dominant against an unshielded target can fall several places once you run the same math against a gold-shield opponent.

Step 5: Add headshot and armor multipliers

Raw body-shot TTK undersells weapons with strong headshot multipliers and good recoil control, which matter more as player skill goes up. Extend the function to accept a hit-location mix instead of assuming every bullet lands on the body.

# ttk.py (continued)
def weighted_ttk(damage_body, headshot_multiplier, headshot_rate,
                  rpm, target_hp, burst_delay=0.0):
    """
    headshot_rate: fraction of hits (0.0-1.0) assumed to land on the head.
    Pull headshot_multiplier per weapon from a current stats source.
    It varies by weapon class and isn't safe to hardcode as one value.
    """
    avg_damage = (
        damage_body * (1 - headshot_rate)
        + damage_body * headshot_multiplier * headshot_rate
    )
    return time_to_kill(avg_damage, rpm, target_hp, burst_delay)

Run this with a low headshot_rate, around 0.1, to model an average player, and again with something higher, around 0.4 to 0.5, to model a controller-aim or high-mechanical-skill player. A gun that only looks good in the second scenario tells you it's a high-ceiling, high-floor-gap weapon rather than a universal pick.

Step 6: Model range falloff for marksman and burst weapons

Damage in Apex drops off at range for most weapons, and burst-fire guns like the Hemlok have a dead interval between bursts that single-fire weapons don't. Add a range-bucket wrapper so a single weapon can carry different scores for close, medium, and long engagements rather than one misleading average.

# ttk.py (continued)
def ttk_by_range(weapon, target_hp, headshot_rate=0.15):
    """
    weapon["damage_by_range"] should be a dict like
    {"close": 18, "medium": 16, "long": 13} sourced from a
    current weapon stats reference.
    """
    results = {}
    for range_band, dmg in weapon["damage_by_range"].items():
        results[range_band] = weighted_ttk(
            damage_body=dmg,
            headshot_multiplier=weapon["headshot_multiplier"],
            headshot_rate=headshot_rate,
            rpm=weapon["rpm"],
            target_hp=target_hp,
            burst_delay=weapon.get("burst_delay", 0.0),
        )
    return results

A weapon with a flat damage falloff curve, which describes most SMGs, will show similar numbers across all three bands. A marksman weapon with a steep falloff curve will show a sharp split, which is exactly the signal a single-number tier list throws away.

Step 7: Score ammo economy after the light-ammo squeeze

Raw TTK tells you how fast a gun kills in a vacuum. It says nothing about whether you can keep shooting it across a whole fight, which is exactly what Season 29's light-ammo stack cut, from 72 down to 60, changed for seven weapons at once. Add an ammo-sustain score that divides a weapon's maximum carried ammo by its rounds-per-kill.

# ammo_economy.py
AMMO_STACK_CAP = {
    "light": 60,     # cut from 72 in Season 29
    "heavy": 64,
    "energy": 60,
    "shotgun": 16,
    "sniper": 32,
}

def sustain_score(ammo_type, rounds_per_kill, inventory_slots=2):
    max_reserve = AMMO_STACK_CAP[ammo_type] * inventory_slots
    kills_before_reload_trip = max_reserve / rounds_per_kill
    return round(kills_before_reload_trip, 2)

A light-ammo weapon that used to sustain eight kills per two inventory slots now sustains fewer, purely from the stack cut. Your composite score should reflect that even if the gun's raw damage number never moved, because that's the actual in-match impact of the Season 29 change.

Step 8: Weigh in mobility, the Sliding Shooter effect

Season 29's Sliding Shooter mechanic lets a weapon draw bullets fired while sliding directly from reserve ammo, capped at half the weapon's max magazine size, instead of forcing a reload mid-slide. That's a mobility and reload-avoidance edge, not a damage buff, so it needs its own score dimension rather than getting folded into TTK. A simple weighted bonus works here: add a small flat bump to weapons with larger magazines, since the cap (half of max mag) benefits them more in absolute bullet count. Keep this bonus modest, 5 to 10 percent of a weapon's total score at most, since it's a situational edge, sliding into a fight, rather than a core combat stat.

Step 9: Combine metrics into one composite score

With TTK, ammo sustain, and a mobility bonus calculated, combine them into a single number you can sort on. Normalize each metric to a 0-100 scale first so one dimension, say a sustain score that ranges into the dozens, doesn't silently dominate a dimension that only ranges from 1 to 3 seconds.

# score.py
def normalize(value, min_val, max_val):
    if max_val == min_val:
        return 50.0
    return 100 * (value - min_val) / (max_val - min_val)


def composite_score(ttk_seconds, ttk_min, ttk_max,
                     sustain, sustain_min, sustain_max,
                     mobility_bonus=0.0,
                     weight_ttk=0.55, weight_sustain=0.35,
                     weight_mobility=0.10):
    # Lower TTK is better, so invert the normalized value.
    ttk_score = 100 - normalize(ttk_seconds, ttk_min, ttk_max)
    sustain_score_norm = normalize(sustain, sustain_min, sustain_max)
    return round(
        ttk_score * weight_ttk
        + sustain_score_norm * weight_sustain
        + mobility_bonus * weight_mobility * 100,
        1,
    )

The weights above, 55 percent TTK, 35 percent sustain, 10 percent mobility, are a starting point, not a law. Ranked players who fight at range all game might weight TTK heavier. Pubs players who loot and reposition constantly might weight sustain heavier. Because the weights are named variables instead of buried constants, changing your mind later takes one line, not a rewrite.

Step 10: Sort guns into S through D tiers

A composite score is a ranked list, not yet a tier list. Tiers group weapons with similar scores so small, possibly noisy differences don't create artificial separation between guns that perform almost identically.

# tiers.py
def assign_tier(score):
    if score >= 85:
        return "S"
    elif score >= 70:
        return "A"
    elif score >= 55:
        return "B"
    elif score >= 40:
        return "C"
    else:
        return "D"


def build_tier_list(scored_weapons):
    # scored_weapons: list of (weapon_name, score) tuples
    tiered = {"S": [], "A": [], "B": [], "C": [], "D": []}
    for name, score in sorted(scored_weapons, key=lambda w: -w[1]):
        tiered[assign_tier(score)].append((name, score))
    return tiered

Adjust the cutoff thresholds, 85, 70, 55, and 40, after you've scored a full weapon roster once. If every gun lands in B and C, your bands are too wide. If half the roster lands in S, they're too narrow.

Step 11: Export JSON and a human-readable report

A tier list only earns trust if someone besides you can read the output and the reasoning behind it. Export both a machine-readable JSON file and a plain-text summary.

# export.py
import json

def export_results(tiered, patch_name, out_dir="output"):
    payload = {
        "patch": patch_name,
        "generated": "replace-with-your-run-timestamp",
        "tiers": tiered,
    }
    with open(f"{out_dir}/tier_list.json", "w") as f:
        json.dump(payload, f, indent=2)

    with open(f"{out_dir}/tier_list.txt", "w") as f:
        f.write(f"Weapon tier list -- {patch_name}\n")
        for tier in ["S", "A", "B", "C", "D"]:
            names = ", ".join(n for n, _ in tiered[tier]) or "(none)"
            f.write(f"{tier}: {names}\n")

Note the placeholder string instead of a live timestamp call. Stamp the actual date and time after the script finishes running, using whatever timestamp source your environment provides, rather than hardcoding one into the tutorial code.

Step 12: Automate re-scoring when the next patch lands

Season 29 gave way to Season 30 "Marked" on August 4, 2026, and every weapon tier list built for Overclocked became stale the moment that patch shipped. Rather than rebuilding the project from scratch each season, wire it into a scheduled job that re-pulls weapon data and re-runs the scoring pipeline.

# crontab entry: runs every Monday at 09:00, after most
# weekly patch notes have had time to post
0 9 * * 1 cd /path/to/apex-weapon-tier && venv/bin/python main.py >> logs/run.log 2>&1

Pair the cron job with a simple diff step: compare this week's tier_list.json against last week's. If a weapon jumps more than one tier, that's your signal to go check the patch notes for what changed, rather than quietly trusting a number that moved for reasons you haven't verified.

A lightweight notification step closes the loop nicely. Have the diff script write a short message to a local file, a webhook, or your own messaging tool of choice whenever a weapon's tier changes, rather than relying on yourself to remember to open the JSON file every Monday. You don't need a dedicated bot framework for this. A plain HTTP POST from the same Python script that just finished scoring is enough to get a heads-up the moment a patch reshuffles the meta.

Step 13: Write a quick regression test

Before you trust the pipeline with a season's worth of balance patches, add one small test file that catches the most common way this kind of script breaks quietly: someone adjusts a weight or a formula and the output shifts in a way nobody notices until a weapon lands in the wrong tier. A full test suite is overkill for a personal project, but a handful of assertions against known values pays for itself the first time you refactor score.py six months from now.

# test_pipeline.py
from ttk import time_to_kill
from ammo_economy import sustain_score

def test_time_to_kill_known_values():
    # 4 hits of 20 damage at 600 rpm should take noticeably
    # less than a second between the first and last shot.
    result = time_to_kill(damage_per_hit=20, rpm=600, target_hp=80)
    assert 0.2 < result < 0.5

def test_sustain_score_light_ammo_after_squeeze():
    # 60-round stack cap, two inventory slots, 5 rounds per kill
    result = sustain_score("light", rounds_per_kill=5, inventory_slots=2)
    assert result == 24.0

if __name__ == "__main__":
    test_time_to_kill_known_values()
    test_sustain_score_light_ammo_after_squeeze()
    print("All checks passed.")

Run this file directly with python test_pipeline.py, or wire it into pytest if you'd rather get proper test discovery and reporting as the project grows. Either way, run it before every scored output you plan to publish or share, not just once when you first write the code.

Common pitfalls when building a weapon tier tool

Most of the bugs that creep into a project like this aren't syntax errors. They're quiet, plausible-looking assumptions that produce a tier list that runs fine and still gives you the wrong answer. The list below covers the mistakes that show up most often in weapon-scoring scripts, roughly in the order you're likely to hit them while working through the steps above.

  • Hardcoding one headshot multiplier for every weapon class. Multipliers vary by weapon type, and treating them as uniform silently favors precision weapons over spray-and-pray SMGs.
  • Ignoring shield tier. A TTK calculated only against an unshielded target overstates how dominant high-damage, low-fire-rate weapons look in a ranked lobby where most fights involve at least blue shields.
  • Forgetting the ammo-economy dimension entirely. A gun that kills fast but runs dry in two fights isn't actually top-tier for a full match, which the light-ammo stack cut makes especially visible this patch.
  • Copying a community tier list's conclusions instead of using it only to sanity-check your own formula. If your ranking and a reputable site's ranking disagree sharply, check your input data first, not your methodology.
  • Treating Care Package weapons the same as ground loot. The L-STAR's 19-to-20 damage change only applies to the Care Package version, and folding that stat into a baseline floor-loot comparison produces a misleading score.
  • Skipping the source field on your data. Six months from now you won't remember which patch note a number came from, and neither will anyone you hand the project to.
  • Re-using last season's scores as a starting point instead of re-reading the current patch notes in full. Balance changes sometimes touch weapons indirectly, through legend ability interactions or care-package rotation shifts, in ways a quick diff of damage numbers won't surface.

Troubleshooting guide

Even a straightforward scoring pipeline hits the same handful of snags across different machines and setups. The table below covers the issues most likely to come up while working through the build steps above, along with what usually causes each one.

SymptomLikely causeFix
Every weapon lands in B or C tierScore bands too narrow for your data spreadRe-run assign_tier thresholds after checking your score distribution's min and max
ZeroDivisionError in sustain_scorerounds_per_kill passed as 0Guard the function with a check before dividing, and verify shots_to_kill returned a positive value
TTK numbers look too fast to be realburst_delay left at 0 for a burst weaponConfirm which weapons in your dataset are burst-fire and set their delay from a current stats source
Scores shift wildly after adding one weaponnormalize() min/max recalculated across the new full setExpected behavior. Normalization is relative, so freeze a baseline set first if you want stable comparisons across runs
JSON export fails with a permissions erroroutput directory doesn't exist or isn't writableRe-run mkdir output from Step 1 and check folder permissions
Cron job never runsVirtual environment path wrong in the crontab lineUse the absolute path to venv/bin/python, not a relative one. Cron doesn't load your shell profile
Weapon missing from ammo_economy outputammo_type string doesn't match a key in AMMO_STACK_CAPCheck for typos or casing mismatches between your schema and the lookup dictionary
Results don't match a community tier list at allDifferent target HP, rank bracket, or headshot-rate assumptionDocument your assumptions and compare like-for-like. A mismatch doesn't mean your script is broken
pandas import error after installInstalled into the wrong Python environmentConfirm the venv is activated before running pip install and before running the script

Advanced tips for a more accurate tier list

Once the base pipeline works, a few additions make the output noticeably more useful. First, split scores by rank bracket instead of producing one universal list. A weapon's effective skill ceiling matters less in Bronze lobbies than in Masters and Predator, where recoil control and headshot consistency separate weapons that looked identical on paper. Second, track score history across patches in a small SQLite database instead of overwriting last week's JSON file. That turns a one-time tool into a dataset you can chart over a full season, showing exactly when a weapon's score moved and which patch caused it.

Third, consider weighting your composite score by weapon rarity in the loot pool. A gun with a great TTK that only spawns as a Care Package drop isn't equally available to every player in every match, so a tier list meant to guide loadout decisions benefits from a small availability discount for rare spawns. Finally, keep your formula's weights in a separate config file rather than inline in the score function. That lets you run the same dataset through a ranked-focused weighting and a pubs-focused weighting without touching any code, just by swapping which config file you load.

If you want a visual output alongside the JSON and text files, a short script using matplotlib can turn your tiered dictionary into a horizontal bar chart in a few lines, sorted by composite score with color bands matching each tier. That's a nice-to-have rather than a requirement, since the plain-text report already answers the main question of where each weapon lands, but a chart makes week-over-week score drift easier to spot at a glance than scrolling through a text file. Keep the chart generation in its own script so a missing plotting library on a headless server never breaks the core scoring pipeline.

The complete working project

Put together, the project folder looks like this once every step above is in place:

apex-weapon-tier/
├── data/
│   └── schema_example.py
├── output/
│   ├── tier_list.json
│   └── tier_list.txt
├── ttk.py
├── ammo_economy.py
├── score.py
├── tiers.py
├── export.py
├── main.py
└── logs/
    └── run.log

main.py ties the pieces together. It loads weapon data from data/, runs each weapon through ttk.py and ammo_economy.py, feeds both results into score.py, sorts the output with tiers.py, and writes the results with export.py. Each module does one job and can be tested on its own, which matters once you start adjusting weights or adding new weapons mid-season. If you already built a legend-focused scoring tool for ranked play, our Apex Legends tier list calculator walkthrough applies the same pattern to legend pick and win rates instead of weapon stats, and our rank tracker build covers tracking your own RP alongside the meta data this project produces. Both projects share one idea that runs through our esports coverage: build the tool once, keep the data sources current, and stop re-trusting a snapshot the moment a patch note changes the game underneath it.

Frequently asked questions

Do I need an official Apex Legends API to build this?
No. Respawn doesn't publish a public weapon-stats API, so this project pulls numbers manually from official patch notes and a stats reference like TTKDB, then stores them in a plain Python or JSON file you control and update yourself.

How often should I re-run the scoring script?
Weekly is reasonable during a normal season. Re-run it immediately after any midseason balance patch, since those updates, like the one that shipped partway through Season 29, can shift several weapons' scores at once.

Why does my tier list disagree with a popular community list?
Check your target HP, headshot-rate, and rank-bracket assumptions first. Most disagreements between tier lists come from different inputs, not different conclusions about the same inputs.

Does the light-ammo stack cut affect every light-ammo weapon equally?
It affects the ceiling on how many rounds you can carry, which hits high-fire-rate SMGs like the R-99 and Alternator harder than slower-firing light-ammo weapons like the Wingman, since the SMGs burn through reserve ammo faster per fight.

Can I use this same project structure for a legend tier list instead of weapons?
Yes. Swap the weapon schema for a legend schema, pick rate, win rate, ability cooldowns, and swap the TTK and sustain functions for whatever metrics matter for legends. The composite-score and tiering logic in Steps 9 and 10 doesn't change.

What's the Care Package weapon rotation, and does it change my scoring?
Care Package weapons like the L-STAR only appear from supply drops rather than floor loot, so their stats, including Season 29's 19-to-20 damage bump, should never be averaged against ground-loot weapon baselines in the same tier band.

How do I know if my headshot multiplier values are current?
Cross-check them against a dedicated stats source before every scoring run, since these values can shift with balance patches and aren't reliably listed in every official patch note.

Is a Python script really more reliable than reading a published tier list?
It's not automatically more accurate, but it's auditable. Every number in your output traces back to a source you recorded, which is the main thing missing from most tier lists you'll find with a quick search.

Should I score weapons separately for ranked and casual play?
If you play both modes seriously, yes. Ranked fights tend to run longer and reward sustained accuracy, which favors different weapons than fast, chaotic pubs engagements where raw close-range TTK matters more than precision.

What should I do when a midseason patch only changes one or two weapons?
Re-run the full pipeline anyway rather than hand-editing the output. A single weapon's tier shift can move the normalization range in Step 9 slightly, which can nudge every other weapon's score even when their underlying stats didn't change.