~starkingdoms/starkingdoms

aaff9ada807be1ffdfb96948a43ea3394d3caf98 — core 1 year, 11 months ago 6d993bc
part editor functionality
M starkingdoms-client/src/components/ui/Button.svelte => starkingdoms-client/src/components/ui/Button.svelte +12 -1
@@ 5,9 5,17 @@
  export let disabled = false;
  export let style = "";
  export let variant = "normal";
  export let selected = false;
</script>

<button {id} class="btn-{variant} {clazz}" {disabled} on:click on:focus {style}>
<button
  {id}
  class="btn-{variant} {clazz}"
  {disabled}
  on:click
  on:focus
  {style}
  class:btn-selected={selected}>
  <slot />
</button>



@@ 34,6 42,9 @@
    @extend %standard-hover;
    background-color: var(--links-transparent);
  }
  .btn-selected {
    background-color: var(--links-semitransparent);
  }

  .btn-danger {
    @extend %standard;

M starkingdoms-client/src/css/themes/catppuccin-mocha.scss => starkingdoms-client/src/css/themes/catppuccin-mocha.scss +1 -0
@@ 49,6 49,7 @@
  --subtle: rgb(var(--overlay1));
  --links: rgb(var(--blue));
  --links-transparent: rgba(var(--blue), 0.35);
  --links-semitransparent: rgba(var(--blue), 0.45);
  --links-ultratransparent: rgba(var(--blue), 0.15);
  --success: rgb(var(--green));
  --warning: rgb(var(--yellow));

M starkingdoms-client/src/pages/ShipEditor.svelte => starkingdoms-client/src/pages/ShipEditor.svelte +92 -7
@@ 10,6 10,7 @@
  import { onMount } from "svelte";
  import { part_texture_url } from "../textures.ts";
  import Button from "../components/ui/Button.svelte";
  import WarningIcon from "../icons/WarningIcon.svelte";

  let config = DEFAULT_CONFIG;
  // Top-level await. Sets the default config, and overwrites it when the new config is avail. Thanks reactivity!


@@ 17,6 18,8 @@
    config = await loadConfig();
  })();

  let selected: PartType | null = null;

  const logger = createDebug("main");
  logger(
    `Hello, world! StarKingdoms ${APP_VERSION} (${COMMIT_HASH}) at your service!`,


@@ 54,11 57,6 @@
  let last_cx = -1;
  let last_cy = -1;

  function mousedown(e: MouseEvent) {
    isdragging = true;
    last_cx = e.clientX;
    last_cy = e.clientY;
  }
  function mouseup(e) {
    isdragging = false;
  }


@@ 104,6 102,28 @@
  let textures: Map<PartType, HTMLImageElement> = new Map();
  let context: CanvasRenderingContext2D | null = null;

  let cantplace_reason: string | null = null;

  function canPlace(): boolean {
    if (selected === null) {
      cantplace_reason = "No part selected";
      return false;
    }
    if (grid.get(grid_x)?.has(grid_y)) {
      cantplace_reason = "Part already exists in this slot";
      return false;
    }
    let counts = part_counts.get(selected)!;
    if (counts.used === counts.available) {
      cantplace_reason = "Out of this part";
      return false;
    }
    cantplace_reason = null;
    return true;
  }

  $: selected, grid, canPlace();

  function render(
    ctx: CanvasRenderingContext2D | null,
    x: number,


@@ 146,7 166,15 @@
    });

    ctx.beginPath();
    ctx.strokeStyle = "rgb(166, 227, 161)";

    if (selected === null) {
      ctx.strokeStyle = "rgb(24, 24, 37)";
    } else if (canPlace()) {
      ctx.strokeStyle = "rgb(166, 227, 161)";
    } else {
      ctx.strokeStyle = "rgb(243, 139, 169)";
    }

    ctx.lineWidth = 2;
    ctx.strokeRect(
      grid_x * grid_size + x,


@@ 241,12 269,60 @@
      confirm_save = false;
    }, 5000);
  }

  function toggle_select(type: PartType) {
    console.log(type);
    if (selected == type) {
      selected = null;
    } else {
      selected = type;
    }
  }

  function mousedown(e: MouseEvent) {
    if (e.button === 0) {
      if (canPlace()) {
        if (!grid.has(grid_x)) {
          grid.set(grid_x, new Map());
        }
        grid.get(grid_x)!.set(grid_y, selected!);
        part_counts.set(selected!, {
          used: part_counts.get(selected!)!.used + 1,
          available: part_counts.get(selected!)!.available,
        });
        part_counts = part_counts;
        grid = grid;
      }

      // This gets unset immediately if a normal click
      isdragging = true;
      last_cx = e.clientX;
      last_cy = e.clientY;
    } else if (e.button == 2) {
      // removal
      if (grid.get(grid_x)?.has(grid_y)) {
        let type = grid.get(grid_x)!.get(grid_y)!;
        grid.get(grid_x)?.delete(grid_y);
        part_counts.set(type, {
          used: part_counts.get(type)!.used - 1,
          available: part_counts.get(type)!.available,
        });
        part_counts = part_counts;
        grid = grid;
      }
    }
  }
</script>

<Popup title="Parts" id="parts" draggable minimizable style="width: 15em;">
  {#each part_counts.entries() as [type, counts]}
    {#if type !== PartType.Hearty}
      <Button style="margin-top: 5px; width: 100%;">
      <Button
        selected={type === selected}
        style="margin-top: 5px; width: 100%;"
        on:click={() => {
          toggle_select(type);
        }}>
        <img
          style="vertical-align: middle;"
          src={part_texture_url(type, true)}


@@ 257,6 333,12 @@
      </Button>
    {/if}
  {/each}
  {#if cantplace_reason !== null}
    <span class="server-danger">
      <WarningIcon class="server-danger-icon" />
      Cannot place: {cantplace_reason}
    </span>
  {/if}
</Popup>

<Popup


@@ 300,6 382,9 @@
  on:mouseup|preventDefault={mouseup}
  on:mousemove|preventDefault={mousemove}
  on:wheel|preventDefault={handleWheel}
  on:contextmenu|preventDefault={() => {
    return false;
  }}
  style="background-size: {grid_size}px {grid_size}px; background-position: {x}px {y}px;"
  bind:this={canvas} />