Skip to content

Commit 51a42db

Browse files
committed
feat: add portal
1 parent 3054ece commit 51a42db

12 files changed

Lines changed: 371 additions & 50 deletions

File tree

game/field.ts

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,35 @@ export class FieldItems implements IStepper, ILoader {
5353
#items: Set<IItem> = new Set()
5454
#deactivateScope: RectScope
5555
#gridSet = new GridSet<IItem>()
56+
#idSet = new Set<string>()
5657

5758
constructor(scope: RectScope) {
5859
this.#deactivateScope = scope
5960
}
6061

6162
checkDeactivate(i: number, j: number) {
6263
this.#deactivateScope.setCenter(i * CELL_SIZE, j * CELL_SIZE)
63-
;[...this.#items.values()]
64+
let c = 0
65+
this.#items.values()
6466
.filter((item) => !this.#deactivateScope.overlaps(item))
6567
.forEach((item) => {
66-
console.log("deactivating item", item.id)
68+
c++
6769
this.#deactivate(item.i, item.j, item.id)
6870
})
71+
if (c > 0) {
72+
console.log(`deactivating ${c} items`)
73+
}
6974
signal.itemsCount.update(this.#items.size)
7075
}
7176

77+
has(id: string) {
78+
return this.#idSet.has(id)
79+
}
80+
7281
add(item: IItem) {
7382
this.#items.add(item)
7483
this.#gridSet.add(item.i, item.j, item)
84+
this.#idSet.add(item.id)
7585
signal.itemsCount.update(this.#items.size)
7686
}
7787

@@ -106,6 +116,7 @@ export class FieldItems implements IStepper, ILoader {
106116
}
107117
this.#items.delete(item)
108118
this.#gridSet.delete(i, j, item)
119+
this.#idSet.delete(id)
109120
signal.itemsCount.update(this.#items.size)
110121
return item
111122
}
@@ -127,14 +138,15 @@ export class FieldItems implements IStepper, ILoader {
127138

128139
async loadAssets(options: LoadOptions): Promise<void> {
129140
await Promise.all(
130-
[...this.#items]
141+
this.#items.values()
131142
.filter((item) => !item.assetsReady)
132-
.map((item) => item.loadAssets(options)),
143+
.map((item) => item.loadAssets(options))
144+
.toArray(),
133145
)
134146
}
135147

136148
get assetsReady(): boolean {
137-
return [...this.#items].every((x) => x.assetsReady)
149+
return this.#items.values().every((x) => x.assetsReady)
138150
}
139151

140152
iter() {
@@ -153,12 +165,16 @@ export class FieldProps implements IStepper, ILoader {
153165

154166
checkDeactivate(i: number, j: number) {
155167
this.#deactivateScope.setCenter(i * CELL_SIZE, j * CELL_SIZE)
156-
;[...this.#props.values()]
168+
let c = 0
169+
this.#props.values()
157170
.filter((obj) => !this.#deactivateScope.overlaps(obj))
158171
.forEach((obj) => {
159-
console.log("deactivating object", obj.id)
172+
c++
160173
this.remove(obj.i, obj.j)
161174
})
175+
if (c > 0) {
176+
console.log(`deactivating ${c} objects`)
177+
}
162178
signal.propsCount.update(this.#props.size)
163179
}
164180

@@ -306,17 +322,21 @@ export class FieldActors implements IStepper, ILoader {
306322
this.#deactivateScope.setCenter(i * CELL_SIZE, j * CELL_SIZE)
307323

308324
const actors = [] as IActor[]
325+
let c = 0
309326
for (const actor of this.#actors) {
310327
if (this.#deactivateScope.overlaps(actor)) {
311328
actors.push(actor)
312329
continue
313330
}
314-
console.log("deactivating actor", actor.id)
331+
c++
315332
this.#idSet.delete(actor.id)
316333
this.#coordCountMap.decrement(actor.physicalGridKey)
317334
}
318335
this.#actors = actors
319336
signal.actorsCount.update(this.#actors.length)
337+
if (c > 0) {
338+
console.log(`deactivating ${c} actors`)
339+
}
320340
}
321341

322342
iter() {
@@ -590,9 +610,7 @@ export class Field implements IField {
590610
this.#addBlock(new FieldBlock(map))
591611
}
592612
const initialLoad = !this.#initialBlocksLoaded
593-
await Promise.all(
594-
[...this.#getChunks(i, j)].map((c) => c.render(initialLoad)),
595-
)
613+
await Promise.all(this.#getChunks(i, j).map((c) => c.render(initialLoad)))
596614
if (!this.#initialBlocksLoaded) {
597615
this.#initialBlocksLoaded = true
598616
this.checkActivate(
@@ -627,23 +645,30 @@ export class Field implements IField {
627645
this.#activateScope.setCenter(i * CELL_SIZE, j * CELL_SIZE)
628646

629647
const chunks = [...this.#getChunks(i, j)]
630-
const newCharSpawns = chunks.flatMap((c) => c.getCharacterSpawns())
648+
const newActorSpawns = chunks.values()
649+
.flatMap((c) => c.getCharacterSpawns())
631650
.filter((spawn) => !this.#actors.has(spawn.id)) // isn't spawned yet
632651
.filter((spawn) => initialLoad || !viewScope.overlaps(spawn)) // not in view
633652
.filter((spawn) => this.#activateScope.overlaps(spawn)) // in activate scope
653+
.toArray()
634654

635-
const newItemSpawns = chunks.flatMap((c) => c.getItemSpawns())
655+
const newItemSpawns = chunks.values()
656+
.flatMap((c) => c.getItemSpawns())
636657
.filter((spawn) => !this.#items.isCollected(spawn.id)) // isn't collected yet
658+
.filter((spawn) => !this.#items.has(spawn.id)) // item is already on the field
637659
.filter((spawn) => initialLoad || !viewScope.overlaps(spawn)) // not in view
638660
.filter((spawn) => this.#activateScope.overlaps(spawn)) // in activate scope
661+
.toArray()
639662

640-
const newObjectSpawns = chunks.flatMap((c) => c.getPropSpawns())
663+
const newPropSpawns = chunks.values()
664+
.flatMap((c) => c.getPropSpawns())
641665
.filter((spawn) => initialLoad || !viewScope.overlaps(spawn)) // not in view
642666
.filter((spawn) => this.#activateScope.overlaps(spawn)) // in activate scope
667+
.toArray()
643668

644-
if (newCharSpawns.length > 0) {
669+
if (newActorSpawns.length > 0) {
645670
let i = 0
646-
for (const spawn of newCharSpawns) {
671+
for (const spawn of newActorSpawns) {
647672
i++
648673
this.#actors.add(Actor.fromSpawn(spawn))
649674
}
@@ -681,9 +706,9 @@ export class Field implements IField {
681706
await this.#items.loadAssets({ loadImage })
682707
}
683708

684-
if (newObjectSpawns.length > 0) {
709+
if (newPropSpawns.length > 0) {
685710
let i = 0
686-
for (const spawn of newObjectSpawns) {
711+
for (const spawn of newPropSpawns) {
687712
if (this.#props.get(spawn.i, spawn.j)) {
688713
// The space is already occupied by some other object
689714
continue

game/game-screen.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ const parseGridPosition = (hash: string) => {
2020
const start = parseGridPosition(globalThis.location.hash)
2121

2222
// The starting position of the main character
23-
const I = start?.i ?? -131
24-
const J = start?.j ?? 183
23+
const I = start?.i ?? -9964
24+
const J = start?.j ?? -9981
2525

2626
/**
2727
* The area which is visible to the user
@@ -148,6 +148,7 @@ export function GameScreen({ el, query }: Context) {
148148
setTimeout(() => {
149149
field.reset()
150150
field.me.fastTravel(i, j)
151+
field.me.unsetFollower()
151152
signal.centerPixel.update({ x: field.me.centerX, y: field.me.centerY })
152153
loop.start()
153154
}, 2000)

game/main-character.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,7 @@ export class IdleMainActor implements IdleDelegate {
6363
export class MoveEndMainActor implements MoveEndDelegate {
6464
onMoveEnd(actor: Actor, field: IField, _move: Move): void {
6565
field.peekItem(actor.i, actor.j)?.onCollect(actor, field)
66+
67+
field.props.get(actor.i, actor.j)?.onEnter(actor, field)
6668
}
6769
}

model/catalog.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface CatalogSource {
1717
readonly props: Record<string, {
1818
readonly src: string
1919
readonly canEnter: boolean
20+
readonly onEnter?: string
2021
readonly pushed?: string
2122
}>
2223
}
@@ -47,6 +48,7 @@ export interface PropDefinition {
4748
readonly type: string
4849
readonly canEnter: boolean
4950
readonly pushed?: string
51+
readonly onEnter?: string
5052
readonly src: string
5153
readonly href: string
5254
}
@@ -97,6 +99,7 @@ export class Catalog {
9799
catalog.props[type] = {
98100
type: type,
99101
canEnter: def.canEnter,
102+
onEnter: def.onEnter,
100103
pushed: def.pushed,
101104
src: def.src,
102105
href: new URL(def.src, url).href,
@@ -141,6 +144,7 @@ export class Catalog {
141144
props[def.type] = {
142145
src: def.src,
143146
canEnter: def.canEnter,
147+
onEnter: def.onEnter,
144148
pushed: def.pushed,
145149
}
146150
}

model/field-block.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,19 @@ export class PropSpawnInfo implements IBox {
7979
readonly y: number
8080
readonly w = CELL_SIZE
8181
readonly h = CELL_SIZE
82+
readonly data: unknown
8283
constructor(
8384
i: number,
8485
j: number,
8586
type: PropType,
8687
def: PropDefinition,
88+
data: unknown,
8789
) {
8890
this.id = `${i}.${j}.${type}` // Unique ID for the spawn
8991
this.i = i
9092
this.j = j
9193
this.type = type
94+
this.data = data
9295
this.def = def
9396
this.x = i * CELL_SIZE
9497
this.y = j * CELL_SIZE
@@ -100,11 +103,15 @@ export class PropSpawnInfo implements IBox {
100103
}
101104

102105
toJSON(): BlockMapSource["props"][number] {
103-
return {
106+
const json: BlockMapSource["props"][number] = {
104107
i: this.i,
105108
j: this.j,
106109
type: this.type,
107110
}
111+
if (this.data !== undefined) {
112+
json.data = this.data
113+
}
114+
return json
108115
}
109116
}
110117

@@ -274,6 +281,7 @@ interface BlockMapSource {
274281
i: number
275282
j: number
276283
type: string
284+
data?: unknown
277285
}[]
278286
field: string[]
279287
}
@@ -331,6 +339,7 @@ export class BlockMap {
331339
prop.j,
332340
prop.type as PropType,
333341
catalog.props[prop.type]!,
342+
prop.data,
334343
)
335344
)
336345
this.field = obj.field

model/item.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,6 @@ export class Item implements IItem {
106106
}
107107

108108
onCollect(actor: Actor, field: IField) {
109-
if (this.id) {
110-
Item.collect(this.id)
111-
}
112109
switch (this.def.type) {
113110
case "apple": {
114111
const delegate = new CollectApple()

model/prop.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CELL_SIZE } from "../util/constants.ts"
22
import type {
33
Dir,
4+
IActor,
45
IField,
56
IProp,
67
LoadOptions,
@@ -23,6 +24,7 @@ export class Prop implements IProp {
2324
readonly j: number
2425
readonly type: PropType
2526
readonly def: PropDefinition
27+
readonly data: unknown
2628
#motion: Motion | null = null
2729
readonly #actionQueue = new ActionQueue<Prop, PropAction>(
2830
(field, action) => {
@@ -60,6 +62,7 @@ export class Prop implements IProp {
6062
spawn.type,
6163
spawn.def,
6264
pushed,
65+
spawn.data,
6366
)
6467
}
6568

@@ -76,13 +79,15 @@ export class Prop implements IProp {
7679
type: PropType,
7780
def: PropDefinition,
7881
pushed: PushedDelegate | null,
82+
data: unknown,
7983
) {
8084
this.id = id
8185
this.i = i
8286
this.j = j
8387
this.type = type
8488
this.def = def
8589
this.#pushed = pushed
90+
this.data = data
8691
}
8792

8893
/**
@@ -152,6 +157,21 @@ export class Prop implements IProp {
152157
this.#pushed?.onPushed(event, this, field)
153158
}
154159

160+
onEnter(_actor: IActor, _field: IField): void {
161+
if (this.def.onEnter === "teleport") {
162+
// deno-lint-ignore no-explicit-any
163+
const data = this.data as any
164+
const i = data.i
165+
const j = data.j
166+
if (typeof i === "number" && typeof j === "number") {
167+
location.hash = ""
168+
setTimeout(() => {
169+
location.replace(`#${i},${j}`)
170+
}, 10)
171+
}
172+
}
173+
}
174+
155175
enqueueActions(...actions: PropAction[]): void {
156176
this.#actionQueue.enqueue(...actions)
157177
}

model/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export type IProp =
4343
& IEntity
4444
& IStepper
4545
& FieldEventTarget
46+
& { onEnter(actor: IActor, field: IField): void }
4647
& {
4748
id: string | null
4849
type: PropType

static/catalog/base.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@
100100
"pushed": "break",
101101
"canEnter": false
102102
},
103+
"portal": {
104+
"src": "../prop/portal.png",
105+
"canEnter": true,
106+
"onEnter": "teleport"
107+
},
103108
"a": {
104109
"src": "../prop/a.png",
105110
"canEnter": true

0 commit comments

Comments
 (0)