summaryrefslogtreecommitdiff
path: root/entity.lua
blob: caa5226f7025a850ad87636b29e38d0a9fe06578 (plain) (blame)
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
Entities = {
    Player = 0,
    Sword = 1,
    Enemy = 2,
    Bow = 3,
    Particle = 4
}
States = {
    Walk = "walk",
    Idle = "idle",
    Active = "active",
    Equipped = "equipped",
    Slashing = "slashing",
    Drawing = "drawing",
    Drawn = "drawn"
}

Entity = {}
Entity.__index = Entity
function Entity:b_add_state(name, state)
    if self.states == nil then self.states = {} end
    self.states[name] = state
    if self.state == nil then self.state = name end
    return self
end
function Entity:b_state(name)
    assert(self.states[name] != nil)
    self.state = name
    self.state_stopwatch = 0
    return self
end
function Entity:b_render_order(ord)
    self.render_order = ord
    return self
end
function Entity:b_position(vec)
    self.position = vec2(vec)
    return self
end
function Entity:b_sprite_position(vec)
    self.sprite_position = vec2(vec)
    return self
end
function Entity:b_type(entity_type)
    self.entity_type = entity_type
    return self
end
function Entity:b_equipped(equipped)
    self.equipped = equipped
    return self
end
function Entity:b_collidable()
    self.collision = true
    return self
end
function Entity:b_live_for(t)
    self.life_time = t
    return self
end
function Entity:b_line_of_sight(vec)
    self.line_of_sight = vec2(vec)
    return self
end
function Entity:build()
    assert(self.transition_state)

    assert(self.position)
    assert(self.state)
    assert(self.state_stopwatch)
end

function Entity:transition_to(state_name)
    assert(self.states[state_name])
    if self.state ~= state_name then
        self.state = state_name
        self.state_stopwatch = 0
    end
    return self
end

function Entity:kill()
    self.life_time = -1
end

function Entity:update(dt)
    self:integrate(dt)

    self:update_line_of_sight()

    self:update_sprite_position()
    if (self.equipped != nil) then
        parent = self
        foreach(self.equipped, function (e) e:equipped_from(parent) end)
    end

    assert(self.transition_state)
    self:transition_state()

    if self.life_time ~= nil then
        self.life_time -= dt
    end
end

function Entity:update_line_of_sight()
    nv = self.velocity:apply(normalize_scalar)
    if nv.y != 0 then self.line_of_sight = vec2(0, nv.y)
    elseif nv.x != 0 then self.line_of_sight = vec2(nv.x, 0) end
end

-- prevent cobblestoning during non-manhattan movement by "lagging" the sprite
-- behind the actual physical position
function Entity:update_sprite_position()
    if self.sprite_position == nil then return end

    -- step in only x or y. trivial
    if self.velocity == nil or self.velocity.y == 0 or self.velocity.x == 0 then
        self.sprite_position.x = self.position.x
        self.sprite_position.y = self.position.y
        return
    end

    dpos = self.position - self.sprite_position
    dx, dy = dpos.x, dpos.y
    adx, ady = abs(dx), abs(dy)

    yslow = abs(self.velocity.y) < abs(self.velocity.x)
    xslow = abs(self.velocity.x) <= abs(self.velocity.y)

    pushx, pushy = false, false
    jerkdelta = 1
    if adx >= jerkdelta and xslow and ady >= 1 then
        pushx, pushy = true, true
    elseif ady >= jerkdelta and yslow and adx >= 1 then
        pushx, pushy = true, true
    elseif xslow and ady >= 1 then
        pushy = true
    elseif yslow and adx >= 1 then
        pushx = true
    end

    if pushx then
        func = flr
        if dx < 0 then func = ceil end
        self.sprite_position.x += func(dx)
    end
    if pushy then
        func = flr
        if dy < 0 then func = ceil end
        self.sprite_position.y += func(dy)
    end

    return self
end

function Entity:integrate(dt)
    self.state_stopwatch += dt
    if (self.velocity != nil) then
        self.position = self.position + (self.velocity * dt)
    end
end

_equipped_item_distance = 6
function Entity:equipped_from(parent, dist)
    dist = dist or _equipped_item_distance
    self.line_of_sight = vec2(parent.line_of_sight)

    offset = (parent.line_of_sight * dist)

    self.position = parent.position + offset
    self.sprite_position = parent.sprite_position + offset
end

--                  -1      0      1
_animation_keys = {"neg", "pos", "pos"}
function _get_animation_key(line_of_sight)
    n_line_of_sight = line_of_sight:apply(normalize_scalar)
    if n_line_of_sight.y ~= 0 then
        return _animation_keys[n_line_of_sight.y + 2] .. "_y"
    end
    return _animation_keys[n_line_of_sight.x + 2] .. "_x"
end

function Entity:render()
    pos = self.sprite_position or self.position

    animation = self.states[self.state].animation
    if (animation == nil) then return end
    key = _get_animation_key(self.line_of_sight)
    animation = animation[key] or animation
    assert(animation)

    frames_passed = flr(self.state_stopwatch / animation.dt)
    frame = animation.sequence[1 + (frames_passed % (#animation.sequence))]
    assert(frame)

    reflection = animation.reflect or vec2(false, false)
    spr(
        frame,
        self.sprite_position.x, self.sprite_position.y,
        1, 1,
        reflection.x, reflection.y
    )
end

_id = 1
function _next_id()
    i = _id
    _id += 1
    return i
end
function entity()
    id = _next_id()
    World.add(setmetatable(
        {
            id = id,
            velocity = vec2(0,0),
            collision = false
        }, Entity
    ))
    return World.get(id)
end