DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

3 4

Open Source Adventures: Episode 57: Extending BATTLETECH Weapon app

Time to improve the app. Here are the main features for this episode:

  • show stability damage
  • include stability damage and minimum range in the form
  • show weapon type
  • show indirect fire indicator - with πŸš€ emoji
  • clean up display a bit

Slider.svelte

I added extra property step to the component. In Svelte to mark property as optional you need to give it a default value, even if it's undefined. Not doing this just results in a warning.

<script>
export let label, min, max, value, format, step = undefined
let id = Math.random().toString(36).slice(2)
</script>

<label for={id}>{label}:</label>
<input type="range" {min} {max} {step} bind:value id={id} />
<span>{format(value)}</span>
Enter fullscreen mode Exit fullscreen mode

Form.svelte

Form now has three extra sliders, and uses step for them and also for heat compensation.

Arguably now that I have this functionality, I might want to switch 0-100 sliders to 0-1 range with 0.01 step. Right now it's a mix of both.

<script>
import Slider from "./Slider.svelte"

export let ammoRounds
export let heatPercentage
export let doubleHeatSinksPercentage
export let rangeAtLeast
export let damageValue
export let stabDamageValue
</script>

<form>
  <Slider label="Ammo for how many rounds" bind:value={ammoRounds} min={1} max={30} format={(v) => `${v}`}/>
  <Slider label="Heat to compensate for" bind:value={heatPercentage} min={0} max={100} step={5} format={(v) => `${v}%`}/>
  <Slider label="How many double heat sinks" bind:value={doubleHeatSinksPercentage} min={0} max={100} format={(v) => `${v}%`}/>
  <Slider label="Normal damage value" bind:value={damageValue} min={0} max={1} step={0.01} format={(v) => `${v}`}/>
  <Slider label="Stability damage value" bind:value={stabDamageValue} min={0} max={1} step={0.01} format={(v) => `${v}`}/>
  <Slider label="Range at least" bind:value={rangeAtLeast} min={90} max={720} step={30} format={(v) => `${v}m`}/>
</form>

<style>
form {
  display: grid;
  grid-template-columns: auto auto auto;
  margin-bottom: 1em;
}
</style>
Enter fullscreen mode Exit fullscreen mode

App.svelte

All the extra functionality didn't really complicate the code too much, but at some point we might want to refactor things out.

I moved rounding to two decimal digits functionality into Row component, as doing it here caused some minor rounding issues, and SRM2/SRM4/SRM6 had slightly different values due to too much rounding.

<script>
import {sortBy} from "lodash"
import data from "./data.json"
import Form from "./Form.svelte"
import Headers from "./Headers.svelte"
import Row from "./Row.svelte"

let ammoRounds = 10
let heatPercentage = 80
let doubleHeatSinksPercentage = 0
let rangeAtLeast = 90
let damageValue = 1.0
let stabDamageValue = 0.5

$: heatSinkingPerTon = 3.0 + 3.0 * doubleHeatSinksPercentage / 100
$: costPerHeat = (heatPercentage / 100) / heatSinkingPerTon

let sortedData
$: {
  for (let row of data) {
    row.value = row.shots * (row.baseDamage * damageValue + row.baseStabDamage * stabDamageValue)
    row.ammoWeight = ammoRounds * row.ammoTonnagePerShot
    row.cost = row.tonnage + row.ammoWeight + row.heat * costPerHeat
    row.ratio = row.value / row.cost
    row.id = Math.random().toString(36).slice(2)
  }
  sortedData = sortBy(data, [(x) => -x.ratio, (x) => x.name])
}
</script>

<h1>BATTLETECH Weapons Data</h1>

<Form bind:ammoRounds bind:heatPercentage bind:doubleHeatSinksPercentage bind:rangeAtLeast bind:damageValue bind:stabDamageValue />

<table>
  <Headers />
  {#each sortedData as row (row.id)}
    {#if row.maxRange >= rangeAtLeast}
      <Row data={row} />
    {/if}
  {/each}
</table>

<style>
:global(body) {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
}
table :global(tr):nth-child(even) {
  background-color: #f2f2f2;
}
table :global(tr):nth-child(odd) {
  background-color: #e0e0e0;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Headers.svelte

Nothing too exciting about this one:

<tr>
  <th>Name</th>
  <th>Bonus</th>
  <th>Type</th>
  <th>Damage</th>
  <th>Stab Damage</th>
  <th>Heat</th>
  <th>Weight</th>
  <th>Ammo Weight</th>
  <th>Range</th>
  <th>Value</th>
  <th>Cost</th>
  <th>Ratio</th>
</tr>
Enter fullscreen mode Exit fullscreen mode

Row.svelte

There's some more funcitonality here. Perhaps typeSymbol should be moved to the data exporter, I didn't notice that Support weapons are marked as AntiPersonnel in game files.

I'm blanking zero fields to improve readability, but not the weight field for MG++.

<script>
export let data

let round100 = (v) => Math.round(v * 100) / 100

let {baseName, bonus, category, baseStabDamage, heat, shots, baseDamage, tonnage, maxRange, value, cost, ratio, ammoWeight, indirectFire} = data
let damage, stabDamage
if (shots == 1) {
  damage = baseDamage
  stabDamage = baseStabDamage
} else {
  damage = `${shots}x${baseDamage}`
  stabDamage = `${shots}x${baseStabDamage}`
}
if (heat == 0) {
  heat = ""
}
if (baseStabDamage == 0) {
  stabDamage = ""
}
if (ammoWeight == 0) {
  ammoWeight = ""
}
let typeSymbol = (category == "AntiPersonnel") ? "S" : category.substring(0, 1)
</script>

<tr>
  <td>{baseName}</td>
  <td>{bonus}</td>
  <td>{typeSymbol}</td>
  <td>{damage}</td>
  <td>{stabDamage}</td>
  <td>{heat}</td>
  <td>{tonnage}</td>
  <td>{round100(ammoWeight)}</td>
  <td>
    {maxRange}m
    {#if indirectFire}
      πŸš€
    {/if}
  </td>
  <td>{round100(value)}</td>
  <td>{round100(cost)}</td>
  <td>{round100(ratio)}</td>
</tr>
Enter fullscreen mode Exit fullscreen mode

Story so far

All the code is on GitHub.

I deployed this on GitHub Pages, you can see it here.

Coming next

I think the app is pretty good, so in the next episode I'll move on to something else.

πŸ‘‹ While you are here

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

πŸ‘‹ Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay