| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- Player = class()
- Player.width = 45
- Player.height = 90
- Player.depth = -3.5
- Player.walkSpeed = 75
- ----------------
- -- Core
- ----------------
- function Player:init()
- -- Physics
- self.x = ctx.map.width / 2
- self.y = ctx.map.height - ctx.map.groundHeight - self.height
- self.prevx = self.x
- self.prevy = self.y
- self.direction = 1
- self.speed = 0
- self.walkSpeed = Player.walkSpeed
- -- Health
- self.maxHealth = 100
- self.health = self.maxHealth
- self.healthDisplay = self.health
- self.prevHealthDisplay = self.healthDisplay
- self.prevHealth = self.health
- self.maxHealthIncreaseTime = 60
- -- Dead
- self.dead = false
- self.deathTimer = 0
- self.deathDuration = 7
- -- Juju
- self.juju = 0
- self.totalJuju = 0
- self.jujuRate = config.player.jujuRate / (ctx.mode == 'survival' and 2 or 1)
- self.jujuTimer = self.jujuRate
- self:addJuju(config.player.baseJuju)
- -- The current magic shruju
- self.shruju = nil
- -- Summoning and selection
- self.summonSelect = 1
- self.totalSummoned = 0
- -- Buffs
- self.invincible = 0
- self.ghostSpeedMultiplier = 1
- self.cooldownSpeed = 1
- self.buffs = PlayerBuffs(self)
- self.footstepIndex = 0
- -- joystick
- self.joystick = #love.joystick.getJoysticks() > 0 and love.joystick.getJoysticks()[1]
- end
- function Player:activate()
- -- Create animation
- self.animation = data.animation.muju()
- self.animation:on('complete', function(data)
- if data.state.name ~= 'death' and not data.state.loop then
- self.animation:set('idle', {force = true})
- end
- end)
- self.animation:on('event', function(event)
- if event.data.name == 'stepone' or event.data.name == 'steptwo' then
- if self.footstepIndex < 2 then
- self.footstepIndex = self.footstepIndex + 1
- else
- ctx.sound:play('footstep' .. love.math.random(1, 2), function(sound)
- sound:setPitch(.9 + love.math.random() * .3)
- sound:setVolume(.4)
- end)
- end
- end
- end)
- self.animation.spine.skeleton:setSkin(ctx.user.hat or 'nohat')
- self.animation.spine.skeleton:findSlot('hat').a = ctx.user.hat and 1 or 0
- self.animation.spine.skeleton:findBone('hat').scaleX = self.animation.scale
- self.animation.spine.skeleton:findBone('hat').scaleY = self.animation.scale
- -- Color animation
- for _, slot in pairs({'robebottom', 'torso', 'front_upper_arm', 'rear_upper_arm', 'front_bracer', 'rear_bracer'}) do
- local slot = self.animation.spine.skeleton:findSlot(slot)
- slot.r, slot.g, slot.b = unpack(config.player.colors[ctx.user.color])
- end
- -- Initialize deck data structure from ctx.user
- self:initDeck()
- ctx.event:emit('view.register', {object = self})
- end
- function Player:update()
- -- Lerp vars
- self.prevx = self.x
- self.prevy = self.y
- self.prevHealthDisplay = self.healthDisplay
- self.prevHealth = self.health
- -- Core updates
- self:move()
- self:animate()
- self.buffs:update()
- if self.ghost then self.ghost:update() end
- -- Rots
- if ctx.tutorial:shouldDecayGhost() then self.deathTimer = timer.rot(self.deathTimer, function() self:spawn() end) end
- self.invincible = timer.rot(self.invincible)
- for i = 1, #self.deck do
- if self.deck[i].cooldown > 0 then
- self.deck[i].cooldown = self.deck[i].cooldown - ls.tickrate * self.cooldownSpeed
- if self.deck[i].cooldown <= 0 then
- ctx.hud.units.cooldownPop[i] = 1
- self.deck[i].cooldown = 0
- end
- end
- end
- -- Max Health Increase
- if ctx.timer * ls.tickrate > self.maxHealthIncreaseTime then
- local ratio = self.health / self.maxHealth
- self.maxHealth = self.maxHealth + config.player.maxHealthPerMinute
- self.health = self.maxHealth * ratio
- self.prevHealth = self.health
- self.maxHealthIncreaseTime = self.maxHealthIncreaseTime + 60
- end
- -- Lerp healthbar
- self.healthDisplay = math.lerp(self.healthDisplay, self.health, math.min(10 * ls.tickrate, 1))
- -- Juju trickle
- self.jujuTimer = timer.rot(self.jujuTimer, function()
- self:addJuju(1)
- return self.jujuRate
- end)
- end
- function Player:draw()
- -- Flash when invincible
- if math.floor(self.invincible * 4) % 2 == 0 then
- local x, y = math.lerp(self.prevx, self.x, ls.accum / ls.tickrate), math.lerp(self.prevy, self.y, ls.accum / ls.tickrate)
- love.graphics.setColor(255, 255, 255)
- self.animation:draw(x, y)
- end
- end
- function Player:keypressed(key)
- -- Select minions with digits
- for i = 1, #self.deck do
- if tonumber(key) == i then
- self.summonSelect = i
- return
- end
- end
- -- Summon with space
- if key == ' ' and not self.dead and ctx.tutorial:shouldSummon() then
- self:summon()
- end
- if key == 'q' then
- local picked = false
- ctx.shrujus:each(function(shruju)
- if shruju:playerNearby() then
- if self.shruju then self.shruju:drop() end
- shruju:pickup()
- picked = true
- return true
- end
- end)
- if not picked and self.shruju then
- self.shruju:drop()
- self.shruju = nil
- end
- end
- end
- function Player:gamepadpressed(gamepad, button)
- end
- function Player:gamepadaxis(joystick, axis, value)
- if axis == 'triggerright' and value > .75 and not self.dead then
- self:summon()
- end
- end
- function Player:paused()
- -- Reset prev variables when paused to fix lerp jitter.
- self.prevx = self.x
- self.prevy = self.y
- self.animation:set('idle')
- if self.ghost then
- self.ghost.prevx = self.ghost.x
- self.ghost.prevy = self.ghost.y
- end
- end
- ----------------
- -- Behavior
- ----------------
- function Player:move()
- -- If we can't move then don't move
- local animation = self.animation.state.name
- if not ctx.tutorial:shouldPlayerMove() or self.dead or animation == 'summon' or animation == 'death' or animation == 'resurrect' then
- self.speed = 0
- return
- end
- -- Adjust speed to target speed based on keystate
- local maxSpeed = self.walkSpeed
- if love.keyboard.isDown('left', 'a') or (self.joystick and self.joystick:getGamepadAxis('leftx') < -.5) then
- self.speed = math.lerp(self.speed, -maxSpeed, math.min(10 * ls.tickrate, 1))
- elseif love.keyboard.isDown('right', 'd') or (self.joystick and self.joystick:getGamepadAxis('leftx') > .5) then
- self.speed = math.lerp(self.speed, maxSpeed, math.min(10 * ls.tickrate, 1))
- else
- self.speed = math.lerp(self.speed, 0, math.min(10 * ls.tickrate, 1))
- self.footstepIndex = 0
- end
- -- Actually move
- self.x = self.x + self.speed * ls.tickrate
- self.direction = self.speed == 0 and self.direction or math.sign(self.speed)
- -- Don't go outside map
- self.x = math.clamp(self.x, 0, ctx.map.width)
- end
- function Player:summon(options)
- options = options or {}
- local minion = self.deck[self.summonSelect].code
- local cooldown = self.deck[self.summonSelect].cooldown
- local unitCount = #ctx.units:filter(function(u) return u.player end)
- local cost = data.unit[minion].cost * (unitCount == 0 and 0 or 1)
- local animation = self.animation.state.name
- -- Check if we can summon
- if not options.force and not (not ctx.hud.upgrades.active and not ctx.paused and cooldown == 0 and animation ~= 'dead' and animation ~= 'resurrect' and self:spend(cost)) then
- return ctx.sound:play('misclick', function(sound) sound:setVolume(.3) end)
- end
- -- Ow
- self:hurt(self.maxHealth * .1)
- -- Create minion
- local unit = ctx.units:add(minion, {player = self, x = self.x + love.math.random(-20, 20)})
- -- Set cooldowns (global cooldown)
- local cooldown = config.player.baseCooldown
- if self:hasShruju('refresh') and love.math.random() < .25 then cooldown = 0 end
- for i = 1, #self.deck do
- if cooldown > self.deck[i].cooldown then
- self.deck[i].cooldown = cooldown
- self.deck[i].maxCooldown = cooldown
- end
- end
- -- Achievement: Mini Arsenal
- ctx.event:emit('achievement', {name = 'miniarsenal'})
- -- Aftermath, juice, animations, etc.
- self.totalSummoned = self.totalSummoned + 1
- self.invincible = 0
- self.animation:set('summon')
- if not options.nosound then
- local summonSound = love.math.random(1, 3)
- ctx.sound:play('summon' .. summonSound)
- end
- ctx.hud.units.animations[self.summonSelect]:set('spawn')
- for i = 1, 20 do
- ctx.particles:emit('jujudrop', self.x + love.math.randomNormal(20), self.y + love.math.randomNormal(20) + self.height / 2, 1)
- end
- end
- function Player:animate()
- if self.dead then return end
- -- Flip animation, set animation speed
- self.animation:set(math.abs(self.speed) > self.walkSpeed / 2 and 'walk' or 'idle')
- self.animation.speed = self.animation.state.name == 'walk' and math.max(math.abs(self.speed / Player.walkSpeed), .4) or 1
- if self.speed ~= 0 then self.animation.flipped = math.sign(self.speed) > 0 end
- end
- function Player:spend(amount)
- if self.juju >= amount then
- self.juju = self.juju - amount
- return true
- end
- return false
- end
- function Player:addJuju(amount)
- if ctx.tutorial and ctx.tutorial.active then return end
- self.juju = self.juju + amount
- self.totalJuju = self.totalJuju + amount
- end
- function Player:hurt(amount, source, kind)
- if not self.dead and self.invincible == 0 then
- amount = self.buffs:prehurt(amount, source, kind) or amount
- self.health = math.max(self.health - amount, 0)
- self.buffs:posthurt(amount, source, kind)
- ctx.event:emit('player.hurt', {amount = amount, source = source, kind = kind})
- if amount > 5 then
- local sound = data.media.sounds['hit' .. love.math.random(1, 3)]
- ctx.sound:play(sound, function(sound) sound:setVolume(0) end)
- end
- -- Die if we are dead
- if self.health <= 0 and self.deathTimer == 0 then self:die() end
- end
- return amount
- end
- function Player:die()
- self.deathTimer = self.deathDuration
- self.dead = true
- self.ghost = GhostPlayer(self)
- self.buffs:die()
- self.animation:set('death')
- ctx.sound:play('death', function(sound) sound:setVolume(.3) end)
- if self:hasShruju('reincarnation') then
- self:summon({force = true, nosound = true})
- end
- end
- function Player:spawn()
- self.deathTimer = 0
- self.invincible = 4.5
- self.health = self.maxHealth
- self.dead = false
- self.ghost:despawn()
- self.ghost = nil
- self.animation:set('resurrect')
- end
- function Player:heal(amount, source)
- if self.dead then return end
- self.health = math.min(self.health + amount, self.maxHealth)
- end
- ----------------
- -- Helper
- ----------------
- function Player:getHealthbar()
- local x = math.lerp(self.prevx, self.x, ls.accum / ls.tickrate)
- local y = math.lerp(self.prevy, self.y, ls.accum / ls.tickrate)
- local healthDisplay = math.lerp(self.prevHealthDisplay, self.healthDisplay, ls.accum / ls.tickrate)
- local health = math.lerp(self.prevHealth, self.health, ls.accum / ls.tickrate)
- return x, y, health / self.maxHealth, healthDisplay / self.maxHealth
- end
- function Player:atShrine()
- local shrine = table.values(ctx.shrines:filter(function(shrine) return shrine.team == self.team end))[1]
- if not shrine then return false end
- return math.abs(self.x - shrine.x) < self.width
- end
- function Player:initDeck()
- self.deck = {}
- local minions = {}
- if ctx.mode == 'campaign' then
- minions = {config.biomes[ctx.biome].minion}
- elseif ctx.mode == 'survival' then
- minions = ctx.user.survival.minions
- end
- for i = 1, 2 do
- local code = minions[i]
- if code then
- self.deck[code] = {
- runes = ctx.user.runes[code],
- cooldown = 0,
- maxCooldown = 2,
- code = code
- }
- self.deck[i] = self.deck[code]
- -- Attribute and ability runes
- table.each(self.deck[i].runes, function(rune)
- if rune.attributes then
- table.each(rune.attributes, function(amount, attribute)
- local class = data.unit[code]
- class.attributes[attribute] = class.attributes[attribute] + amount
- end)
- elseif rune.abilities then
- table.each(rune.abilities, function(stats, ability)
- table.each(stats, function(amount, stat)
- local target = data.ability[code][ability]
- local proxy = config.runes.abilityProxies[code] and config.runes.abilityProxies[code][ability]
- if proxy then
- if type(proxy) == 'string' then
- if proxy == 'buff' then
- target = data.buff[ability]
- elseif proxy == 'ability' then
- target = data.ability[code][ability]
- end
- elseif type(proxy) == 'table' then
- local kind, key = unpack(proxy)
- if kind == 'ability' then
- target = data.ability[code][key]
- elseif kind == 'buff' then
- target = data.buff[key]
- end
- end
- end
- local key = 'rune' .. stat:capitalize()
- target[key] = target[key] + amount
- end)
- end)
- end
- end)
- end
- end
- end
- function Player:contains(x, y)
- math.inside(x, y, self.x - self.width / 2, self.y, self.width, self.height)
- end
- function Player:hasShruju(code)
- return self.shruju and self.shruju.code == code
- end
|