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 = 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