Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
78cb94d
overlay: close-on-backdrop-tap, pickup UX polish
Feb 8, 2026
b889e6e
fov, dungeon gen, grid los, dungeon
Feb 8, 2026
42d5fbf
dungeon gen fix
Feb 8, 2026
7c485d2
ranged attack, equipment loader,
Feb 8, 2026
3c5ee6e
blastwave and meteor are back
Feb 8, 2026
aa7e4a3
bye bye node, helo deno
Feb 8, 2026
ed015f1
fixing tests
Feb 8, 2026
8e31488
more tests ;-)
Feb 8, 2026
c559543
more tests
Feb 8, 2026
4e81e70
continued
Feb 8, 2026
124e7cd
bye bye materials
Feb 8, 2026
065364f
fully porting to `deno test --alow-read`
Feb 8, 2026
a24864d
'f' to cast active spell
Feb 8, 2026
c92e16f
adding debugging items to demo room
Feb 8, 2026
dcc884b
affixes procs
Feb 8, 2026
1d352b9
more affix procs
Feb 8, 2026
062ebbc
early dungeon gen, overloaded
Feb 8, 2026
c4bcbdc
dungeon gen-2
Feb 8, 2026
b1372f8
adding chunk load budget, still plenty to do, perf getting better
Feb 8, 2026
cbce369
FOV re-introduced, based on brain::vision
Feb 8, 2026
4594a61
FOV continued (perf focus)
Feb 8, 2026
8c8a6ad
fov keys and packing
Feb 8, 2026
be47676
adding example of packKey32
Feb 8, 2026
a9f8ade
small-fix
Feb 8, 2026
8b6e6ee
checkpoint rm chunk system, level tunes
Feb 8, 2026
202c489
perf tunes -- cdx
Feb 8, 2026
1eb317a
perf tuning cont
Feb 8, 2026
ff7f462
FIXED: monster on open door combat issue
Feb 8, 2026
8f6529a
adding some basic monsters
Feb 9, 2026
f5c4844
adding monster speed and monster scripts
Feb 9, 2026
145f8de
weekend continued porting features from archive
Feb 9, 2026
17ef1d3
equippable ammo type
Feb 9, 2026
956077e
Merge pull request #17 from PJensen/features/cc-dungeon-generator
PJensen Feb 9, 2026
214dc19
adding deity-js submodule
Feb 11, 2026
dc548fa
rm debug item pop, fix chest glyph
Feb 11, 2026
c1dd47d
fixing chips
Feb 11, 2026
83a899d
early revisions of shopkeeper and diety
Feb 11, 2026
c671dba
adjusting loot tables
Feb 11, 2026
9e5d0d6
adjust shopkeeper glyph
Feb 11, 2026
52572b2
adjust neglect counter early
Feb 11, 2026
daf091d
update
Feb 11, 2026
f2a45fd
FIXED: small dupe import
Feb 11, 2026
8a27b0b
dial down deity spam
Feb 11, 2026
b997d73
kills are offerings
Feb 11, 2026
b3cb928
adjusting base
Feb 11, 2026
2d743bc
small tunings
Feb 11, 2026
d06f377
adjusting loot continued
Feb 11, 2026
fb0ce02
rm debug overlay vis
Feb 11, 2026
12fc139
fixing chests
Feb 11, 2026
e23d2a5
basic engraving, basic pets
Feb 12, 2026
accf7b8
scuffed engravings
Feb 12, 2026
fa19743
kitty behavior
Feb 12, 2026
82d0e05
a gift
Feb 12, 2026
95cceaa
Add shared pet placement helper for spawn and teleport
PJensen Feb 13, 2026
21d6b1e
Merge pull request #18 from PJensen/features/add-tile-finding-helper-…
PJensen Feb 13, 2026
ea40d66
Keep kitty deliveries on visible adjacent ground
PJensen Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
path = src/lib/ecs-js
url = https://github.com/PJensen/ecs-js.git
branch = main
[submodule "src/lib/deity-js"]
path = src/lib/deity-js
url = https://github.com/PJensen/Deity-js.git
29 changes: 27 additions & 2 deletions app/input/rulesDispatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// App-owned translation from display/input Actions → rules intents on the ECS world.
// This file is allowed to import rules and the ECS World (per Separation Manifest).

import { MoveIntent, WaitIntent, DrinkIntent, CastSpellIntent, PickupIntent, EquipIntent, Position, ItemInfo } from "../../src/rules/components/index.js";
import { MoveIntent, WaitIntent, DrinkIntent, CastSpellIntent, PickupIntent, EquipIntent, RangedAttackIntent, EngraveIntent, Position, ItemInfo } from "../../src/rules/components/index.js";
import { UseIntent } from "../../src/rules/components/Intents/UseIntent.js";
import { itemsAt } from "../../src/rules/utils/queries.js";

Expand Down Expand Up @@ -36,7 +36,13 @@ export function makeRulesDispatcher(world, getActorId) {
break;
}
case "rules.castActiveSpell": {
const { spellId = 0, targetId = actorId } = action.payload || {};
const { spellId, targetId = actorId } = action.payload || {};
if (!spellId) {
// No spell specified (keyboard shortcut); delegate to app-side
// active spell resolution via the same path as the Cast button.
try { window.dispatchEvent(new CustomEvent('ui:castActiveSpell')); } catch {}
break;
}
world?.add?.(actorId, CastSpellIntent, { spellId, targetId });
world?.tick?.(1);
break;
Expand All @@ -55,6 +61,25 @@ export function makeRulesDispatcher(world, getActorId) {
world?.tick?.(1);
break;
}
case "rules.shootRanged": {
// No explicit target; delegate to app-side auto-targeting
try { window.dispatchEvent(new CustomEvent('ui:shootRanged')); } catch {}
break;
}
case "rules.rangedAttack": {
const { targetId = 0, toX = 0, toY = 0 } = action.payload || {};
if (!Number.isInteger(targetId) || targetId <= 0) break;
world?.add?.(actorId, RangedAttackIntent, { targetId, toX, toY });
world?.tick?.(1);
break;
}
case "rules.engrave": {
const { text = "" } = action.payload || {};
if (!text) break;
world?.add?.(actorId, EngraveIntent, { text });
world?.tick?.(1);
break;
}
case "rules.pickupItem": {
// Determine which item to pick up: prefer payload.itemId; otherwise choose a ground item at actor's tile.
const { itemId = 0, count = null } = action.payload || {};
Expand Down
37 changes: 28 additions & 9 deletions app/rules/scheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@
// Register rules systems into phases and set the world scheduler.

import { composeScheduler, registerSystem, clearSystems, getOrderedSystems } from "../../src/lib/ecs-js/index.js";
/** @typedef {import('../../src/lib/ecs-js').World} World */
/** @typedef {import('../../src/lib/ecs-js/index.js').World} World */
import { drinkSystem } from "../../src/rules/systems/drinkSystem.js";
import { itemPickupSystem, autoPickupPostMoveSystem } from "../../src/rules/systems/itemPickupSystem.js";
import { itemDropSystem } from "../../src/rules/systems/itemDropSystem.js";
import { equipItemSystem } from "../../src/rules/systems/equipItemSystem.js";
import { useItemSystem } from "../../src/rules/systems/useItemSystem.js";
import { projectileSystem } from "../../src/rules/systems/projectileSystem.js";
import { rangedAttackSystem } from "../../src/rules/systems/rangedAttackSystem.js";
import { interactionSystem } from "../../src/rules/systems/interactionSystem.js";
import { effectSystem } from "../../src/rules/systems/effectSystem.js";
import { equipmentSystem } from "../../src/rules/systems/equipmentSystem.js";
import { waitSystem } from "../../src/rules/systems/waitSystem.js";
import { castSpellSystem } from "../../src/rules/systems/castSpellSystem.js";
import { aiChaseSystem } from "../../src/rules/systems/aiChaseSystem.js";
import { petFollowSystem } from "../../src/rules/systems/petFollowSystem.js";
import { movementSystem } from "../../src/rules/systems/movementSystem.js";
import { combatSystem } from "../../src/rules/systems/combatSystem.js";
import { installAffixTriggers } from "../../src/rules/systems/affixTriggerSystem.js";
import { cleanupSystem } from "../../src/rules/systems/cleanupSystem.js";
import { trapSystem } from "../../src/rules/systems/trapSystem.js";
import { manaRegenerationSystem } from "../../src/rules/systems/manaRegenerationSystem.js";
import { monsterSpawnerSystem } from "../../src/rules/systems/monsterSpawnerSystem.js";
// Side-effect: registers trap script handlers at import time
import { spatialIndexSystem } from "../../src/rules/systems/spatialIndexSystem.js";
import { deitySystem } from "../../src/rules/systems/deitySystem.js";
import { engraveSystem, installEngraveListeners } from "../../src/rules/systems/engraveSystem.js";
// Side-effect: registers script handlers at import time
import "../../src/rules/scripts/traps.js";
import "../../src/rules/scripts/monsters.js";

/**
* @param {World} world
Expand All @@ -32,19 +38,26 @@ export function configureWorld(world) {

// Install affix event listeners once per world
installAffixTriggers(world);
// Install engraving scramble-on-step listener once per world
installEngraveListeners(world);

// Phase: intents (consume queued intents)
// Producers first (AI), then consumers (movement, interactions, etc.)
registerSystem(aiChaseSystem, 'intents');
registerSystem(petFollowSystem, 'intents');
registerSystem(waitSystem, 'intents');
registerSystem(drinkSystem, 'intents');
registerSystem(useItemSystem, 'intents');
registerSystem(equipItemSystem, 'intents');
registerSystem(itemDropSystem, 'intents');
registerSystem(projectileSystem, 'intents');
registerSystem(interactionSystem, 'intents');
registerSystem(rangedAttackSystem, 'intents');
registerSystem(castSpellSystem, 'intents');
registerSystem(engraveSystem, 'intents');
registerSystem(movementSystem, 'intents');
// interactionSystem must run AFTER movementSystem: bump-to-interact adds
// InteractIntent during movement; processing it in the same tick prevents
// the shop overlay from re-firing on every subsequent action.
registerSystem(interactionSystem, 'intents');
registerSystem(combatSystem, 'intents');
// Run pickup after movement so stepping onto items can pick them up immediately
registerSystem(itemPickupSystem, 'intents');
Expand All @@ -54,13 +67,18 @@ export function configureWorld(world) {
// Phase: effects (derived first, then per-turn effects)
registerSystem(equipmentSystem, 'effects');
registerSystem(effectSystem, 'effects');
registerSystem(manaRegenerationSystem, 'effects');
// Post-move auto-pickup runs after intents, within the same tick
registerSystem(autoPickupPostMoveSystem, 'effects');
// Spawners tick in the effects phase
registerSystem(monsterSpawnerSystem, 'effects');
// Deity mood ticks in the effects phase (after combat results are emitted)
registerSystem(deitySystem, 'effects');

// Phase: cleanup (end-of-turn removals like killing entities with hp <= 0)
registerSystem(cleanupSystem, 'cleanup');
// Keep spatial index in sync after structural changes
registerSystem(spatialIndexSystem, 'cleanup');

// Compose scheduler: order of phases matters
const baseScheduler = composeScheduler('intents', 'effects', 'cleanup');
Expand All @@ -71,8 +89,8 @@ export function configureWorld(world) {
}

// Build profiled scheduler: measure per system and per phase using high-res timer
/** @type {Array<'intents'|'effects'>} */
const phases = ['intents', 'effects'];
/** @type {Array<'intents'|'effects'|'cleanup'>} */
const phases = ['intents', 'effects', 'cleanup'];
/** @type {Record<string, Function[]>} */
const phaseSystems = Object.create(null);
for (const ph of phases) phaseSystems[ph] = getOrderedSystems(ph);
Expand All @@ -89,8 +107,8 @@ export function configureWorld(world) {
let phStart = performance.now();
const sysTimes = [];
for (let i = 0; i < list.length; i++) {
/** @type {Function} */
const fn = /** @type any */ (list[i] || (()=>{}));
/** @type {Function} */
const fn = /** @type any */ (list[i] || (()=>{}));
const s0 = performance.now();
fn(w, dt);
const s1 = performance.now();
Expand All @@ -106,6 +124,7 @@ export function configureWorld(world) {
}

function shouldProfileRules() {
if (typeof window === 'undefined') return false;
const params = new URLSearchParams(window.location.search || '');
const v = (params.get('rulesProfile') || (typeof localStorage !== 'undefined' ? localStorage.getItem('jshack.rulesProfile') : '0') || '0');
return v === '1' || v === 'true' || v === 'on';
Expand Down
Loading
Loading