diff --git a/arena.lua b/arena.lua index 142561a..df9b754 100644 --- a/arena.lua +++ b/arena.lua @@ -8,9 +8,14 @@ function Arena:init(name) self.effects = Group() self.ui = Group():no_camera() self.main:disable_collision_between('player', 'player') + self.main:disable_collision_between('player', 'enemy') + self.main:disable_collision_between('enemy', 'enemy') + self.main:enable_trigger_between('player', 'enemy') self.x1, self.y1 = gw/2 - 0.8*gw/2, gh/2 - 0.8*gh/2 self.x2, self.y2 = gw/2 + 0.8*gw/2, gh/2 + 0.8*gh/2 + self.spawn_points = {{x = self.x1 + 32, y = self.y1 + 32}, {x = self.x1 + 32, y = self.y2 - 32}, {x = self.x2 - 32, y = self.y1 + 32}, {x = self.x2 - 32, y = self.y2 - 32}, {x = gw/2, y = gh/2}} + self.spawn_offsets = {{x = -12, y = -12}, {x = 12, y = -12}, {x = 12, y = 12}, {x = -12, y = 12}, {x = 0, y = 0}} Wall{group = self.main, vertices = math.to_rectangle_vertices(-40, -40, self.x1, gh + 40), color = bg[-1]} Wall{group = self.main, vertices = math.to_rectangle_vertices(self.x2, -40, gw + 40, gh + 40), color = bg[-1]} @@ -18,7 +23,7 @@ function Arena:init(name) Wall{group = self.main, vertices = math.to_rectangle_vertices(self.x1, self.y2, self.x2, gh + 40), color = bg[-1]} self.player = Unit{group = self.main, x = gw/2, y = gh/2, player = true, leader = true, character = 'vagrant'} - -- self.player:add_follower(Unit{group = self.main, player = true, color = red[0]}) + -- self.player:add_follower(Unit{group = self.main, player = true, character = 'seeker'}) end @@ -32,6 +37,10 @@ function Arena:update(dt) self.main:update(dt*slow_amount) self.effects:update(dt*slow_amount) self.ui:update(dt*slow_amount) + + if input.k.pressed then + self:spawn_enemy() + end end @@ -40,3 +49,12 @@ function Arena:draw() self.effects:draw() self.ui:draw() end + + +function Arena:spawn_enemy() + local p = table.random(self.spawn_points) + local o = table.random(self.spawn_offsets) + local leader = Unit{group = self.main, x = p.x + o.x, y = p.y + o.y, enemy = true, leader = true, character = 'seeker'} + leader:add_follower(Unit{group = self.main, x = p.x + o.x, y = p.y + o.y, enemy = true, character = 'seeker'}) + leader:add_follower(Unit{group = self.main, x = p.x + o.x, y = p.y + o.y, enemy = true, character = 'seeker'}) +end diff --git a/engine/game/physics.lua b/engine/game/physics.lua index e0bc546..646686c 100644 --- a/engine/game/physics.lua +++ b/engine/game/physics.lua @@ -452,7 +452,7 @@ end -- Applies a continuous amount of force to the object -- self:apply_force(100*math.cos(angle), 100*math.sin(angle)) function Physics:apply_force(fx, fy, x, y) - if self.body then self.body:applyForce(fx, fy, x, y) end + if self.body then self.body:applyForce(fx, fy, x or self.x, y or self.y) end return self end diff --git a/engine/game/state.lua b/engine/game/state.lua index 2ba45f6..4c5527e 100644 --- a/engine/game/state.lua +++ b/engine/game/state.lua @@ -28,11 +28,11 @@ end -- then you need to destroy everything that needs to be destroyed in an on_exit function and then recreate it again in the on_enter function. -- -- You'd add a state to the game like this: --- state.add(MyState'level_1') +-- main:add(MyState'level_1') -- You'd move to that state like so: --- state.go_to'level_1' --- state.go_to automatically calls on_exit for the currently active state and on_enter for the new one. --- You can access the currently active state with state.current. +-- main:go_to'level_1' +-- main:go_to automatically calls on_exit for the currently active state and on_enter for the new one. +-- You can access the currently active state with main.current. State = Object:extend() function State:init_state(name) self.name = name or random:uid() diff --git a/engine/game/steering.lua b/engine/game/steering.lua index 9ad2d1a..a65efe0 100644 --- a/engine/game/steering.lua +++ b/engine/game/steering.lua @@ -5,6 +5,7 @@ -- self:set_as_steerable(100, 1000) function Physics:set_as_steerable(max_v, max_f, max_turn_rate, turn_multiplier) self.steerable = true + self.steering_enabled = true self.heading = Vector() self.side = Vector() self.mass = 1 @@ -29,14 +30,14 @@ end function Physics:steering_update(dt) - if self.steerable then + if self.steerable and self.steering_enabled then local steering_force = self:calculate_steering_force(dt):div(self.mass) self:apply_force(steering_force.x, steering_force.y) local vx, vy = self:get_velocity() local v = Vector(vx, vy):truncate(self.max_v) self:set_velocity(v.x, v.y) if v:length_squared() > 0.00001 then - self.heading = self.v:clone():normalize() + self.heading = v:clone():normalize() self.side = self.heading:perpendicular() end end @@ -80,7 +81,8 @@ function Physics:seek_point(x, y, deceleration, weight) local v = d/((deceleration or 1)*0.08) v = math.min(v, self.max_v) local dvx, dvy = v*tx/d, v*ty/d - self.seek_f:set((dvx - self.v.x)*self.turn_multiplier*(weight or 1), (dvy - self.v.y)*self.turn_multiplier*(weight or 1)) + local vx, vy = self:get_velocity() + self.seek_f:set((dvx - vx)*self.turn_multiplier*(weight or 1), (dvy - vy)*self.turn_multiplier*(weight or 1)) else self.seek_f:set(0, 0) end end diff --git a/objects.lua b/objects.lua index 6d6797d..67f0431 100644 --- a/objects.lua +++ b/objects.lua @@ -7,8 +7,17 @@ function Unit:init(args) if self.character == 'vagrant' then self.color = fg[0] - self.visual_shape = 'triangle' + self.visual_shape = 'rectangle' self.classes = {'ranger', 'warrior', 'mage'} + elseif self.character == 'seeker' then + self.color = red[0] + self.visual_shape = 'capsule' + self.classes = {'seeker'} + if self.enemy then + self.enemy_behavior = 'seek' + self:calculate_stats() + self:set_as_steerable(self.v) + end end self:calculate_stats() @@ -49,8 +58,34 @@ function Unit:update(dt) else camera.r = math.lerp_angle_dt(0.005, dt, camera.r, 0) end end + if not self.player and self.leader then + local player = main.current.player + if self.enemy_behavior == 'seek' then + if self.being_pushed then + local v = math.length(self:get_velocity()) + if v < 25 then + self.being_pushed = false + self.steering_enabled = true + self:set_damping(0) + self:set_angular_damping(0) + end + self.r = self:get_angle() + else + self:seek_point(player.x, player.y) + self:wander(25, 100, 20) + self:rotate_towards_velocity(0.5) + self.r = self:get_angle() + end + end + end + if not self.leader then - local d = math.ceil(self.v*0.1)*self.follower_index + local ds + if self.parent.v >= 80 and self.parent.v <= 90 then ds = 8 end + if self.parent.v >= 20 and self.parent.v <= 30 then ds = 12 end + if not ds then error('undefined unit distance for parent velocity: ' .. self.parent.v) end + + local d = ds*self.follower_index local p = self.parent.previous_positions[d] if p then self:set_position(p.x, p.y) @@ -62,12 +97,31 @@ function Unit:update(dt) end +function Unit:on_trigger_enter(other) + if other:is(Unit) and other.enemy then + other:push(math.length(self:get_velocity())/3.5, self:angle_to_object(other)) + end +end + + +function Unit:push(f, r) + self.being_pushed = true + self.steering_enabled = false + self:apply_impulse(f*math.cos(r), f*math.sin(r)) + self:apply_angular_impulse(random:float(-12*math.pi, 12*math.pi)) + self:set_damping(1.5) + self:set_angular_damping(1.5) +end + + function Unit:draw() graphics.push(self.x, self.y, self.r, self.hfx.hit.x, self.hfx.hit.x) if self.visual_shape == 'triangle' then graphics.triangle(self.x, self.y, self.shape.w, self.shape.h, self.hfx.hit.f and fg[0] or self.color) elseif self.visual_shape == 'rectangle' then graphics.rectangle(self.x, self.y, self.shape.w, self.shape.h, 2, 2, self.hfx.hit.f and fg[0] or self.color) + elseif self.visual_shape == 'capsule' then + graphics.rectangle(self.x, self.y, self.shape.w, 0.525*self.shape.h, 2, 2, self.hfx.hit.f and fg[0] or self.color) end graphics.pop() @@ -154,35 +208,29 @@ function Unit:calculate_stats() for _, class in ipairs(self.classes) do if class == 'warrior' then self.class_hp_m = self.class_hp_m*1.4 - elseif class == 'ranger' then self.class_hp_m = self.class_hp_m*1 elseif class == 'mage' then self.class_hp_m = self.class_hp_m*0.6 elseif class == 'healer' then self.class_hp_m = self.class_hp_m*1.1 - elseif class == 'cycler' then self.class_hp_m = self.class_hp_m*0.9 end + elseif class == 'cycler' then self.class_hp_m = self.class_hp_m*0.9 + elseif class == 'seeker' then self.class_hp_m = self.class_hp_m*0.5 end end self.max_hp = (self.base_hp + self.class_hp_a + self.buff_hp_a)*self.class_hp_m*self.buff_hp_m for _, class in ipairs(self.classes) do if class == 'warrior' then self.class_dmg_m = self.class_dmg_m*1.1 elseif class == 'ranger' then self.class_dmg_m = self.class_dmg_m*1.2 - elseif class == 'mage' then self.class_dmg_m = self.class_dmg_m*1.4 - elseif class == 'healer' then self.class_dmg_m = self.class_dmg_m*1 - elseif class == 'cycler' then self.class_dmg_m = self.class_dmg_m*1 end + elseif class == 'mage' then self.class_dmg_m = self.class_dmg_m*1.4 end end self.dmg = (self.base_dmg + self.class_dmg_a + self.buff_dmg_a)*self.class_dmg_m*self.buff_dmg_m for _, class in ipairs(self.classes) do if class == 'warrior' then self.class_aspd_m = self.class_aspd_m*0.9 elseif class == 'ranger' then self.class_aspd_m = self.class_aspd_m*1.4 - elseif class == 'mage' then self.class_aspd_m = self.class_aspd_m*1 - elseif class == 'healer' then self.class_aspd_m = self.class_aspd_m*0.5 - elseif class == 'cycler' then self.class_aspd_m = self.class_aspd_m*1 end + elseif class == 'healer' then self.class_aspd_m = self.class_aspd_m*0.5 end end self.aspd = 1/((self.base_aspd + self.class_aspd_a + self.buff_aspd_a)*self.class_aspd_m*self.buff_aspd_m) for _, class in ipairs(self.classes) do - if class == 'warrior' then self.class_cycle_m = self.class_cycle_m*1 - elseif class == 'ranger' then self.class_cycle_m = self.class_cycle_m*1 - elseif class == 'mage' then self.class_cycle_m = self.class_cycle_m*1.25 + if class == 'mage' then self.class_cycle_m = self.class_cycle_m*1.25 elseif class == 'healer' then self.class_cycle_m = self.class_cycle_m*1.1 elseif class == 'cycler' then self.class_cycle_m = self.class_cycle_m*1.5 end end @@ -192,17 +240,14 @@ function Unit:calculate_stats() if class == 'warrior' then self.class_def_m = self.class_def_m*1.25 elseif class == 'ranger' then self.class_def_m = self.class_def_m*1.1 elseif class == 'mage' then self.class_def_m = self.class_def_m*1.5 - elseif class == 'healer' then self.class_def_m = self.class_def_m*1.2 - elseif class == 'cycler' then self.class_def_m = self.class_def_m*1 end + elseif class == 'healer' then self.class_def_m = self.class_def_m*1.2 end end self.def = (self.base_def + self.class_def_a + self.buff_def_a)*self.class_def_m*self.buff_def_m for _, class in ipairs(self.classes) do if class == 'warrior' then self.class_mvspd_m = self.class_mvspd_m*0.9 elseif class == 'ranger' then self.class_mvspd_m = self.class_mvspd_m*1.2 - elseif class == 'mage' then self.class_mvspd_m = self.class_mvspd_m*1 - elseif class == 'healer' then self.class_mvspd_m = self.class_mvspd_m*1 - elseif class == 'cycler' then self.class_mvspd_m = self.class_mvspd_m*1 end + elseif class == 'seeker' then self.class_mvspd_m = self.class_mvspd_m*0.3 end end self.v = (self.base_mvspd + self.class_mvspd_a + self.buff_mvspd_a)*self.class_mvspd_m*self.buff_mvspd_m end