-
Notifications
You must be signed in to change notification settings - Fork 11
/
entity.go
280 lines (242 loc) · 8.02 KB
/
entity.go
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
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package engine
import (
"fmt"
"reflect"
"github.com/hajimehoshi/ebiten/v2"
"github.com/divVerent/aaaaxy/internal/level"
"github.com/divVerent/aaaaxy/internal/log"
m "github.com/divVerent/aaaaxy/internal/math"
"github.com/divVerent/aaaaxy/internal/propmap"
)
// An Entity is an object that exists in the game.
type Entity struct {
// Info needed for management.
Incarnation EntityIncarnation
SpawnTilesGrowth m.Delta
// Info needed for gameplay.
contents level.Contents
Rect m.Rect
BorderPixels int // Border applied to ALL sides. Used for entity tracing only.
Transform m.Orientation // Possibly needed for initialization.
name string // Possibly searched for.
RequireTiles bool // Entity requires tiles to be loaded.
// Info needed for rendering.
Orientation m.Orientation
Image *ebiten.Image
RenderOffset m.Delta
ResizeImage bool // Conceptually incompatible with RenderOffset.
Alpha float64
ColorAdd [4]float64
ColorMod [4]float64
zIndex int
// Intrusive list state.
indexInListPlusOne [numLists]int
// Entity's own state.
Impl EntityImpl
}
// EntityIncarnation represents a specific incarnation of an entity. Entities spawn more than once if their tile is seen more than once.
type EntityIncarnation struct {
ID level.EntityID
TilePos m.Pos
}
func (e EntityIncarnation) IsValid() bool {
return e.ID.IsValid()
}
type EntityImpl interface {
// Spawn initializes the entity based on a Spawnable.
// Receiver will be a zero struct of the entity type.
// Will usually remember a reference to the World and Entity.
// ID, Pos, Size and Orientation of the entity will be preset but may be changed.
Spawn(w *World, sp *level.SpawnableProps, e *Entity) error
// Despawn notifies the entity that it will be deleted.
Despawn()
// Update asks the entity to update its state.
Update()
// Touch notifies the entity that it was hit by another entity moving.
Touch(other *Entity)
}
// Some entities fulfill PrecacheImpl. These will get precached.
type Precacher interface {
// Precache gets called during level loading to preload anything the entity may need.
Precache(sp *level.Spawnable) error
}
// Some entities get a pre-despawn notification.
type PreDespawner interface {
// PreDespawn gets called when the game enters the menu. After this, either Despawn or Update will happen eventually.
PreDespawn()
}
// entityTypes is a helper map to know how to spawn an entity.
var entityTypes = map[string]EntityImpl{}
// RegisterEntityType adds an entity type to the spawn system.
// To be called from init() functions of entity implementations.
func RegisterEntityType(t EntityImpl) {
typeName := reflect.TypeOf(t).Elem().Name()
if entityTypes[typeName] != nil {
log.Fatalf("duplicate entity type: %v", typeName)
}
entityTypes[typeName] = t
log.Debugf("registered entity type %q", typeName)
}
// Precache all entities.
func precacheEntities(lvl *level.Level) error {
var err error
lvl.ForEachTile(func(pos m.Pos, t *level.LevelTile) {
for _, sp := range t.Tile.Spawnables {
if err != nil {
break
}
eTmpl := entityTypes[sp.EntityType]
if eTmpl == nil {
err = fmt.Errorf("unknown entity type %q", sp.EntityType)
break
}
if precacher, ok := eTmpl.(Precacher); ok {
err = precacher.Precache(sp)
if err != nil {
err = fmt.Errorf("failed to precache entity %v: %w", sp, err)
}
}
}
})
return err
}
// spawnAt spawns a given entity at a given location.
// Still need to provide a Spawnable. Transform should be the transform of the origin entity or tile.
func (w *World) spawnAt(sp *level.SpawnableProps, rect m.Rect, transform, tInv m.Orientation, incarnation EntityIncarnation) (*Entity, error) {
eTmpl := entityTypes[sp.EntityType]
if eTmpl == nil {
return nil, fmt.Errorf("unknown entity type %q", sp.EntityType)
}
eImplVal := reflect.New(reflect.TypeOf(eTmpl).Elem())
eImplVal.Elem().Set(reflect.ValueOf(eTmpl).Elem())
eImpl := eImplVal.Interface().(EntityImpl)
e := &Entity{
Incarnation: incarnation,
Transform: transform,
name: propmap.StringOr(sp.Properties, "name", ""),
Impl: eImpl,
Rect: rect,
Orientation: tInv.Concat(sp.Orientation),
SpawnTilesGrowth: sp.SpawnTilesGrowth,
}
e.Alpha = 1.0
e.ColorMod[0] = 1.0
e.ColorMod[1] = 1.0
e.ColorMod[2] = 1.0
e.ColorMod[3] = 1.0
w.link(e)
err := eImpl.Spawn(w, sp, e)
if err != nil {
w.unlink(e)
return nil, err
}
return e, nil
}
// Spawn turns a Spawnable into an Entity.
func (w *World) Spawn(sp *level.Spawnable, tilePos m.Pos, t *level.Tile) (*Entity, error) {
tInv := t.Transform.Inverse()
originTilePos := tilePos.Add(tInv.Apply(sp.LevelPos.Delta(t.LevelPos)))
incarnation := EntityIncarnation{
ID: sp.ID,
TilePos: originTilePos,
}
if _, found := w.incarnations[incarnation]; found {
return nil, nil
}
pivot2InTile := m.Pos{X: level.TileSize, Y: level.TileSize}
rect := tInv.ApplyToRect2(pivot2InTile, sp.RectInTile)
rect.Origin = originTilePos.Mul(level.TileSize).Add(rect.Origin.Delta(m.Pos{}))
return w.spawnAt(&sp.SpawnableProps, rect, t.Transform, tInv, incarnation)
}
// SpawnDetached spawns a detached new entity.
// Note that it will inherit the spawning entity's transform; sp.Orientation should be set to the same as the transform if one wants to undo that.
func (w *World) SpawnDetached(sp *level.SpawnableProps, rect m.Rect, orientation m.Orientation, by *Entity) (*Entity, error) {
return w.spawnAt(sp, rect, by.Transform, by.Transform.Inverse(), EntityIncarnation{
ID: level.InvalidEntityID,
TilePos: by.Incarnation.TilePos,
})
}
func (w *World) Despawn(e *Entity) {
e.Impl.Despawn()
w.unlink(e)
}
// MutateContents mutates an entity's contents.
func (w *World) MutateContents(e *Entity, mask, set level.Contents) {
if e.contents&mask == set {
return
}
w.unlink(e)
e.contents &= ^mask
e.contents |= set
w.link(e)
}
// MutateContentsBool mutates an entity's contents.
func (w *World) MutateContentsBool(e *Entity, mask level.Contents, set bool) {
if set {
w.MutateContents(e, mask, mask)
} else {
w.MutateContents(e, mask, 0)
}
}
// SetSolid makes an entity solid (or not).
func (w *World) SetSolid(e *Entity, solid bool) {
w.MutateContentsBool(e, level.SolidContents, solid)
}
// SetOpaque makes an entity opaque (or not).
func (w *World) SetOpaque(e *Entity, opaque bool) {
w.MutateContentsBool(e, level.OpaqueContents, opaque)
}
// SetZIndex sets an entity's Z index.
func (w *World) SetZIndex(e *Entity, index int) {
if e.zIndex == index {
return
}
w.unlink(e)
e.zIndex = index
w.link(e)
}
// Detach detaches an entity from its spawn origin.
// If the spawn origin is onscreen, this will respawn the entity from there this frame.
func (w *World) Detach(e *Entity) {
if !e.Incarnation.IsValid() {
return
}
w.unlink(e)
e.Incarnation.ID = level.InvalidEntityID
w.link(e)
}
func (e *Entity) Detached() bool {
return e.Incarnation.ID == level.InvalidEntityID
}
func (e *Entity) ZIndex() int {
return e.zIndex
}
func (e *Entity) Contents() level.Contents {
return e.contents
}
func (e *Entity) Name() string {
return e.name
}
// PlayerEntityImpl defines some additional methods player entities must have.
type PlayerEntityImpl interface {
EntityImpl
// EyePos is the location of the eye in world coordinates.
EyePos() m.Pos
// LookPos is the desired screen center position.
LookPos() m.Pos
// Respawned() notifies the entity that the world respawned it.
Respawned()
}