Class = require 'hump.class' require 'entities/physicsentity' require 'settings' require 'entities/debug' require 'entities/animation' require 'entities/ground' require 'entities/arrow' Matrix = require 'matrix' VectorLight = require 'hump/vector-light' require 'utils' require 'machinegun' -- normalizes angle to be between [0 .. 2 pi[ local normalizeAngle = function(angle) if angle < 0 then angle = -angle angle = angle - 2 * math.pi * math.floor(angle / (2 * math.pi)) angle = math.pi * 2 - angle else angle = angle - 2 * math.pi * math.floor(angle / (2 * math.pi)) end return angle end local CLANG_SFX = { love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #1.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #2.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #3.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #4.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #5.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #6.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #7.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #8.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #9.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #10.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #11.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #12.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #13.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #14.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #15.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #16.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #17.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #18.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #19.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #20.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #21.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #22.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #23.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #24.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #25.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #26.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #27.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #28.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #29.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #30.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #31.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #32.wav"), love.audio.newSource("resources/audio/clangs/cast iron clangs - Marker #33.wav") } local function sign(v) if v >= 0 then return 1 else return -1 end end local lift = function(angle) return 1.68429 * math.exp(-math.pow(angle / math.pi * 180.0 -17.3801, 2.0) / (2.0 * math.pow(15.0, 2.0))) end local fwd_frict_coeff = 0.1 local fwd_frict_coeff_decelare_multiplier = 3 local nor_frict_coeff = 0.8 local tail_frict_coeff = 0.4 local tail_area = 5.0 local turn_coeff = 500.0 local wing_lift = 0.3 local accel_speed = 400.0 local decel_speed = 400.0 local max_motorPower = ENGINE_MAX local head_area = 1.0 local plane_area = 10.0 local motor_speed_ratio = 80.0 local flipping_speed = 1.0 / 0.5 / 500 -- 0.5 seconds at speed 500 local out_of_bounds_coeff_multiplier = 10.0 local explosionFrames = AnimationFrames("resources/graphics/explosion-%04d.png", 36, 15, true) Plane = Class{ __includes = PhysicsEntity, frames = {}, motorPower = INITIAL_ENGINE_SPEED, debugVectors = {}, turningCw = false, turningCcw = false, accelerating = false, decelerating = false, goingRight = true, -- the plane is upside up and going right (or upside down and going left) orientationAngle = 0, controlX = 0, controlY = 0, throttleX = 0, throttleY = 0, kbdShooting = false, joyShooting = false, contactingGround = false, init = function(self, x, y, xDir, yDir, r, g, b, level) local density = 50 PhysicsEntity.init(self, x, y, level, "dynamic", 0.2) self.r = r self.g = g self.b = b self.level = level self.xsize = 52 self.ysize = 50 self.collisionXSize = 4.0 * PIXELS_PER_METER self.collisionYSize = 3.5 * PIXELS_PER_METER self.shape = love.physics.newRectangleShape(0, -4, self.collisionXSize, self.collisionYSize) PhysicsEntity.attachShape(self, density) self.body:setX(self.x + self.xsize / 2) self.body:setY(self.y - self.ysize / 2) self.body:setLinearVelocity(xDir, yDir) self.goingRight = xDir >= 0 self.body:setAngle(math.atan2(yDir, xDir)) -- self.body:setMassData(self.xsize / 2, self.ysize / 2, 440 * PIXELS_PER_METER, -1.0) -- self.body:setMassData(self.xsize / 2, 0, 430, 158194) self.body:setMassData(7, 5, 800, 558194.140625) self.body:setAngularDamping(0.1) self.fixture:setFriction(0.5) self.angle = 0 --x 16 y 17.200000762939 mass 3520 inertia 1942476.875 --x 0 y 0 mass 860.00006103516 inertia 158194.140625 local x, y, mass, inertia = self.body:getMassData() --print("x", x, "y", y, "mass", mass, "inertia", inertia) for frame = 0,35 do self.frames[frame] = love.graphics.newImage(string.format("resources/graphics/plane-%04d.png", frame)) end self.quad = love.graphics.newQuad(0, 0, self.xsize, self.ysize, self.frames[0]:getWidth(), self.frames[0]:getHeight()) self.machinegun = MachineGun(self, PLANE_DEFAULT_GUN) self.health = PLANE_HEALTH self.powerupmode = nil self.outOfBoundsArrow = nil self.motorSound = love.audio.newSource("resources/audio/motorsound.wav", "static") self.motorSound:rewind() self.motorSound:play() self.motorSound:setLooping(true) if self.goingRight then self.orientationAngle = 0 else self.orientationAngle = 180 end end; receiveDamage = function(self, amount) local oldHealth = self.health self.health = math.max(0, self.health - amount); if oldHealth > 0 and self.health == 0 then self:die() end end; die = function(self) self.health = 0 if self.joyShooting or self.kbdShooting then self.machinegun:stopShooting() end self.joyShooting = false self.kbdShooting = false Animation(self.body:getX(), self.body:getY(), self.level, explosionFrames) self:getOwner():setPlane(nil) self.motorSound:stop() self:delete() end; delete = function(self) self.motorSound:stop() PhysicsEntity.delete(self) end; getGunPosition = function(self) local x = self.body:getX() local y = self.body:getY() local pos = rad_dist_to_xy(self.angle, self.xsize) return {x + pos[1], y + pos[2]} end; setPowerUpMode = function(self, powerupmode) --print("Got powerup!") self.powerupmode = powerupmode self.powerupmode:activate(self) end; accelerate = function(self, down) self.accelerating = down end; decelerate = function(self, down) self.decelerating = down end; shoot = function(self, down) if self.health > 0 then if down then if not self.kbdShooting and not self.joyShooting then self.machinegun:startShooting() end self.kbdShooting = true else self.kbdShooting = false if not self.kbdShooting and not self.joyShooting then self.machinegun:stopShooting() end end end end; noLongerHitBy = function(self, by) if by:isinstance(Ground) then self.contactingGround = false end end; wasHitBy = function(self, by) if by:isinstance(Ground) then local angle = normalizeAngle(self.body:getAngle()) self.contactingGround = true if self.flipping == nil and ((self.goingRight and (angle > math.pi * 2 - math.pi / 4 or angle < math.pi / 4)) or (not self.goingRight and (angle > math.pi - math.pi / 4 and angle < math.pi + math.pi / 4))) then -- don't die this time. else self:receiveDamage(1000) self:getOwner():addScore(SUICIDE_SCORE) end elseif not by:isinstance(Ground) then local i = love.math.random(#CLANG_SFX) CLANG_SFX[i]:play() end end; update = function(self, dt) local mass_x, mass_y, mass, inertia = self.body:getMassData() self.debug = {} PhysicsEntity.update(self, dt) self.machinegun:update(dt) local dir_coeff; if self.goingRight then dir_coeff = 1 else dir_coeff = -1 end local coeff_multiplier = 1 local vel_x, vel_y = self.body:getLinearVelocity() local abs_vel = VectorLight.len(vel_x, vel_y) if self.body:getY() < 0 then self.motorPower = 0 if vel_y < 0 then coeff_multiplier = out_of_bounds_coeff_multiplier end if not self.outOfBoundsArrow then self.outOfBoundsArrow = Arrow(0, 0, self.level, self.r, self.g, self.b) end self.outOfBoundsArrow:setX(self.body:getX()) elseif self.outOfBoundsArrow then self.outOfBoundsArrow:delete() self.outOfBoundsArrow = nil end if self.powerupmode ~= nil then self.powerupmode:update(dt) end self.x, self.y = self.fixture:getBoundingBox() self.angle = self.body:getAngle() if self.accelerating then self.motorPower = math.min(max_motorPower, self.motorPower + dt * accel_speed) end if self.decelerating then self.motorPower = math.max(0.0, self.motorPower - dt * decel_speed) end self.motorPower = math.max(0.0, math.min(max_motorPower, self.motorPower + dt * self.throttleY * accel_speed)) -- -- local base = -- let base = V.base (V.vec_of_ang (~-(body#get_angle))) in local basde = Matrix{{math.cos(-self.angle), math.sin(-self.angle)}, {math.cos(-self.angle + math.pi / 2), math.sin(-self.angle + math.pi / 2)}} local to_base = function(x, y) local m = Matrix.mul(basde, Matrix{{x}, {y}}) -- print("to_base", x, y, "->", Matrix.tostring(m), " = ", m[1][1], m[2][1]) return m end -- print("-") -- print(Matrix.tostring(base)) local fwd_x, fwd_y = VectorLight.rotate(self.angle, 1, 0) local normal_x, normal_y = VectorLight.perpendicular(fwd_x, fwd_y) local fwd_vel = VectorLight.dot(vel_x, vel_y, fwd_x, fwd_y) local normal_vel = VectorLight.dot(vel_x, vel_y, normal_x, normal_y) -- print("fwd_vel", fwd_vel, "normal_vel", normal_vel, "vel_x", vel_x, "vel_y", vel_y, "normal_x", normal_x, "normal_y", normal_y) table.insert(self.debug, DebugVector("vel", self.body:getX(), self.body:getY(), vel_x, vel_y)) table.insert(self.debug, DebugVector("fwd", self.body:getX(), self.body:getY(), fwd_x, fwd_y)) table.insert(self.debug, DebugVector("normal", self.body:getX(), self.body:getY(), normal_x, normal_y)) local tail_speed = self.body:getAngularVelocity() * math.pi * 2.0 * self.xsize / 2.0 / PIXELS_PER_METER -- print("angular velocity", self.body:getAngularVelocity()) local tail_vel = to_base(0, tail_speed) -- hmm?! not tail_speed, 0? local abs_tail_vel_x, abs_tail_vel_y = VectorLight.add(vel_x, vel_y, tail_vel[1][1], tail_vel[2][1]) -- table.insert(self.debug, DebugVector("tail", self.body:getX(), self.body:getY(), tail_vel[1][1], tail_vel[2][1])) --print("absolute tail veloicty: ", abs_tail_vel_x, abs_tail_vel_y) local head_angle = self.angle local rel_force = function(label, force_x, force_y, rel_at_x, rel_at_y) if label == "turn" or label == "lift" or label == "hddrag" or label == "airdrag" or label == "tf" or label == "motor" then -- rdx, rdy = VectorLight.add(rdx, rdy, self.body:getX(), self.body:getY()) local base_force = to_base(force_x, force_y) local base_rel_at = to_base(rel_at_x, rel_at_y) base_rel_at_x, base_rel_at_y = VectorLight.add(base_rel_at[1][1], base_rel_at[2][1], self.body:getX(), self.body:getY()) self.body:applyForce(base_force[1][1], base_force[2][1], base_rel_at_x, base_rel_at_y) at_x, at_y = VectorLight.add(base_rel_at_x, base_rel_at_y, base_force[1][1], base_force[2][1]) --print(at_x, at_y) -- print("base matrix", Matrix.tostring(base)) -- print("force", force_x, force_y) -- print("force after transformation", Matrix.tostring(to_base(force_x, force_y))) -- print("base_force", base_force[1][1], base_force[2][1]) -- print("base_force'", Matrix.tostring(base_force)) table.insert(self.debug, DebugVector(label, base_rel_at_x, base_rel_at_y, base_force[1][1], base_force[2][1])) end end local speed_angle if abs_vel < 1.0 then speed_angle = head_angle else speed_angle = math.atan2(vel_y, vel_x) end local air_wing_angle local tmp tmp = head_angle - speed_angle if tmp > math.pi then air_wing_angle = tmp - 2.0 * math.pi else air_wing_angle = tmp end local lift_coeff = lift(-air_wing_angle) * dir_coeff -- print("lift coeff: ", lift_coeff) -- motor rel_force("motor", self.motorPower * motor_speed_ratio * PIXELS_PER_METER, 0, mass_x + 0.2, mass_y + 0.4) --self.body:applyForce(dx, dy); -- table.insert(self.debug, DebugVector(self.body:getX(), self.body:getY(), dx, dy)) -- (* self#add_force "fwddrag" *) -- (* (Gg.V2.smul (fwd_frict_coeff *. fwd_vel ** 2.0 *. head_area) (V.unit (negate vel))) *) -- (* (to_base (Gg.V2.v 0.0 0.0)); *) local airdrag = coeff_multiplier * -fwd_frict_coeff * math.pow(fwd_vel, 2.0) * head_area if self.decelerating then airdrag = airdrag * fwd_frict_coeff_decelare_multiplier end rel_force("airdrag", airdrag, 0, 0, 0.2 * self.ysize) -- Air friction (and drag?) opposes movement towards plane velocity normal also -- hdd drag local hddrag_x, hddrag_y = VectorLight.mul(coeff_multiplier * nor_frict_coeff * math.pow(normal_vel, 2.0) * plane_area * sign(normal_vel), 0, -1.0) rel_force("hddrag", hddrag_x, hddrag_y, mass_x, mass_y); local lift_x, lift_y = VectorLight.mul(wing_lift * math.pow(fwd_vel, 2.0) * lift_coeff, 0, -1) rel_force("lift", lift_x, lift_y, mass_x, mass_y) local tail_frict = tail_area * coeff_multiplier * tail_frict_coeff * math.pow(normal_vel, 2.0) * -sign(normal_vel) -- print("tail_speed", tail_speed) rel_force("tf", 0, tail_frict, -self.xsize / 2, 0) -- if self.turningCcw then -- dx, dy = VectorLight.rotate(self.angle, 0, 50000) -- elseif self.turningCw then -- dx, dy = VectorLight.rotate(self.angle, 0, -50000) -- end dy = 0 if self.turningCcw then dy = 1 elseif self.turningCw then dy = -1 else dy = self.controlY end if dy ~= 0 then dx, dy = VectorLight.mul(fwd_vel * turn_coeff, 0, dy) -- dx, dy = VectorLight.mul(turn_coeff * 10000 -- * sign(fwd_vel) -- , dx, dy) -- print("dx", dx, "dy", dy, "tail_speed", tail_speed) -- self.body:applyForce(dx, dy, self.body:getX() + rdx, self.body:getY() + rdy) rel_force("turn", dx, dy, -0.4 * self.xsize, 0); end -- print(dx, dy) -- table.insert(self.debug, DebugVector(self.x + width / 2, self.y + height / 2, 0, -100)) -- table.insert(self.debug, DebugVector(self.x, self.y, -100, 0)) -- table.insert(self.debug, DebugVector(self.x + 55, self.y + 10, dx, dy) local xy = to_base(mass_x, mass_y) local x, y = VectorLight.add(xy[1][1], xy[2][1], self.body:getX(), self.body:getY()) table.insert(self.debug, DebugCircle("mc", x, y)) local motorEffort = (self.motorPower / (math.max(fwd_vel, 100) + 1) - 1) / 2 + 1 self.motorSound:setPitch(motorEffort * self.motorPower / ENGINE_MAX + 0.5) if self.flipping ~= nil then if self.contactingGround then self:die() else self.flipping = self.flipping + flipping_speed * dt * fwd_vel local finished = self.flipping <= -1.0 or self.flipping >= 1.0 if finished then self.flipping = 1.0 end if self.goingRight then self.orientationAngle = 180 * self.flipping else self.orientationAngle = 180 + 180 * self.flipping end self.orientationAngle = self.orientationAngle % 360 if finished then self.goingRight = not self.goingRight self.flipping = nil end end end end; draw = function(self) if self.health > 0 then PhysicsEntity.draw(self) love.graphics.push() love.graphics.setColor(self.r, self.g, self.b); love.graphics.translate(self.body:getX(), self.body:getY()) love.graphics.scale(-1, 1) love.graphics.draw(self.frames[math.floor(self.orientationAngle / 10.0)], self.quad, 0, 0, -self.angle, 1, 1, self.xsize / 2, self.ysize / 2) love.graphics.pop() drawDebug(self.debug) end end; cw = function(self, isTurning) self.turningCw = isTurning end; ccw = function(self, isTurning) self.turningCcw = isTurning end; analog = function(self, controlY, throttleX, throttleY) if self.health > 0 then self.controlY = controlY self.throttleX = throttleX self.throttleY = -throttleY end end; flip = function(self, down) if self.flipping == nil then self.flipping = 0 end end }