-
Notifications
You must be signed in to change notification settings - Fork 0
/
Item.ts
386 lines (363 loc) · 10.2 KB
/
Item.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
import { ISpriteFrameDef } from "./data/DefPack";
import Game from "./Game";
import ItemGroup from "./ItemGroup";
import Position, { IPosition } from "./Position";
import { applyDefault } from "./data/util";
import Member from "./data/Member";
import {
IDidApplyEvent,
IDidSetUpdateEvent,
DataObject,
} from "./data/DataHolder";
import { IWillDestroyEvent } from "./Destroyable";
import AnyEvent, { IEventType } from "./events/AnyEvent";
import Tile from "./Tile";
import Character from "./Character";
import FieldDef from "./data/FieldDef";
import { IItemDef } from "./data/ItemDefPack";
export interface IRepositionEvent extends IEventType {
type: "reposition";
data: {
prevTile: Tile | null;
currentTile: Tile | null;
};
}
/**
* Essential data for creating a new item object.
*/
export interface IItemData extends DataObject {
type: string; // Item type. Which is the name of the item definition.
position: IPosition & DataObject;
inFront?: boolean | null; // Indicate if the item should be in front of the character. If null, use item definition.
frameName?: string; // The name of the frame. If null, use default frame name.
page?: string | null; // The page to link to. If null, use item definition.
facingDir?: 1 | -1;
walkable?: boolean | null; // If null, use item definition.
}
function getFieldDef(game: Game, data: IItemData) {
return new FieldDef<IItemData>(
{
type: "object",
acceptNull: false,
children: {
type: {
type: "string",
valueList: game.assetPack.itemDefPack.typeNames,
},
position: {
type: "object",
children: {
col: {
type: "number",
minNum: 0,
maxNum: game.tileManager.colCount,
},
row: {
type: "number",
minNum: 0,
maxNum: game.tileManager.rowCount,
},
},
},
inFront: {
type: "boolean",
acceptUndefined: true,
acceptNull: true,
},
frameName: {
type: "string",
acceptUndefined: true,
editable: false,
},
page: {
type: "string",
acceptUndefined: true,
acceptNull: true,
},
facingDir: {
type: "number",
valueList: [1, -1],
acceptUndefined: true,
},
walkable: {
type: "boolean",
acceptUndefined: true,
acceptNull: true,
},
},
},
data,
"itemData"
);
}
const DEFAULT_ITEM_DATA: Partial<IItemData> = {
inFront: null,
frameName: "default",
page: null,
facingDir: 1,
walkable: null,
};
export default class Item
extends Member<ItemGroup, IItemData>
implements IPosition
{
public static readonly DEFAULT_FRAME_NAME = DEFAULT_ITEM_DATA.frameName;
private _game: Game;
private _itemDef: IItemDef;
private _frameDef: ISpriteFrameDef;
private _currentTile: Tile | null = null;
/**
* Get type of the item
*/
public get type(): string {
return this.data.type;
}
public set type(type: string) {
if (!this.group.itemDefPack.get(type)) {
throw new Error(`Item type ${type} not found`);
}
this.data.type = type;
}
/**
* Indicate if the item is collectable
*/
public get collectable(): boolean {
return this._itemDef.collectable;
}
/**
* Get linked page name
*/
public get page(): string | null {
return this.data.page == null ? this._itemDef.page : this.data.page;
}
/**
* Set linked page name
*/
public set page(page: string | null) {
this.data.page = page;
}
/**
* Indicate if the item should be in front of the character.
*/
public get inFront(): boolean {
return this.data.inFront == null
? this._itemDef.inFront
: this.data.inFront;
}
public set inFront(inFront: boolean) {
this.data.inFront = inFront;
}
public get walkable(): boolean {
return this.data.walkable == null
? this._itemDef.walkable
: this.data.walkable;
}
/**
* Get position of the item.
*/
public get position(): Position {
return new Position(this.data.position);
}
public set position(position: IPosition) {
if (!this._game.tileManager.isInRange(position)) {
throw new Error(
`Position out of range: ${position.col} - ${position.row}`
);
}
this.data.position = { col: position.col, row: position.row };
}
/**
* Get the tile that the item is currently on.
*/
public get currentTile(): Tile {
return this._currentTile as Tile;
}
/**
* Shortcut for position.col
*/
public get col(): number {
return this.data.position.col;
}
/**
* Shortcut for position.row
*/
public get row(): number {
return this.data.position.row;
}
/**
* Get a copy of the definition of the current frame of the item.
*/
public get frameDef(): ISpriteFrameDef {
return { ...this._frameDef };
}
/**
* Get the name of the current frame of the item.
*/
public get frameName(): string {
return this.data.frameName as string;
}
/**
* Set the name of the current frame of the item.
*/
public set frameName(frameName: string) {
this._getFrameDef(frameName); // Check if frame name exists
this.data.frameName = frameName;
}
/**
* Indicate the facing direction of the character.
* 1: Right
* -1: Left
*/
public get facingDir(): 1 | -1 {
return this.data.facingDir || 1;
}
/**
* Create a new game item
* @param data
*/
constructor(group: ItemGroup, id: number, data: IItemData) {
data = applyDefault(data, DEFAULT_ITEM_DATA) as IItemData;
super(group, id, data);
this._game = group.game;
this._itemDef = this._getItemDef(this.data.type as string);
this._frameDef = this._getFrameDef(this.data.frameName as string);
//Set up event listeners
let onDidSetUpdate = () => {
this._itemDef = this._getItemDef(this.type as string);
this._frameDef = this._getFrameDef(this.frameName as string);
this._updateTile();
};
this.on<IDidSetUpdateEvent>("didSetUpdate", onDidSetUpdate);
this.on<IDidApplyEvent>("didApply", onDidSetUpdate);
this.once<IWillDestroyEvent>("willDestroy", () => {
this.off<IDidSetUpdateEvent>("didSetUpdate", onDidSetUpdate);
this.off<IDidApplyEvent>("didApply", onDidSetUpdate);
this._currentTile && this._currentTile["internal"].removeItem(this);
});
// Register to tile
this._updateTile();
}
/**
* Get items that this item is colliding with.
* @param target A specific item to check collision with. If null, check collision with all items on the same tile.
* @returns Returns null if no collision.
*/
public hitItem(target: Item | null = null): Array<Item> | null {
// No target
if (!target) {
let list = this.currentTile.items.filter((item) => item != this);
return list.length > 0 ? list : null;
}
// Target on the same tile
if (this.currentTile.hasItem(target)) {
return [target];
}
return null;
}
/**
* Get items that this item is adjacent to.
* @param target A specific item to check adjacency with. If null, check adjacency with all items on adjacent tiles.
* @returns
*/
public adjacentItem(target: Item | null = null): Array<Item> | null {
if (!target) {
let list: Array<Item> = [];
this.currentTile.adjacentTiles.forEach((tile) => {
list = list.concat(tile.items);
});
list = list.filter((item) => item != this);
return list.length > 0 ? list : null;
}
for (let tile of this.currentTile.adjacentTiles) {
if (tile.hasItem(target)) {
return [target];
}
}
return null;
}
/**
* Get characters that this item is colliding with.
* @param target A specific character to check collision with. If null, check collision with all characters on the same tile.
* @returns Returns null if no collision.
*/
public hitCharacter(
target: Character | null = null
): Array<Character> | null {
// No target
if (!target) {
let list = this.currentTile.characters;
return list.length > 0 ? list : null;
}
// Target on the same tile
if (this.currentTile.hasCharacter(target)) {
return [target];
}
return null;
}
/**
* Get characters that this item is adjacent to.
* @param target A specific character to check adjacency with. If null, check adjacency with all characters on adjacent tiles.
* @returns Returns null if no adjacency.
*/
public adjacentCharacter(
target: Character | null = null
): Array<Character> | null {
if (!target) {
let list: Array<Character> = [];
this.currentTile.adjacentTiles.forEach((tile) => {
list = list.concat(tile.characters);
});
return list.length > 0 ? list : null;
}
for (let tile of this.currentTile.adjacentTiles) {
if (tile.hasCharacter(target)) {
return [target];
}
}
return null;
}
public setData(data: Partial<IItemData>): void {
super.setData(data);
}
private _updateTile() {
let tile = this._game.tileManager.getTile(this.position);
if (!tile) {
throw new Error(
`Tile not found at ${this.position.col} - ${this.position.row}`
);
}
// Current tile
if (tile == this._currentTile) {
return null;
}
if (this._currentTile) {
this._currentTile["internal"].removeItem(this);
}
let prevTile = this._currentTile;
this._currentTile = tile;
this._currentTile["internal"].addItem(this);
this.emit<IRepositionEvent>(
new AnyEvent("reposition", {
prevTile,
currentTile: this._currentTile,
})
);
}
public getFieldDef(): FieldDef<IItemData> {
return getFieldDef(this._game, this.data);
}
private _getItemDef(type: string): IItemDef {
let itemDef = this.group.itemDefPack.get(type);
if (!itemDef) {
throw new Error(`Unknown item type: ${type}`);
}
return itemDef;
}
private _getFrameDef(frameName: string): ISpriteFrameDef {
let frameDef = this._itemDef.frames[frameName];
if (!frameDef) {
throw new Error(`Unknown frame name: ${frameName}`);
}
return frameDef;
}
}