Advertisement
Guest User

Untitled

a guest
May 22nd, 2015
203
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.06 KB | None | 0 0
  1. ------------------------------
  2. -- Generic enemy node
  3. -- Written by G3nius
  4. -- Refactor by jhoff
  5. --
  6. -- Add a node to the .tmx of 'type' == 'enemy'
  7. --    with an 'enemytype' property set to the type of enemy ( I.E. 'hippy' / 'frog' / etc )
  8. -- You must create a src/nodes/enemies file to define unique
  9. --    animation frames, movement function and additional properties.
  10. ------------------------------
  11.  
  12. local collision  = require 'hawk/collision'
  13. local gamestate = require 'vendor/gamestate'
  14. local anim8 = require 'vendor/anim8'
  15. local Timer = require 'vendor/timer'
  16. local tween = require 'vendor/tween'
  17. local cheat = require 'cheat'
  18. local sound = require 'vendor/TEsound'
  19. local token = require 'nodes/token'
  20. local game = require 'game'
  21. local utils = require 'utils'
  22. local window = require 'window'
  23. local Dialog = require 'dialog'
  24. local tween = require 'vendor/tween'
  25.  
  26.  
  27. local Enemy = {}
  28. Enemy.__index = Enemy
  29. Enemy.isEnemy = true
  30.  
  31. function Enemy.new(node, collider, enemytype)
  32.   local enemy = {}
  33.   setmetatable(enemy, Enemy)
  34.   enemy.minimum_x = -math.huge -- -3000
  35.   enemy.minimum_y = -math.huge -- -3000
  36.   enemy.maximum_x = math.huge -- 30000
  37.   enemy.maximum_y = math.huge -- 2000
  38.  
  39.   local type = node.properties.enemytype or enemytype
  40.  
  41.   enemy.type = type
  42.  
  43.   enemy.props = utils.require('nodes/enemies/' .. type)
  44.   local sprite_sheet
  45.   if node.properties.sheet then
  46.     sprite_sheet = 'images/enemies/' .. node.properties.sheet .. '.png'
  47.   else
  48.     sprite_sheet = 'images/enemies/' .. type .. '.png'
  49.   end
  50.   enemy.sprite = love.graphics.newImage( sprite_sheet )
  51.   enemy.sprite:setFilter('nearest', 'nearest')
  52.  
  53.   enemy.grid = anim8.newGrid( enemy.props.width, enemy.props.height, enemy.sprite:getWidth(), enemy.sprite:getHeight() )
  54.  
  55.   enemy.node_properties = node.properties
  56.   enemy.node = node
  57.   enemy.collider = collider
  58.  
  59.   enemy.dead = false
  60.   enemy.dying = false
  61.   enemy.idletime = 0
  62.  
  63.   assert( enemy.props.damage, "You must provide a 'damage' value for " .. type )
  64.   enemy.special_damage = enemy.props.special_damage or {}
  65.  
  66.   assert( enemy.props.hp, "You must provide a 'hp' ( hit point ) value for " .. type )
  67.   assert( tonumber(enemy.props.hp),"Hp must be a number" )
  68.   enemy.hp = tonumber(enemy.props.hp)
  69.  
  70.   enemy.height = enemy.props.height
  71.   enemy.width = enemy.props.width
  72.   enemy.bb_width = enemy.props.bb_width or enemy.width
  73.   enemy.bb_height = enemy.props.bb_height or enemy.height
  74.  
  75.   enemy.position_offset = enemy.props.position_offset or {x=0,y=0}
  76.  
  77.   -- Height to be used when offsetting an enemy to its node
  78.   local height = node.height or enemy.height
  79.  
  80.   -- adjust position so bottom is lined up with node bottom
  81.   enemy.position = {
  82.     x = node.x + ( enemy.position_offset.x or 0),
  83.     y = node.y + height - enemy.height + ( enemy.position_offset.y or 0)
  84.   }
  85.   --enemy.velocity = enemy.props.velocity or {x=0,y=0}
  86.   enemy.velocity = {
  87.     x = node.velocityX or (node.velocity and node.velocity.x) or 0,
  88.     y = node.velocityY or (node.velocity and node.velocity.y) or 0
  89.   }
  90.  
  91.   enemy.last_jump = 0
  92.  
  93.   enemy.jumpkill = enemy.props.jumpkill
  94.   if enemy.jumpkill == nil then enemy.jumpkill = true end
  95.  
  96.   enemy.dyingdelay = enemy.props.dyingdelay and enemy.props.dyingdelay or 0.75
  97.   enemy.revivedelay = enemy.props.revivedelay and enemy.props.revivedelay or .5
  98.  
  99.   enemy.state = 'default'
  100.   enemy.direction = node.properties.direction or 'left'
  101.   enemy.offset_hand_right = {}
  102.   enemy.offset_hand_right[1] = enemy.props.hand_x or enemy.width/2
  103.   enemy.offset_hand_right[2] = enemy.props.hand_y or enemy.height/2
  104.   enemy.chargeUpTime = enemy.props.chargeUpTime
  105.   enemy.player_rebound = enemy.props.player_rebound or 300
  106.   enemy.vulnerabilities = enemy.props.vulnerabilities or {}
  107.   enemy.attackingWorld = false
  108.   enemy.fadeIn = enemy.props.fadeIn or false
  109.   enemy.animations = {}
  110.  
  111.   for state, data in pairs( enemy.props.animations ) do
  112.     enemy.animations[state] = {}
  113.     for dir, a in pairs( data ) do
  114.       enemy.animations[ state ][ dir ] = anim8.newAnimation( a[1], enemy.grid( unpack(a[2]) ), a[3])
  115.     end
  116.   end
  117.  
  118.   enemy.bb = collider:addRectangle(node.x, node.y,
  119.                                    enemy.props.bb_width or enemy.props.width,
  120.                                    enemy.props.bb_height or enemy.props.height)
  121.   enemy.bb.node = enemy
  122.   enemy.bb_offset = enemy.props.bb_offset or {x=0,y=0}
  123.  
  124.   if enemy.props.passive then
  125.     collider:setGhost(enemy.bb)
  126.   end
  127.  
  128.   if enemy.props.attack_bb then
  129.     enemy.attack_bb = collider:addRectangle(node.x, node.y,
  130.                                             enemy.props.attack_width or enemy.props.width,
  131.                                             enemy.props.attack_height or enemy.props.height)
  132.     enemy.attack_bb.node = enemy
  133.     enemy.attack_offset = enemy.props.attack_offset or {x=0,y=0}
  134.     collider:setGhost(enemy.attack_bb)
  135.     enemy.last_attack = 0
  136.   end
  137.   enemy.enterScript = enemy.props.enterScript or false
  138.   enemy.deathScript = enemy.props.deathScript or false
  139.   enemy.rage = false
  140.  
  141.   enemy.foreground = node.properties.foreground or enemy.props.foreground or false
  142.  
  143.   return enemy
  144. end
  145.  
  146. function Enemy:enter( player )
  147.   if self.props.enter then
  148.     self.props.enter(self, player)
  149.   end
  150.  
  151.   if self.enterScript then
  152.     player.freeze = true
  153.     Dialog.new(self.enterScript, function()
  154.         player.freeze = false
  155.       end)
  156.   end
  157. end
  158.  
  159. function Enemy:animation()
  160.   if self.animations[self.state] == nil then
  161.     print( string.format( "Warning: No animation supplied for %s::%s", self.type, self.state ) );
  162.     return self.animations["default"][self.direction]
  163.   else
  164.     return self.animations[self.state][self.direction]
  165.   end
  166. end
  167.  
  168. function Enemy:hurt( damage, special_damage, knockback )
  169.   if self.dead then return end
  170.   if self.props.die_sound then sound.playSfx( self.props.die_sound ) end
  171.  
  172.   if not damage then damage = 1 end
  173.   self.state = 'hurt'
  174.  
  175.   -- Subtract from hp total damage including special damage
  176.   self.hp = self.hp - self:calculateDamage(damage, special_damage)
  177.  
  178.   if self.hp <= 0 then
  179.     self.state = 'dying'
  180.     self.dying = true
  181.     self:cancel_flash()
  182.  
  183.     if self.containerLevel and self.props.splat then
  184.       table.insert(self.containerLevel.nodes, 5, self.props.splat(self))
  185.     end
  186.  
  187.     self.collider:setGhost(self.bb)
  188.     self.collider:setGhost(self.attack_bb)
  189.    
  190.     if self.currently_held then
  191.       self.currently_held:die()
  192.     end
  193.     Timer.add(self.dyingdelay, function()
  194.       if self.props.die then self.props.die( self ) else self:die() end
  195.        
  196.     end)
  197.     if self.reviveTimer then Timer.cancel( self.reviveTimer ) end
  198.     self:dropTokens()
  199.   else
  200.     if knockback and not self.knockbackActive then
  201.       self.knockbackActive = true
  202.       tween.start(0.5, self.position,
  203.               {x = self.position.x + (knockback or 0) * (self.props.knockback or 1)},
  204.               'outCubic',
  205.               function() self.knockbackActive = false end)
  206.     end
  207.     if not self.flashing then
  208.       self:start_flash()
  209.     end
  210.     if self.props.hurt then self.props.hurt( self ) end
  211.   end
  212. end
  213.  
  214. -- Compares vulnerabilities to a weapons special damage and sums up total damage
  215. function Enemy:calculateDamage(damage, special_damage)
  216.   if self.props.calculateDamage then
  217.     self.props.calculateDamage(self, damage, special_damage)
  218.   end
  219.  
  220.   if not special_damage then
  221.     return damage
  222.   end
  223.   for _, value in ipairs(self.vulnerabilities) do
  224.     if special_damage[value] ~= nil then
  225.       damage = damage + special_damage[value]
  226.     end
  227.   end
  228.  
  229.   return damage
  230. end
  231.  
  232. function Enemy:start_flash()
  233.   if not self.blink then
  234.     self.blink = Timer.addPeriodic(.12, function()
  235.       self.flash = not self.flash
  236.     end, math.ceil(self.revivedelay / .12))
  237.   end
  238.   if self.reviveTimer then Timer.cancel( self.reviveTimer ) end
  239.   self.reviveTimer = Timer.add( self.revivedelay, function()
  240.                                 self.state = 'default'
  241.                                 self:cancel_flash()
  242.                                 self.flashing = false
  243.                                 end )
  244.   self.flashing = true
  245. end
  246.  
  247. function Enemy:cancel_flash()
  248.   self.flashing = false
  249.   if self.blink then
  250.     Timer.cancel(self.blink)
  251.     self.blink = nil
  252.   end
  253.   self.flash = false
  254. end
  255.  
  256. function Enemy:die()
  257.   --if self.props.die then self.props.die( self ) end
  258.   self.dead = true
  259.   self.collider:remove(self.bb)
  260.   self.collider:remove(self.attack_bb)
  261.   self.bb = nil
  262.   self.attack_bb = nil
  263.   if self.containerLevel then
  264.     self.containerLevel:removeNode(self)
  265.   end
  266. end
  267.  
  268. function Enemy:dropTokens()
  269.   if not self.props.tokens or self.props.tokens == 0 then return end
  270.  
  271.   for i=1, self.props.tokens do
  272.     local r = math.random(100) / 100
  273.     for _,d in pairs( self.props.tokenTypes ) do
  274.       if r < d.p then
  275.         local node = {
  276.           type = "token",
  277.           name = d.item,
  278.           x = self.position.x + self.props.width / 2,
  279.           y = self.position.y + self.props.height,
  280.           width = 24,
  281.           height = 24,
  282.           properties = {
  283.             life = 5,
  284.             value = d.v
  285.           }
  286.         }
  287.         local token = token.new(node,self.collider)
  288.         self.containerLevel:addNode(token)
  289.         break
  290.       end
  291.     end
  292.   end
  293. end
  294.  
  295. function Enemy:collide(node, dt, mtv_x, mtv_y)
  296.   function attack()
  297.     -- attack
  298.     if self.props.attack_sound then
  299.       if not self.attackingWorld then
  300.         if type(self.props.attack_sound) == 'table' then
  301.           sound.playSfx( self.props.attack_sound[math.random(#self.props.attack_sound)] )
  302.         else
  303.           sound.playSfx( self.props.attack_sound )
  304.         end
  305.       end
  306.     end
  307.  
  308.     if self.props.attack then
  309.       self.props.attack(self,self.props.attackDelay)
  310.     elseif self.animations['attack'] then
  311.       self.state = 'attack'
  312.       if not self.rage then
  313.         Timer.add(1, function()
  314.           if self.state ~= 'dying' then self.state = 'default' end
  315.         end)
  316.       end
  317.     end
  318.   end
  319.  
  320.   if node.isWall then
  321.     attack()
  322.  
  323.     if self.props.damage ~= 0 then
  324.       if self.attackingWorld then return end
  325.       self.attackingWorld = true
  326.       node:hurt(self.props.damage, self.props.special_damage)
  327.       Timer.add(1.25, function()
  328.         self.attackingWorld = false
  329.       end)
  330.     end
  331.   end
  332.  
  333.   if not node.isPlayer or
  334.      self.props.peaceful or
  335.      self.dead or
  336.      node.dead then return end
  337.  
  338.   local player = node
  339.   if player.rebounding or player.dead then
  340.     player.current_enemy = nil
  341.     return
  342.   end
  343.  
  344.   if not player.current_enemy then
  345.      player.current_enemy = self
  346.   end
  347.  
  348.   if player.current_enemy ~= self then
  349.     player.velocity.x = -player.velocity.x/100
  350.   return end
  351.  
  352.   local _, _, _, playerBottom = player.bottom_bb:bbox()
  353.   local _, enemyTop, _, y2 = self.bb:bbox()
  354.   local headsize = 3*(y2 - enemyTop) / 4
  355.  
  356.   if playerBottom >= enemyTop and (playerBottom - enemyTop) < headsize
  357.     and player.velocity.y > self.velocity.y and self.jumpkill then
  358.     -- successful attack
  359.     self:hurt(player.jumpDamage)
  360.     -- reset fall damage when colliding with an enemy
  361.     player.fall_damage = 0
  362.     player.velocity.y = -450 * player.jumpFactor
  363.   end
  364.  
  365.   if cheat:is('god') then
  366.     self:hurt(self.hp)
  367.     return
  368.   end
  369.  
  370.   if player.invulnerable or self.state == 'dying' or self.state == 'hurt' then
  371.     return
  372.   end
  373.  
  374.   -- attack
  375.   attack()
  376.  
  377.   if self.props.damage ~= 0 then
  378.     if node.isPlayer then
  379.       player:hurt(self.props.damage)
  380.       player.top_bb:move(mtv_x, mtv_y)
  381.       player.bottom_bb:move(mtv_x, mtv_y)
  382.       player.velocity.y = -450
  383.       player.velocity.x = self.player_rebound * ( player.position.x < self.position.x + ( self.props.width / 2 ) + self.bb_offset.x and -1 or 1 )
  384.     elseif node.isWall then
  385.       node:hurt(self.props.damage)
  386.       print('enemy hurts wall')
  387.     end
  388.   end
  389. end
  390.  
  391. function Enemy:collide_end( node )
  392.   if node and node.isPlayer and node.current_enemy == self then
  393.     node.current_enemy = nil
  394.   end
  395. end
  396.  
  397. function Enemy:update( dt, player, map )
  398.   local level = gamestate.currentState()
  399.   if level.scene or player.inventory.visible then return end
  400.  
  401.   if(self.position.x < self.minimum_x or self.position.x > self.maximum_x or
  402.      self.position.y < self.minimum_y or self.position.y > self.maximum_y) then
  403.       self:die()
  404.   end
  405.  
  406.   if self.dead then
  407.     return
  408.   end
  409.  
  410.   if not self.flashing then
  411.     self:cancel_flash()
  412.   end
  413.  
  414.   self:animation():update(dt)
  415.   if self.state == 'dying' then
  416.     if self.props.dyingupdate then
  417.       self.props.dyingupdate( dt, self )
  418.     end
  419.     return
  420.   end
  421.  
  422.   -- passive sound
  423.   if self.props.passive_sound then
  424.     if (math.random() <= (self.props.passive_sound_chance or .05) * dt) and (self.state == 'default') and self:onScreen() then
  425.       if type(self.props.passive_sound) == 'table' then
  426.         sound.playSfx( self.props.passive_sound[math.random(#self.props.passive_sound)] )
  427.       else
  428.         sound.playSfx( self.props.passive_sound )
  429.       end
  430.     end
  431.   end
  432.  
  433.   if self.props.update then
  434.     self.props.update( dt, self, player )
  435.   end
  436.  
  437.   if not self.props.antigravity and not self.dying then
  438.     -- Gravity
  439.     self.velocity.y = self.velocity.y + game.gravity * dt
  440.     if self.velocity.y > game.max_y then
  441.       self.velocity.y = game.max_y
  442.     end
  443.   end
  444.  
  445.   self:updatePosition(map, self.velocity.x * dt, self.velocity.y * dt)
  446.  
  447.   self:moveBoundingBox()
  448. end
  449.  
  450. function Enemy:updatePosition(map, dx, dy)
  451.   local offset_x = self.width/2 - self.bb_width / 2 + self.bb_offset.x
  452.   local offset_y = self.height/2 + self.bb_offset.y - self.bb_height/2
  453.  
  454.   local nx, ny = collision.move(map, self, self.position.x + offset_x,
  455.                                 self.position.y + offset_y,
  456.                                 self.bb_width, self.bb_height,
  457.                                 -dx, dy)
  458.  
  459.   self.position.x = nx - offset_x
  460.   self.position.y = ny - offset_y
  461. end
  462.  
  463. function Enemy:draw()
  464.   local r, g, b, a = love.graphics.getColor()
  465.  
  466.   if self.flash then
  467.     love.graphics.setColor(255, 0, 0, 255)
  468.   elseif self.fadeIn  then
  469.     local fade = {255, 255, 255, 0}
  470.     tween(1, fade, {255, 255, 255, 255}, 'outQuad', function() self.fadeIn = false end)
  471.     love.graphics.setColor(unpack(fade))  
  472.   else
  473.     love.graphics.setColor(255, 255, 255, 255)
  474.   end
  475.  
  476.   if not self.dead then
  477.     self:animation():draw( self.sprite, math.floor( self.position.x ), math.floor( self.position.y ) )
  478.   end
  479.  
  480.   love.graphics.setColor(r, g, b, a)
  481.   if self.props.draw then
  482.     self.props.draw(self)
  483.   end
  484.  
  485. end
  486.  
  487. function Enemy:ceiling_pushback()
  488.   if self.props.ceiling_pushback then
  489.     self.props.ceiling_pushback(self)
  490.   end
  491. end
  492.  
  493. function Enemy:floor_pushback()
  494.   self.velocity.y = 0
  495.   if self.props.floor_pushback then
  496.     self.props.floor_pushback(self)
  497.   else
  498.     self:moveBoundingBox()
  499.   end
  500. end
  501.  
  502. function Enemy:wall_pushback()
  503.   if self.props.wall_pushback then
  504.     self.props.wall_pushback(self)
  505.   else
  506.     if self.attackingWorld then return end
  507.     self.direction = self.direction == 'left' and 'right' or 'left'
  508.     --self.velocity.x = 0
  509.     self:moveBoundingBox()
  510.   end
  511. end
  512.  
  513. function Enemy:moveBoundingBox()
  514.   if not self.bb then
  515.     -- We should never get to this state, but we somehow do
  516.     return
  517.   end
  518.  
  519.   self.bb:moveTo( self.position.x + ( self.props.width / 2 ) + self.bb_offset.x,
  520.           self.position.y + ( self.props.height / 2 ) + self.bb_offset.y )
  521.  
  522.   if self.attack_bb then
  523.     local width = self.direction == 'right' and self.props.bb_width or -40
  524.     self.attack_bb:moveTo( self.position.x + ( self.props.width / 2 ) + self.attack_offset.x + width,
  525.                  self.position.y + ( self.props.height / 2 ) + self.attack_offset.y )
  526.   end
  527. end
  528.  
  529. ---
  530. -- Registers an object as something that the user can currently hold on to
  531. -- @param holdable
  532. -- @return nil
  533. function Enemy:registerHoldable(holdable)
  534.   if self.holdable == nil and self.currently_held == nil and holdable.holder == nil then
  535.     self.holdable = holdable
  536.   end
  537. end
  538.  
  539. ---
  540. -- Cancels the holdability of a node
  541. -- @param holdable
  542. -- @return nil
  543. function Enemy:cancelHoldable(holdable)
  544.   if self.holdable == holdable then
  545.     self.holdable = nil
  546.   end
  547. end
  548.  
  549. function Enemy:pickup()
  550.   if not self.holdable or self.currently_held then return end
  551.  
  552.   local obj
  553.   if self.holdable.pickup then
  554.     obj = self.holdable:pickup(self)
  555.   end
  556.   if obj then self.holdable = nil end
  557.   self.currently_held = obj
  558. end
  559.  
  560. -- Throws an object.
  561. -- @return nil
  562. function Enemy:throw()
  563.   if self.currently_held then
  564.     local object_thrown = self.currently_held
  565.     self.currently_held = nil
  566.     if object_thrown.throw then
  567.       object_thrown:throw(self)
  568.     end
  569.   end
  570. end
  571.  
  572. function Enemy:onScreen()
  573.   x_min, y_min = self.containerLevel:cameraPosition()
  574.   x_max = x_min + window.width
  575.   y_max = y_min + window.height
  576.   if self.position.x >= x_min and self.position.x <= x_max then
  577.     if self.position.y >= y_min and self.position.y <= y_max then
  578.       return true
  579.     end
  580.   end
  581.   return false
  582. end
  583.  
  584. return Enemy
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement