melonjs 9.1.0 → 10.0.1

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.
Files changed (78) hide show
  1. package/{LICENSE → LICENSE.md} +0 -0
  2. package/README.md +93 -57
  3. package/dist/melonjs.js +10334 -11179
  4. package/dist/melonjs.min.js +4 -10
  5. package/dist/melonjs.module.d.ts +13206 -0
  6. package/dist/melonjs.module.js +9913 -10872
  7. package/package.json +19 -14
  8. package/src/audio/audio.js +477 -553
  9. package/src/camera/camera2d.js +67 -65
  10. package/src/entity/draggable.js +26 -35
  11. package/src/entity/droptarget.js +17 -14
  12. package/src/entity/entity.js +59 -79
  13. package/src/game.js +194 -204
  14. package/src/index.js +12 -30
  15. package/src/input/gamepad.js +8 -19
  16. package/src/input/keyboard.js +4 -4
  17. package/src/input/pointer.js +14 -12
  18. package/src/input/pointerevent.js +15 -13
  19. package/src/lang/deprecated.js +2 -887
  20. package/src/level/level.js +3 -3
  21. package/src/level/tiled/TMXGroup.js +7 -11
  22. package/src/level/tiled/TMXLayer.js +33 -32
  23. package/src/level/tiled/TMXTileMap.js +15 -19
  24. package/src/level/tiled/TMXTileset.js +5 -5
  25. package/src/level/tiled/TMXUtils.js +3 -3
  26. package/src/level/tiled/renderer/TMXRenderer.js +4 -0
  27. package/src/loader/loader.js +8 -23
  28. package/src/loader/loadingscreen.js +51 -60
  29. package/src/math/matrix3.js +1 -1
  30. package/src/particles/emitter.js +36 -39
  31. package/src/particles/particle.js +27 -12
  32. package/src/particles/particlecontainer.js +17 -16
  33. package/src/physics/body.js +80 -118
  34. package/src/physics/collision.js +5 -235
  35. package/src/physics/detector.js +235 -0
  36. package/src/physics/quadtree.js +14 -14
  37. package/src/physics/world.js +84 -18
  38. package/src/plugin/plugin.js +26 -24
  39. package/src/polyfill/console.js +9 -14
  40. package/src/renderable/GUI.js +48 -62
  41. package/src/renderable/collectable.js +11 -4
  42. package/src/renderable/colorlayer.js +28 -26
  43. package/src/renderable/container.js +120 -96
  44. package/src/renderable/imagelayer.js +94 -93
  45. package/src/renderable/renderable.js +164 -138
  46. package/src/renderable/sprite.js +42 -44
  47. package/src/renderable/trigger.js +24 -17
  48. package/src/shapes/ellipse.js +27 -27
  49. package/src/shapes/line.js +12 -8
  50. package/src/shapes/poly.js +77 -49
  51. package/src/shapes/rectangle.js +193 -268
  52. package/src/state/stage.js +23 -25
  53. package/src/state/state.js +35 -86
  54. package/src/system/device.js +233 -285
  55. package/src/system/event.js +485 -432
  56. package/src/system/pooling.js +61 -54
  57. package/src/system/save.js +17 -16
  58. package/src/system/timer.js +34 -38
  59. package/src/text/bitmaptext.js +44 -46
  60. package/src/text/text.js +39 -34
  61. package/src/tweens/easing.js +0 -2
  62. package/src/tweens/interpolation.js +3 -8
  63. package/src/tweens/tween.js +332 -351
  64. package/src/utils/function.js +6 -8
  65. package/src/utils/utils.js +34 -30
  66. package/src/video/canvas/canvas_renderer.js +13 -8
  67. package/src/video/renderer.js +8 -7
  68. package/src/video/texture.js +8 -8
  69. package/src/video/texture_cache.js +5 -5
  70. package/src/video/video.js +373 -403
  71. package/src/video/webgl/glshader.js +2 -2
  72. package/src/video/webgl/webgl_compositor.js +14 -8
  73. package/src/video/webgl/webgl_renderer.js +21 -19
  74. package/plugins/debug/debugPanel.js +0 -770
  75. package/plugins/debug/font/PressStart2P.fnt +0 -100
  76. package/plugins/debug/font/PressStart2P.ltr +0 -1
  77. package/plugins/debug/font/PressStart2P.png +0 -0
  78. package/plugins/debug/particleDebugPanel.js +0 -303
@@ -0,0 +1,235 @@
1
+ import * as SAT from "./sat.js";
2
+ import Vector2d from "./../math/vector2.js";
3
+ import { world } from "./../game.js";
4
+
5
+ // a dummy object when using Line for raycasting
6
+ var dummyObj = {
7
+ pos : new Vector2d(0, 0),
8
+ ancestor : {
9
+ _absPos : new Vector2d(0, 0),
10
+ getAbsolutePosition : function () {
11
+ return this._absPos;
12
+ }
13
+ }
14
+ };
15
+
16
+ /**
17
+ * a function used to determine if two objects should collide (based on both respective objects collision mask and type).<br>
18
+ * you can redefine this function if you need any specific rules over what should collide with what.
19
+ * @name shouldCollide
20
+ * @memberOf me.collision
21
+ * @ignore
22
+ * @function
23
+ * @param {me.Renderable} a a reference to the object A.
24
+ * @param {me.Renderable} b a reference to the object B.
25
+ * @return {Boolean} true if they should collide, false otherwise
26
+ */
27
+ function shouldCollide(a, b) {
28
+ return (
29
+ a.isKinematic !== true && b.isKinematic !== true &&
30
+ a.body && b.body &&
31
+ (a.body.collisionMask & b.body.collisionType) !== 0 &&
32
+ (a.body.collisionType & b.body.collisionMask) !== 0
33
+ );
34
+ };
35
+
36
+ /**
37
+ * @classdesc
38
+ * An object representing the result of an intersection.
39
+ * @property {me.Renderable} a The first object participating in the intersection
40
+ * @property {me.Renderable} b The second object participating in the intersection
41
+ * @property {Number} overlap Magnitude of the overlap on the shortest colliding axis
42
+ * @property {me.Vector2d} overlapV The overlap vector (i.e. `overlapN.scale(overlap, overlap)`). If this vector is subtracted from the position of a, a and b will no longer be colliding
43
+ * @property {me.Vector2d} overlapN The shortest colliding axis (unit-vector)
44
+ * @property {Boolean} aInB Whether the first object is entirely inside the second
45
+ * @property {Boolean} bInA Whether the second object is entirely inside the first
46
+ * @property {Number} indexShapeA The index of the colliding shape for the object a body
47
+ * @property {Number} indexShapeB The index of the colliding shape for the object b body
48
+ * @name ResponseObject
49
+ * @memberOf me.collision
50
+ * @public
51
+ * @see me.collision.check
52
+ */
53
+ class ResponseObject {
54
+ constructor() {
55
+ this.a = null;
56
+ this.b = null;
57
+ this.overlapN = new Vector2d();
58
+ this.overlapV = new Vector2d();
59
+ this.aInB = true;
60
+ this.bInA = true;
61
+ this.indexShapeA = -1;
62
+ this.indexShapeB = -1;
63
+ this.overlap = Number.MAX_VALUE;
64
+ return this;
65
+ }
66
+
67
+ /**
68
+ * Set some values of the response back to their defaults. <br>
69
+ * Call this between tests if you are going to reuse a single <br>
70
+ * Response object for multiple intersection tests <br>
71
+ * (recommended as it will avoid allocating extra memory) <br>
72
+ * @name clear
73
+ * @memberOf me.collision.ResponseObject
74
+ * @public
75
+ * @function
76
+ */
77
+ clear () {
78
+ this.aInB = true;
79
+ this.bInA = true;
80
+ this.overlap = Number.MAX_VALUE;
81
+ this.indexShapeA = -1;
82
+ this.indexShapeB = -1;
83
+ return this;
84
+ }
85
+ }
86
+
87
+ // @ignore
88
+ export var globalResponse = new ResponseObject();
89
+
90
+ /**
91
+ * find all the collisions for the specified object
92
+ * @name collisionCheck
93
+ * @ignore
94
+ * @function
95
+ * @param {me.Renderable} obj object to be tested for collision
96
+ * @param {me.collision.ResponseObject} [response=me.collision.response] a user defined response object that will be populated if they intersect.
97
+ * @return {Boolean} in case of collision, false otherwise
98
+ */
99
+ export function collisionCheck(objA, response = globalResponse) {
100
+ var collisionCounter = 0;
101
+ // retreive a list of potential colliding objects from the game world
102
+ var candidates = world.broadphase.retrieve(objA);
103
+
104
+ for (var i = candidates.length, objB; i--, (objB = candidates[i]);) {
105
+
106
+ // check if both objects "should" collide
107
+ if ((objB !== objA) && shouldCollide(objA, objB) &&
108
+ // fast AABB check if both bounding boxes are overlaping
109
+ objA.body.getBounds().overlaps(objB.body.getBounds())) {
110
+
111
+ // go trough all defined shapes in A
112
+ var aLen = objA.body.shapes.length;
113
+ var bLen = objB.body.shapes.length;
114
+ if (aLen === 0 || bLen === 0) {
115
+ continue;
116
+ }
117
+
118
+ var indexA = 0;
119
+ do {
120
+ var shapeA = objA.body.getShape(indexA);
121
+ // go through all defined shapes in B
122
+ var indexB = 0;
123
+ do {
124
+ var shapeB = objB.body.getShape(indexB);
125
+
126
+ // full SAT collision check
127
+ if (SAT["test" + shapeA.shapeType + shapeB.shapeType]
128
+ .call(
129
+ this,
130
+ objA, // a reference to the object A
131
+ shapeA,
132
+ objB, // a reference to the object B
133
+ shapeB,
134
+ // clear response object before reusing
135
+ response.clear()) === true
136
+ ) {
137
+ // we touched something !
138
+ collisionCounter++;
139
+
140
+ // set the shape index
141
+ response.indexShapeA = indexA;
142
+ response.indexShapeB = indexB;
143
+
144
+ // execute the onCollision callback
145
+ if (objA.onCollision && objA.onCollision(response, objB) !== false) {
146
+ objA.body.respondToCollision.call(objA.body, response);
147
+ }
148
+ if (objB.onCollision && objB.onCollision(response, objA) !== false) {
149
+ objB.body.respondToCollision.call(objB.body, response);
150
+ }
151
+ }
152
+ indexB++;
153
+ } while (indexB < bLen);
154
+ indexA++;
155
+ } while (indexA < aLen);
156
+ }
157
+ }
158
+ // we could return the amount of objects we collided with ?
159
+ return collisionCounter > 0;
160
+ };
161
+
162
+ /**
163
+ * Checks for object colliding with the given line
164
+ * @name rayCast
165
+ * @ignore
166
+ * @function
167
+ * @param {me.Line} line line to be tested for collision
168
+ * @param {Array.<me.Renderable>} [result] a user defined array that will be populated with intersecting physic objects.
169
+ * @return {Array.<me.Renderable>} an array of intersecting physic objects
170
+ * @example
171
+ * // define a line accross the viewport
172
+ * var ray = new me.Line(
173
+ * // absolute position of the line
174
+ * 0, 0, [
175
+ * // starting point relative to the initial position
176
+ * new me.Vector2d(0, 0),
177
+ * // ending point
178
+ * new me.Vector2d(me.game.viewport.width, me.game.viewport.height)
179
+ * ]);
180
+ *
181
+ * // check for collition
182
+ * result = me.collision.rayCast(ray);
183
+ *
184
+ * if (result.length > 0) {
185
+ * // ...
186
+ * }
187
+ */
188
+ export function rayCast(line, result = []) {
189
+ var collisionCounter = 0;
190
+
191
+ // retrieve a list of potential colliding objects from the game world
192
+ var candidates = world.broadphase.retrieve(line);
193
+
194
+ for (var i = candidates.length, objB; i--, (objB = candidates[i]);) {
195
+
196
+ // fast AABB check if both bounding boxes are overlaping
197
+ if (objB.body && line.getBounds().overlaps(objB.getBounds())) {
198
+
199
+ // go trough all defined shapes in B (if any)
200
+ var bLen = objB.body.shapes.length;
201
+ if ( objB.body.shapes.length === 0) {
202
+ continue;
203
+ }
204
+
205
+ var shapeA = line;
206
+
207
+ // go through all defined shapes in B
208
+ var indexB = 0;
209
+ do {
210
+ var shapeB = objB.body.getShape(indexB);
211
+
212
+ // full SAT collision check
213
+ if (SAT["test" + shapeA.shapeType + shapeB.shapeType]
214
+ .call(
215
+ this,
216
+ dummyObj, // a reference to the object A
217
+ shapeA,
218
+ objB, // a reference to the object B
219
+ shapeB
220
+ )) {
221
+ // we touched something !
222
+ result[collisionCounter] = objB;
223
+ collisionCounter++;
224
+ }
225
+ indexB++;
226
+ } while (indexB < bLen);
227
+ }
228
+ }
229
+
230
+ // cap result in case it was not empty
231
+ result.length = collisionCounter;
232
+
233
+ // return the list of colliding objects
234
+ return result;
235
+ };
@@ -1,7 +1,7 @@
1
1
  import Vector2d from "./../math/vector2.js";
2
2
  import Container from "./../renderable/container.js";
3
- import utils from "./../utils/utils.js";
4
- import game from "./../game.js";
3
+ import * as arrayUtil from "./../utils/array.js";
4
+ import { viewport } from "./../game.js";
5
5
 
6
6
  /*
7
7
  * A QuadTree implementation in JavaScript, a 2d spatial subdivision algorithm.
@@ -21,13 +21,13 @@ var QT_ARRAY = [];
21
21
  * or create a new one if the array is empty
22
22
  * @ignore
23
23
  */
24
- function QT_ARRAY_POP(bounds, max_objects, max_levels, level) {
24
+ function QT_ARRAY_POP(bounds, max_objects = 4, max_levels = 4, level = 0) {
25
25
  if (QT_ARRAY.length > 0) {
26
26
  var _qt = QT_ARRAY.pop();
27
27
  _qt.bounds = bounds;
28
- _qt.max_objects = max_objects || 4;
29
- _qt.max_levels = max_levels || 4;
30
- _qt.level = level || 0;
28
+ _qt.max_objects = max_objects;
29
+ _qt.max_levels = max_levels;
30
+ _qt.level = level;
31
31
  return _qt;
32
32
  } else {
33
33
  return new QuadTree(bounds, max_objects, max_levels, level);
@@ -63,11 +63,11 @@ var QT_VECTOR = new Vector2d();
63
63
  */
64
64
  class QuadTree {
65
65
 
66
- constructor(bounds, max_objects, max_levels, level) {
67
- this.max_objects = max_objects || 4;
68
- this.max_levels = max_levels || 4;
66
+ constructor(bounds, max_objects = 4, max_levels = 4, level = 0) {
67
+ this.max_objects = max_objects;
68
+ this.max_levels = max_levels;
69
69
 
70
- this.level = level || 0;
70
+ this.level = level;
71
71
  this.bounds = bounds;
72
72
 
73
73
  this.objects = [];
@@ -125,9 +125,9 @@ class QuadTree {
125
125
  getIndex(item) {
126
126
  var pos;
127
127
 
128
- // use world coordinates for floating items
128
+ // use game world coordinates for floating items
129
129
  if (item.floating || (item.ancestor && item.ancestor.floating)) {
130
- pos = game.viewport.localToWorld(item.left, item.top, QT_VECTOR);
130
+ pos = viewport.localToWorld(item.left, item.top, QT_VECTOR);
131
131
  } else {
132
132
  pos = QT_VECTOR.set(item.left, item.top);
133
133
  }
@@ -295,7 +295,7 @@ class QuadTree {
295
295
  var index = this.getIndex(item);
296
296
 
297
297
  if (index !== -1) {
298
- found = utils.array.remove(this.nodes[index], item);
298
+ found = arrayUtil.remove(this.nodes[index], item);
299
299
  // trim node if empty
300
300
  if (found && this.nodes[index].isPrunable()) {
301
301
  this.nodes.splice(index, 1);
@@ -306,7 +306,7 @@ class QuadTree {
306
306
  if (found === false) {
307
307
  // try and remove the item from the list of items in this node
308
308
  if (this.objects.indexOf(item) !== -1) {
309
- utils.array.remove(this.objects, item);
309
+ arrayUtil.remove(this.objects, item);
310
310
  found = true;
311
311
  }
312
312
  }
@@ -1,9 +1,10 @@
1
1
  import Vector2d from "./../math/vector2.js";
2
- import game from "./../game.js";
3
- import event from "./../system/event.js";
2
+ import * as event from "./../system/event.js";
4
3
  import QuadTree from "./quadtree.js";
5
4
  import Container from "./../renderable/container.js";
6
5
  import collision from "./collision.js";
6
+ import { collisionCheck } from "./detector.js";
7
+ import state from "./../state/state.js";
7
8
 
8
9
  /**
9
10
  * an object representing the physic world, and responsible for managing and updating all childs and physics
@@ -16,13 +17,14 @@ import collision from "./collision.js";
16
17
  * @param {Number} [w=me.game.viewport.width] width of the container
17
18
  * @param {Number} [h=me.game.viewport.height] height of the container
18
19
  */
19
- var World = Container.extend({
20
+ class World extends Container {
20
21
  /**
21
22
  * @ignore
22
23
  */
23
- init : function (x = 0, y = 0, width = Infinity, height = Infinity) {
24
- // call the _super constructor
25
- this._super(Container, "init", [x, y, width, height, true]);
24
+ constructor(x = 0, y = 0, width = Infinity, height = Infinity) {
25
+
26
+ // call the super constructor
27
+ super(x, y, width, height, true);
26
28
 
27
29
  // world is the root container
28
30
  this.name = "rootContainer";
@@ -66,6 +68,15 @@ var World = Container.extend({
66
68
  */
67
69
  this.preRender = false;
68
70
 
71
+ /**
72
+ * the active physic bodies in this simulation
73
+ * @name bodies
74
+ * @memberOf me.World
75
+ * @public
76
+ * @type {Set}
77
+ */
78
+ this.bodies = new Set();
79
+
69
80
  /**
70
81
  * the instance of the game world quadtree used for broadphase
71
82
  * @name broadphase
@@ -76,14 +87,14 @@ var World = Container.extend({
76
87
  this.broadphase = new QuadTree(this.getBounds().clone(), collision.maxChildren, collision.maxDepth);
77
88
 
78
89
  // reset the world container on the game reset signal
79
- event.subscribe(event.GAME_RESET, this.reset.bind(this));
90
+ event.on(event.GAME_RESET, this.reset, this);
80
91
 
81
92
  // update the broadband world bounds if a new level is loaded
82
- event.subscribe(event.LEVEL_LOADED, function () {
93
+ event.on(event.LEVEL_LOADED, () => {
83
94
  // reset the quadtree
84
- game.world.broadphase.clear(game.world.getBounds());
95
+ this.broadphase.clear(this.getBounds());
85
96
  });
86
- },
97
+ }
87
98
 
88
99
  /**
89
100
  * reset the game world
@@ -91,16 +102,50 @@ var World = Container.extend({
91
102
  * @memberOf me.World
92
103
  * @function
93
104
  */
94
- reset : function () {
105
+ reset() {
95
106
  // clear the quadtree
96
107
  this.broadphase.clear();
97
108
 
98
109
  // reset the anchorPoint
99
110
  this.anchorPoint.set(0, 0);
100
111
 
101
- // call the _super constructor
102
- this._super(Container, "reset");
103
- },
112
+ // call the parent method
113
+ super.reset();
114
+
115
+ // empty the list of active physic bodies
116
+ // Note: this should be empty already when calling the parent method
117
+ this.bodies.clear();
118
+ }
119
+
120
+ /**
121
+ * Add a physic body to the game world
122
+ * @name addBody
123
+ * @memberOf me.World
124
+ * @see me.Container.addChild
125
+ * @function
126
+ * @param {me.Body} body
127
+ * @return {me.World} this game world
128
+ */
129
+ addBody(body) {
130
+ //add it to the list of active body
131
+ this.bodies.add(body);
132
+ return this;
133
+ }
134
+
135
+ /**
136
+ * Remove a physic body from the game world
137
+ * @name removeBody
138
+ * @memberOf me.World
139
+ * @see me.Container.removeChild
140
+ * @function
141
+ * @param {me.Body} body
142
+ * @return {me.World} this game world
143
+ */
144
+ removeBody(body) {
145
+ //remove from the list of active body
146
+ this.bodies.delete(body);
147
+ return this;
148
+ }
104
149
 
105
150
  /**
106
151
  * update the game world
@@ -108,16 +153,37 @@ var World = Container.extend({
108
153
  * @memberOf me.World
109
154
  * @function
110
155
  */
111
- update : function (dt) {
156
+ update (dt) {
157
+ var isPaused = state.isPaused();
158
+
112
159
  // clear the quadtree
113
160
  this.broadphase.clear();
114
161
 
115
162
  // insert the world container (children) into the quadtree
116
163
  this.broadphase.insertContainer(this);
117
164
 
118
- // call the _super constructor
119
- return this._super(Container, "update", [dt]);
165
+ // iterate through all bodies
166
+ this.bodies.forEach((body) => {
167
+ if (!body.isStatic) {
168
+ var ancestor = body.ancestor;
169
+ // if the game is not paused, and ancestor can be updated
170
+ if (!(isPaused && (!ancestor.updateWhenPaused)) &&
171
+ (ancestor.inViewport || ancestor.alwaysUpdate)) {
172
+ // apply physics to the body (this moves it)
173
+ if (body.update(dt) === true) {
174
+ // mark ancestor as dirty
175
+ ancestor.isDirty = true;
176
+ };
177
+ // handle collisions against other objects
178
+ collisionCheck(ancestor);
179
+ }
180
+ }
181
+ });
182
+
183
+ // call the super constructor
184
+ return super.update(dt);
120
185
  }
121
- });
186
+
187
+ };
122
188
 
123
189
  export default World;
@@ -1,6 +1,5 @@
1
1
  import utils from "./../utils/utils.js";
2
2
  import { version } from "./../index.js";
3
- import "jay-extend";
4
3
 
5
4
  /**
6
5
  * This namespace is a container for all registered plugins.
@@ -10,6 +9,22 @@ import "jay-extend";
10
9
  */
11
10
  export var plugins = {};
12
11
 
12
+
13
+ class BasePlugin {
14
+
15
+ constructor() {
16
+ /**
17
+ * define the minimum required version of melonJS<br>
18
+ * this can be overridden by the plugin
19
+ * @public
20
+ * @type String
21
+ * @default "__VERSION__"
22
+ * @name me.plugin.Base#version
23
+ */
24
+ this.version = "__VERSION__";
25
+ }
26
+ }
27
+
13
28
  /**
14
29
  * @namespace plugin
15
30
  * @memberOf me
@@ -17,29 +32,16 @@ export var plugins = {};
17
32
  export var plugin = {
18
33
 
19
34
  /**
20
- * a base Object for plugin <br>
21
- * plugin must be installed using the register function
22
- * @see me.plugin
23
- * @class
24
- * @extends me.Object
25
- * @name plugin.Base
26
- * @memberOf me
27
- * @constructor
28
- */
29
- Base : window.Jay.extend({
30
- /** @ignore */
31
- init : function () {
32
- /**
33
- * define the minimum required version of melonJS<br>
34
- * this can be overridden by the plugin
35
- * @public
36
- * @type String
37
- * @default "__VERSION__"
38
- * @name me.plugin.Base#version
39
- */
40
- this.version = "__VERSION__";
41
- }
42
- }),
35
+ * a base Object for plugin <br>
36
+ * plugin must be installed using the register function
37
+ * @see me.plugin
38
+ * @class
39
+ * @extends me.Object
40
+ * @name plugin.Base
41
+ * @memberOf me
42
+ * @constructor
43
+ */
44
+ Base : BasePlugin,
43
45
 
44
46
  /**
45
47
  * patch a melonJS function
@@ -1,16 +1,11 @@
1
- /* eslint-disable no-global-assign, no-native-reassign */
2
- if (typeof console === "undefined") {
3
- /**
4
- * Dummy console.log to avoid crash
5
- * in case the browser does not support it
6
- * @ignore
7
- */
8
- console = {
9
- log : function () {},
10
- info : function () {},
11
- error : function () {
1
+ if (typeof window !== "undefined") {
2
+ if (typeof window.console === "undefined") {
3
+ window.console = {};
4
+ window.console.log = function() {};
5
+ window.console.assert = function() {};
6
+ window.console.warn = function() {};
7
+ window.console.error = function() {
12
8
  alert(Array.prototype.slice.call(arguments).join(", "));
13
- }
14
- };
9
+ };
10
+ }
15
11
  }
16
- /* eslint-enable no-global-assign, no-native-reassign */