16 Commits 5ccaffe4da ... 326e00ddfe

Author SHA1 Message Date
  caryoscelus 326e00ddfe DJII: fix profession item spawning, show profession in status, item descriptions 10 months ago
  caryoscelus 1b59f21aa7 Fix Select index 10 months ago
  caryoscelus 9256a6b184 DJII: basic inventory 10 months ago
  caryoscelus fcb11828b1 DJII: floor description 10 months ago
  caryoscelus 3c6f4e2067 Select component: allow access by index 10 months ago
  caryoscelus 79e3f9fc59 Inventory improvements 10 months ago
  caryoscelus 4e9b43dc1e DJII: attacking WIP 10 months ago
  caryoscelus 265074cba7 DJII: bit of intro 10 months ago
  caryoscelus f5a5b23f3d DJII: basic map zoom 10 months ago
  caryoscelus 7f85748657 DJII: thugs WIP 10 months ago
  caryoscelus 2514caf450 Use TS-style notNullish 10 months ago
  caryoscelus 001c81fb5e DJII: Skills 10 months ago
  caryoscelus 842d22a4ee Generalize ItemList 10 months ago
  caryoscelus b07a68561e DJII CharStatus WIP 10 months ago
  caryoscelus 40c23111b4 Support Pos3 in entity direction indicator 10 months ago
  caryoscelus fd4b8b178f Hide non-graphic entities from entity list 10 months ago

+ 13 - 4
src/lib/inventory.ts

@@ -18,12 +18,11 @@ export const Stored = defineComponent('Stored', {
   parent: Types.eid,
 });
 
-export const getInventory = (world) => {
-  const pc = playerQuery(world)[0];
-  const sz = Inventory.count[pc];
+export const getInventory = (world, char) => {
+  const sz = Inventory.count[char];
   let res = [];
   for (let i = 0; i < sz; ++i) {
-    res.push(Inventory.inv[pc][i]);
+    res.push(Inventory.inv[char][i]);
   };
   return res;
 };
@@ -74,6 +73,16 @@ export const DropAction = (item) => (world, e) => {
 
 registerAction([Stored], 'drop', DropAction);
 
+export const addToInventory = (world, e, item) => {
+  const ix = Inventory.count[e];
+  if (ix >= Inventory.size[e]) {
+    return false;
+  }
+  Inventory.inv[e][ix] = item;
+  ++Inventory.count[e];
+  return true;
+};
+
 export const removeFromInventory = (world, e, item) => {
   const ix = Inventory.inv[e].indexOf(item);
   if (ix < 0) {

+ 0 - 2
src/lib/simulate.ts

@@ -60,7 +60,6 @@ export const stepActors = (world, cmd) => {
         stepActor(world, e, cmd);
         cmd = undefined;
       } else if (hasComponent(world, AI, e)) {
-        console.log('AI');
         const action = findAIAction(world, e);
         if (action) {
           stepActor(world, e, action);
@@ -69,7 +68,6 @@ export const stepActors = (world, cmd) => {
           Clock.time[e] += 1;
         }
       } else {
-        console.log('Neither player nor AI');
         for (let component of getEntityComponents(world, e)) {
           const action = getOnTickActive(component);
           if (action) {

+ 4 - 0
src/lib/util.ts

@@ -16,3 +16,7 @@ export const makeEnum = (...names) => {
   });
   return Object.freeze(obj);
 };
+
+export function notNullish<T>(v: T | null | undefined): v is NonNullable<T> {
+    return v !== null && v !== undefined;
+}

+ 18 - 0
src/routes/Counter.svelte

@@ -0,0 +1,18 @@
+<script>
+  let { label, amount, plus, minus } = $props();
+
+  const onkeydown = (ev) => {
+    // TODO: support common custom arrow bindings
+    if (ev.key === '+' || ev.key === 'ArrowRight') {
+      plus();
+    } else if (ev.key === '-' || ev.key === 'ArrowLeft') {
+      minus();
+    }
+  };
+</script>
+
+<div tabindex="0" {onkeydown}>
+  {label} {amount}
+  <button tabindex="-1" onclick={minus}>-</button>
+  <button tabindex="-1" onclick={plus}>+</button>
+</div>

+ 20 - 5
src/routes/EntityLine.svelte

@@ -1,19 +1,30 @@
 <script>
   import { describe, getPlayer } from '$lib';
   import { Glyph } from '$lib/components';
+  import { hasComponent } from '$lib/ecs';
   import { getPos2, hasPos2 } from '$lib/pos2';
+  import { getPos3, hasPos3 } from '$lib/pos3';
   import { decode } from '$lib/util';
 
   let { item, world, onclick, selected, zoomlevel } = $props();
   let selectedClass = $derived(item === selected ? 'selected' : '');
+  let emptyClass = $derived((!debug && !hasComponent(world, Glyph, item)) ? 'empty' : '');
   let pc = $derived(getPlayer(world));
   let style = $derived(zoomlevel ? `font-size: ${zoomlevel*0.6}px` : '');
+  const debug = false;
 
   const describeDirection = (e) => {
-    if (!hasPos2(world, e))
-      return '';
-    const {x, y} = getPos2(e);
-    const pov = getPos2(pc);
+    let xy, pov;
+    if (hasPos2(world, e)) {
+      xy = getPos2(e);
+      pov = getPos2(pc);
+    } else if (hasPos3(world, e)) {
+      xy = getPos3(e);
+      pov = getPos3(pc);
+    } else {
+      return;
+    }
+    const {x, y} = xy;
     const dx = x - pov.x;
     const dy = y - pov.y;
     if (dx === 0 && dy === 0) {
@@ -33,7 +44,7 @@
   };
 </script>
 
-<div class="envitem {selectedClass}" onclick={() => onclick(item)}>
+<div class="envitem {selectedClass} {emptyClass}" onclick={() => onclick(item)}>
   <pre class="glyph" style="{style}; color: {decode(Glyph.color[item])};">{decode(Glyph.glyph[item])} </pre>
   <span {style}>
     {describe(world, item)}
@@ -52,4 +63,8 @@
   .overlay {
     margin-left: auto;
   }
+
+  .empty {
+    display: none;
+  }
 </style>

+ 42 - 0
src/routes/ItemList.svelte

@@ -0,0 +1,42 @@
+<script>
+  let widget;
+
+  const onkeydown = (ev) => {
+    // TODO: maybe there's a better way to change focus?
+    if (ev.key === 'ArrowUp') {
+      for (let child of widget.children) {
+        if (document.activeElement === child) {
+          let el = child;
+          // we have to skip comment elements inserted by svelte
+          while (el = el.previousSibling) {
+            // UGH, this depends on Stat being div
+            if (el.nodeName.toLowerCase() === 'div') {
+              el.focus();
+              break;
+            }
+          }
+          break;
+        }
+      }
+    } else if (ev.key === 'ArrowDown') {
+      for (let child of widget.children) {
+        if (document.activeElement === child) {
+          let el = child;
+          // we have to skip comment elements inserted by svelte
+          while (el = el.nextSibling) {
+            // UGH, this depends on Stat being div
+            if (el.nodeName.toLowerCase() === 'div') {
+              el.focus();
+              break;
+            }
+          }
+          break;
+        }
+      }
+    }
+  };
+</script>
+
+<div {onkeydown} bind:this={widget}>
+  <slot/>
+</div>

+ 7 - 4
src/routes/Select.svelte

@@ -1,5 +1,5 @@
 <script>
-  let { options, value } = $props();
+  let { options, index, value } = $props();
 
   const onkeydown = (ev) => {
     // TODO: support custom controls
@@ -7,9 +7,11 @@
     if (ev.key === 'ArrowUp') {
       const newIx = Math.max(0, ix-1);
       value = options[newIx];
+      index = newIx;
     } else if (ev.key === 'ArrowDown') {
       const newIx = Math.min(options.length-1, ix+1);
       value = options[newIx];
+      index = newIx;
     }
   };
 
@@ -20,14 +22,15 @@
     return '';
   };
 
-  const select = (option) => {
+  const select = (option, ix) => {
     value = option;
+    index = ix;
   };
 </script>
 
 <ul tabindex="0" {onkeydown}>
-  {#each options as option}
-    <li class="selectable {selectedClass(option)}" onclick={() => select(option)}>
+  {#each options as option, ix}
+    <li class="selectable {selectedClass(option)}" onclick={() => select(option, ix)}>
       {option.name}
     </li>
   {/each}

+ 15 - 0
src/routes/djii/BodyPartIcon.svelte

@@ -0,0 +1,15 @@
+<script>
+  let { health, selected, select, part } = $props();
+  let selectedClass = $derived(selected === part ? 'selected' : '');
+  let extraStyle = $derived(`color: color-mix(in lch, #090 ${health[part]*100}%, #900)`);
+</script>
+
+<span class="bodypart {selectedClass}" style={extraStyle} onclick={select(part)}>
+  <slot />
+</span>
+
+<style>
+  .bodypart {
+    /* font-weight: bold; */
+  }
+</style>

+ 9 - 9
src/routes/djii/CharCreation.svelte

@@ -4,9 +4,10 @@
   import { onMount } from 'svelte';
   import Select from '../Select.svelte';
   import { newGame } from './game';
-  import { enumProfessions } from './profession';
+  import { applyProfession, enumProfessions } from './profession';
   import { enumSkills } from './skill';
   import Stats from './Stats.svelte';
+  import Skills from './Skills.svelte';
 
   let { game } = $props();
 
@@ -22,6 +23,7 @@
   const professions = enumProfessions();
   const skills = enumSkills();
 
+  let professionIx = $state(0);
   let selectedProfession = $state(professions[0]);
 
   // skip char-creation (for debug purposes)
@@ -43,12 +45,15 @@
     addComponent(game0.world, Name, game0.pc, {
       name: playerName,
     });
+    applyProfession(game0.world, game0.pc, professions[professionIx]);
+    // NOTE: line below doesn't work because of Svelte reactivity conflict with bitECS
+    /* applyProfession(game0.world, game0.pc, selectedProfession); */
     game = game0;
   };
 
   const finishCharacter = () => {
     /*
-    if (freePoints > 0) {
+    if (freeStatPoints > 0) {
       error = 'Free stat points left!';
       return;
     }
@@ -62,18 +67,13 @@
   <input bind:this={charNameWidget} bind:value={playerName} /> the {selectedProfession.name}
   <div>
     <h3>Profession</h3>
-    <Select options={professions} bind:value={selectedProfession} />
+    <Select options={professions} bind:value={selectedProfession} bind:index={professionIx} />
   </div>
   <div>
     <h3>Hobby</h3>
   </div>
   <Stats game={game0} bind:tick />
-  <div>
-    <h3>Skills</h3>
-    {#each skills as [name, skill]}
-      {name}
-    {/each}
-  </div>
+  <Skills game={game0} bind:tick />
   <div>{error}</div>
   <button onclick={finishCharacter}>Ok</button>
 </section>

+ 0 - 0
src/routes/djii/CharStatus.svelte


Some files were not shown because too many files changed in this diff