@inglorious/engine 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/engine",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "A JavaScript game engine written with global state, immutability, and pure functions in mind. Have fun(ctional programming) with it!",
5
5
  "author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "license": "MIT",
@@ -31,12 +31,17 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@inglorious/store": "4.0.0",
34
- "@inglorious/utils": "3.1.0"
34
+ "@inglorious/utils": "3.5.0"
35
+ },
36
+ "peerDependencies": {
37
+ "@inglorious/store": "4.0.0",
38
+ "@inglorious/utils": "3.5.0"
35
39
  },
36
40
  "devDependencies": {
37
41
  "prettier": "^3.5.3",
38
42
  "vite": "^7.1.3",
39
- "vitest": "^1.6.0"
43
+ "vitest": "^1.6.0",
44
+ "@inglorious/eslint-config": "1.0.0"
40
45
  },
41
46
  "engines": {
42
47
  "node": ">= 22"
@@ -1,37 +1,21 @@
1
- import { extend } from "@inglorious/utils/data-structures/objects.js"
2
-
3
1
  import { createGamepad, gamepadListener, gamepadsPoller } from "./gamepad.js"
4
2
  import { createInput, input } from "./input.js"
5
3
  import { createKeyboard, keyboard } from "./keyboard.js"
6
4
 
7
- const DEFAULT_PARAMS = {
8
- name: "input0",
9
- }
10
-
11
- export function setupControls(params) {
12
- params = extend(DEFAULT_PARAMS, params)
13
-
5
+ export function controls(...targetIds) {
14
6
  return {
15
- types: {
16
- keyboard: [keyboard(params)],
17
- gamepads_poller: [gamepadsPoller(params)],
18
- gamepad_listener: [gamepadListener(params)],
19
- input: [input(params)],
20
- },
21
- entities: {
22
- gamepads: { type: "gamepads_poller" },
23
- },
7
+ keyboard: [keyboard()],
8
+ gamepads_poller: [gamepadsPoller(targetIds)],
9
+ gamepad_listener: [gamepadListener()],
10
+ input: [input()],
24
11
  }
25
12
  }
26
13
 
27
- export function controlsEntities(
28
- name = DEFAULT_PARAMS.name,
29
- targetIds,
30
- mapping = {},
31
- ) {
14
+ export function createControls(targetId, mapping = {}) {
32
15
  return {
33
- [`keyboard_${name}`]: createKeyboard(`keyboard_${name}`, name, mapping),
34
- [`gamepad_${name}`]: createGamepad(`gamepad_${name}`, name, mapping),
35
- [name]: createInput(name, targetIds, mapping),
16
+ gamepads: { type: "gamepads_poller" },
17
+ [`keyboard_${targetId}`]: createKeyboard(targetId, mapping),
18
+ [`gamepad_${targetId}`]: createGamepad(targetId, mapping),
19
+ [`input_${targetId}`]: createInput(targetId, mapping),
36
20
  }
37
21
  }
@@ -1,8 +1,4 @@
1
- const DEFAULT_PARAMS = {
2
- name: "gamepad_input0",
3
- }
4
-
5
- export function gamepadsPoller() {
1
+ export function gamepadsPoller(targetIds = []) {
6
2
  return {
7
3
  create(entity, entityId) {
8
4
  if (entityId !== entity.id) return
@@ -12,9 +8,7 @@ export function gamepadsPoller() {
12
8
 
13
9
  update(entity, dt, api) {
14
10
  navigator.getGamepads().forEach((gamepad) => {
15
- if (gamepad == null) {
16
- return
17
- }
11
+ if (gamepad == null) return
18
12
 
19
13
  const cache = (entity.gamepadStateCache[gamepad.index] ??= {
20
14
  axes: [],
@@ -22,12 +16,10 @@ export function gamepadsPoller() {
22
16
  })
23
17
 
24
18
  gamepad.axes.forEach((axis, index) => {
25
- if (axis === cache.axes[index]) {
26
- return
27
- }
19
+ if (axis === cache.axes[index]) return
28
20
 
29
21
  api.notify("gamepadAxis", {
30
- gamepadIndex: gamepad.index,
22
+ targetId: targetIds[gamepad.index],
31
23
  axis: `Axis${index}`,
32
24
  value: axis,
33
25
  })
@@ -40,12 +32,12 @@ export function gamepadsPoller() {
40
32
 
41
33
  if (isPressed && !wasPressed) {
42
34
  api.notify("gamepadPress", {
43
- gamepadIndex: gamepad.index,
35
+ targetId: targetIds[gamepad.index],
44
36
  button: `Btn${index}`,
45
37
  })
46
38
  } else if (!isPressed && wasPressed) {
47
39
  api.notify("gamepadRelease", {
48
- gamepadIndex: gamepad.index,
40
+ targetId: targetIds[gamepad.index],
49
41
  button: `Btn${index}`,
50
42
  })
51
43
  }
@@ -59,58 +51,42 @@ export function gamepadsPoller() {
59
51
 
60
52
  export function gamepadListener() {
61
53
  return {
62
- gamepadAxis(entity, { gamepadIndex, axis, value }, api) {
63
- if (entity.id !== `gamepad_input${gamepadIndex}`) {
64
- return
65
- }
54
+ gamepadAxis(entity, { targetId, axis, value }, api) {
55
+ if (targetId !== entity.targetId) return
66
56
 
67
57
  const action = entity.mapping[axis]
68
- if (!action) {
69
- return
70
- }
58
+ if (!action) return
71
59
 
72
60
  entity[action] = value
73
- api.notify("inputAxis", { controlId: entity.id, action, value })
61
+ api.notify("inputAxis", { targetId, action, value })
74
62
  },
75
63
 
76
- gamepadPress(entity, { gamepadIndex, button }, api) {
77
- if (entity.id !== `gamepad_input${gamepadIndex}`) {
78
- return
79
- }
64
+ gamepadPress(entity, { targetId, button }, api) {
65
+ if (targetId !== entity.targetId) return
80
66
 
81
67
  const action = entity.mapping[button]
82
- if (!action) {
83
- return
84
- }
68
+ if (!action) return
85
69
 
86
70
  if (!entity[action]) {
87
71
  entity[action] = true
88
- api.notify("inputPress", { controlId: entity.id, action })
72
+ api.notify("inputPress", { targetId, action })
89
73
  }
90
74
  },
91
75
 
92
- gamepadRelease(entity, { gamepadIndex, button }, api) {
93
- if (entity.id !== `gamepad_input${gamepadIndex}`) {
94
- return
95
- }
76
+ gamepadRelease(entity, { targetId, button }, api) {
77
+ if (targetId !== entity.targetId) return
96
78
 
97
79
  const action = entity.mapping[button]
98
- if (!action) {
99
- return
100
- }
80
+ if (!action) return
101
81
 
102
82
  if (entity[action]) {
103
83
  entity[action] = false
104
- api.notify("inputRelease", { controlId: entity.id, action })
84
+ api.notify("inputRelease", { targetId, action })
105
85
  }
106
86
  },
107
87
  }
108
88
  }
109
89
 
110
- export function createGamepad(
111
- name = DEFAULT_PARAMS.name,
112
- targetInput,
113
- mapping = {},
114
- ) {
115
- return { id: name, type: "gamepad_listener", targetInput, mapping }
90
+ export function createGamepad(targetId, mapping = {}) {
91
+ return { type: "gamepad_listener", targetId, mapping }
116
92
  }
@@ -1,48 +1,31 @@
1
- const DEFAULT_PARAMS = {
2
- name: "input0",
3
- }
4
-
5
1
  export function input() {
6
2
  return {
7
- inputAxis(entity, { controlId, action, value }, api) {
8
- if (!controlId.endsWith(entity.id)) {
9
- return
10
- }
3
+ inputAxis(entity, { targetId, action, value }, api) {
4
+ if (targetId !== entity.targetId) return
5
+
11
6
  entity[action] = value
12
7
 
13
- entity.targetIds.forEach((targetId) => {
14
- api.notify(action, { entityId: targetId, value })
15
- })
8
+ api.notify(action, { entityId: entity.targetId, value })
16
9
  },
17
10
 
18
- inputPress(entity, { controlId, action }, api) {
19
- if (!controlId.endsWith(entity.id)) {
20
- return
21
- }
11
+ inputPress(entity, { targetId, action }, api) {
12
+ if (targetId !== entity.targetId) return
13
+
22
14
  entity[action] = true
23
15
 
24
- entity.targetIds.forEach((targetId) => {
25
- api.notify(action, targetId)
26
- })
16
+ api.notify(action, entity.targetId)
27
17
  },
28
18
 
29
- inputRelease(entity, { controlId, action }, api) {
30
- if (!controlId.endsWith(entity.id)) {
31
- return
32
- }
19
+ inputRelease(entity, { targetId, action }, api) {
20
+ if (targetId !== entity.targetId) return
21
+
33
22
  entity[action] = false
34
23
 
35
- entity.targetIds.forEach((targetId) => {
36
- api.notify(`${action}End`, targetId)
37
- })
24
+ api.notify(`${action}End`, entity.targetId)
38
25
  },
39
26
  }
40
27
  }
41
28
 
42
- export function createInput(
43
- name = DEFAULT_PARAMS.name,
44
- targetIds = [],
45
- mapping = {},
46
- ) {
47
- return { id: name, type: "input", targetIds, mapping }
29
+ export function createInput(targetId, mapping = {}) {
30
+ return { type: "input", targetId, mapping }
48
31
  }
@@ -1,7 +1,3 @@
1
- const DEFAULT_PARAMS = {
2
- name: "keyboard0",
3
- }
4
-
5
1
  export function keyboard() {
6
2
  let handleKeyDown, handleKeyUp
7
3
  let currentDocument = null
@@ -26,36 +22,28 @@ export function keyboard() {
26
22
 
27
23
  keyboardKeyDown(entity, keyCode, api) {
28
24
  const action = entity.mapping[keyCode]
29
- if (!action) {
30
- return
31
- }
25
+ if (!action) return
32
26
 
33
27
  if (!entity[action]) {
34
28
  entity[action] = true
35
- api.notify("inputPress", { controlId: entity.id, action })
29
+ api.notify("inputPress", { targetId: entity.targetId, action })
36
30
  }
37
31
  },
38
32
 
39
33
  keyboardKeyUp(entity, keyCode, api) {
40
34
  const action = entity.mapping[keyCode]
41
- if (!action) {
42
- return
43
- }
35
+ if (!action) return
44
36
 
45
37
  if (entity[action]) {
46
38
  entity[action] = false
47
- api.notify("inputRelease", { controlId: entity.id, action })
39
+ api.notify("inputRelease", { targetId: entity.targetId, action })
48
40
  }
49
41
  },
50
42
  }
51
43
  }
52
44
 
53
- export function createKeyboard(
54
- name = DEFAULT_PARAMS.name,
55
- targetInput,
56
- mapping = {},
57
- ) {
58
- return { id: name, type: "keyboard", targetInput, mapping }
45
+ export function createKeyboard(targetId, mapping = {}) {
46
+ return { type: "keyboard", targetId, mapping }
59
47
  }
60
48
 
61
49
  function createKeyboardHandler(id, api) {
@@ -2,10 +2,6 @@ import { findCollision } from "@inglorious/engine/collision/detection.js"
2
2
  import { clampToBounds } from "@inglorious/engine/physics/bounds.js"
3
3
  import { zero } from "@inglorious/utils/math/vector.js"
4
4
 
5
- const DEFAULT_PARAMS = {
6
- name: "mouse",
7
- }
8
-
9
5
  const NO_Y = 0
10
6
 
11
7
  export function mouse() {
@@ -49,9 +45,8 @@ export function track(parent, options) {
49
45
  }
50
46
  }
51
47
 
52
- export function createMouse(name = DEFAULT_PARAMS.name, overrides = {}) {
48
+ export function createMouse(overrides = {}) {
53
49
  return {
54
- id: name,
55
50
  type: "mouse",
56
51
  layer: 999, // A high layer value to ensure it's always rendered on top
57
52
  position: zero(),
@@ -1,3 +1,7 @@
1
+ import {
2
+ deserialize,
3
+ serialize,
4
+ } from "@inglorious/utils/data-structures/object.js"
1
5
  import { extend } from "@inglorious/utils/data-structures/objects.js"
2
6
 
3
7
  import { coreEvents } from "../core-events.js"
@@ -33,7 +37,7 @@ export function multiplayerMiddleware(config = {}) {
33
37
  if (!event.fromServer) {
34
38
  if (ws?.readyState === WebSocket.OPEN) {
35
39
  // If the connection is open, send the event immediately.
36
- ws.send(JSON.stringify(event))
40
+ ws.send(serialize(event))
37
41
  } else {
38
42
  // If the connection is not open, queue the event for later.
39
43
  localQueue.push(event)
@@ -62,12 +66,12 @@ export function multiplayerMiddleware(config = {}) {
62
66
  ws.onopen = () => {
63
67
  // Send any queued events to the server.
64
68
  while (localQueue.length) {
65
- ws.send(JSON.stringify(localQueue.shift()))
69
+ ws.send(serialize(localQueue.shift()))
66
70
  }
67
71
  }
68
72
 
69
73
  ws.onmessage = (event) => {
70
- const serverEvent = JSON.parse(event.data)
74
+ const serverEvent = deserialize(event.data)
71
75
 
72
76
  if (serverEvent.type === "initialState") {
73
77
  // Merge the server's initial state with the client's local state.
@@ -1,20 +0,0 @@
1
- import { extend } from "@inglorious/utils/data-structures/objects.js"
2
-
3
- const DEFAULT_PARAMS = {
4
- onState: "default",
5
- }
6
-
7
- export function collidable(params) {
8
- params = extend(DEFAULT_PARAMS, params)
9
-
10
- return (type) =>
11
- extend(type, {
12
- states: {
13
- [params.onState]: {
14
- update(entity, dt, api) {
15
- type.states?.[params.onState].update?.(entity, dt, api)
16
- },
17
- },
18
- },
19
- })
20
- }