commit 47647a5c8a36b41fc2f7b191b3fd525a53b8baac Author: Neckrat Date: Thu Jul 31 01:54:03 2025 +0300 initial commit Co-authored-by: Ivan Yuriev diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..825018e --- /dev/null +++ b/.luarc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "runtime.version": "LuaJIT", + "workspace.library": ["../love2d-luacats"], + "runtime.special": { + "love.filesystem.load": "loadfile" + } +} diff --git a/character.lua b/character.lua new file mode 100644 index 0000000..b301033 --- /dev/null +++ b/character.lua @@ -0,0 +1,62 @@ +local anim8 = require "anim8" + +local CHARACTER_SIZE = 64 + +local Character = {} +Character.name = "" + +Character.animationTable = { + idle = {}, + run = {}, + attack = {} + -- etc etc +} +Character.animation = Character.animationTable.idle + +Character.stats = {} +Character.stats.initiative = 0 + +Character.player = {} + +--- Обёртка над Character:Create +--- @param name string +--- @param imagePath string +--- @param initiative? integer +CreateCharacter = Character.create + +-- ты клоун же +-- какого черта у тебя конструктор объекта принимает ссылку на объект +-- +-- хз +-- ща я в душ +-- :clown: +--- Создаёт персонажа, которым будет управлять или игрок или компьютер +--- @param name string +--- @param imagePath string +--- @param initiative? integer +function Character:create(name, imagePath, initiative) + local image = love.graphics.newImage(imagePath) + local animationGrid = anim8.newGrid(CHARACTER_SIZE, CHARACTER_SIZE, image:getWidth(), image:getHeight()) + return Character { + name = name, + --- мы должны определиться со спрайтшитами и подобным, всё что здесь написано лишь пример + --- + --- предполагается, что у всех будет одинаковое кол-во кадров в анимации и их скорость произведения + animationTable = { + idle = anim8.newAnimation(animationGrid('1-8', 1), 0.2) + }, + --- чтобы не обновлять все анимации одновременно, храним нужную анимацию здесь + --- + --- когда нужно сменить анимацию с idle на run например, меняем именно это поле + animation = self.animationTable.idle, + stats = { + initiative = initiative or 10 + } + } +end + +function Character:update(dt) + self.animation:update(dt) +end + +local f = Character.create \ No newline at end of file diff --git a/grid.lua b/grid.lua new file mode 100644 index 0000000..39dc63a --- /dev/null +++ b/grid.lua @@ -0,0 +1,14 @@ +local utils = require("utils") + +---Generates an empty grid +---@param width number +---@param height number +local function generateGrid(width, height) + local grid = utils.generateList(width, function(i) + return utils.generateList(height, function(i) + return {} + end) + end) + + return grid +end diff --git a/lib/anim8.lua b/lib/anim8.lua new file mode 100644 index 0000000..3532546 --- /dev/null +++ b/lib/anim8.lua @@ -0,0 +1,306 @@ +local anim8 = { + _VERSION = 'anim8 v2.3.1', + _DESCRIPTION = 'An animation library for LÖVE', + _URL = 'https://github.com/kikito/anim8', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2011 Enrique García Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] +} + +local Grid = {} + +local _frames = {} + +local function assertPositiveInteger(value, name) + if type(value) ~= 'number' then error(("%s should be a number, was %q"):format(name, tostring(value))) end + if value < 1 then error(("%s should be a positive number, was %d"):format(name, value)) end + if value ~= math.floor(value) then error(("%s should be an integer, was %f"):format(name, value)) end +end + +local function createFrame(self, x, y) + local fw, fh = self.frameWidth, self.frameHeight + return love.graphics.newQuad( + self.left + (x - 1) * fw + x * self.border, + self.top + (y - 1) * fh + y * self.border, + fw, + fh, + self.imageWidth, + self.imageHeight + ) +end + +local function getGridKey(...) + return table.concat({ ... }, '-') +end + +local function getOrCreateFrame(self, x, y) + if x < 1 or x > self.width or y < 1 or y > self.height then + error(("There is no frame for x=%d, y=%d"):format(x, y)) + end + local key = self._key + _frames[key] = _frames[key] or {} + _frames[key][x] = _frames[key][x] or {} + _frames[key][x][y] = _frames[key][x][y] or createFrame(self, x, y) + return _frames[key][x][y] +end + +local function parseInterval(str) + if type(str) == "number" then return str, str, 1 end + str = str:gsub('%s', '') -- remove spaces + local min, max = str:match("^(%d+)-(%d+)$") + assert(min and max, ("Could not parse interval from %q"):format(str)) + min, max = tonumber(min), tonumber(max) + local step = min <= max and 1 or -1 + return min, max, step +end + +function Grid:getFrames(...) + local result, args = {}, { ... } + local minx, maxx, stepx, miny, maxy, stepy + + for i = 1, #args, 2 do + minx, maxx, stepx = parseInterval(args[i]) + miny, maxy, stepy = parseInterval(args[i + 1]) + for y = miny, maxy, stepy do + for x = minx, maxx, stepx do + result[#result + 1] = getOrCreateFrame(self, x, y) + end + end + end + + return result +end + +local Gridmt = { + __index = Grid, + __call = Grid.getFrames +} + +local function newGrid(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border) + assertPositiveInteger(frameWidth, "frameWidth") + assertPositiveInteger(frameHeight, "frameHeight") + assertPositiveInteger(imageWidth, "imageWidth") + assertPositiveInteger(imageHeight, "imageHeight") + + left = left or 0 + top = top or 0 + border = border or 0 + + local key = getGridKey(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border) + + local grid = setmetatable( + { + frameWidth = frameWidth, + frameHeight = frameHeight, + imageWidth = imageWidth, + imageHeight = imageHeight, + left = left, + top = top, + border = border, + width = math.floor(imageWidth / frameWidth), + height = math.floor(imageHeight / frameHeight), + _key = key + }, + Gridmt + ) + return grid +end + +----------------------------------------------------------- + +local Animation = {} + +local function cloneArray(arr) + local result = {} + for i = 1, #arr do result[i] = arr[i] end + return result +end + +local function parseDurations(durations, frameCount) + local result = {} + if type(durations) == 'number' then + for i = 1, frameCount do result[i] = durations end + else + local min, max, step + for key, duration in pairs(durations) do + assert(type(duration) == 'number', "The value [" .. tostring(duration) .. "] should be a number") + min, max, step = parseInterval(key) + for i = min, max, step do result[i] = duration end + end + end + + if #result < frameCount then + error("The durations table has length of " .. + tostring(#result) .. ", but it should be >= " .. tostring(frameCount)) + end + + return result +end + +local function parseIntervals(durations) + local result, time = { 0 }, 0 + for i = 1, #durations do + time = time + durations[i] + result[i + 1] = time + end + return result, time +end + +local Animationmt = { __index = Animation } +local nop = function() end + +local function newAnimation(frames, durations, onLoop) + local td = type(durations); + if (td ~= 'number' or durations <= 0) and td ~= 'table' then + error("durations must be a positive number. Was " .. tostring(durations)) + end + onLoop = onLoop or nop + durations = parseDurations(durations, #frames) + local intervals, totalDuration = parseIntervals(durations) + return setmetatable({ + frames = cloneArray(frames), + durations = durations, + intervals = intervals, + totalDuration = totalDuration, + onLoop = onLoop, + timer = 0, + position = 1, + status = "playing", + flippedH = false, + flippedV = false + }, + Animationmt + ) +end + +function Animation:clone() + local newAnim = newAnimation(self.frames, self.durations, self.onLoop) + newAnim.flippedH, newAnim.flippedV = self.flippedH, self.flippedV + return newAnim +end + +function Animation:flipH() + self.flippedH = not self.flippedH + return self +end + +function Animation:flipV() + self.flippedV = not self.flippedV + return self +end + +local function seekFrameIndex(intervals, timer) + local high, low, i = #intervals - 1, 1, 1 + + while (low <= high) do + i = math.floor((low + high) / 2) + if timer >= intervals[i + 1] then + low = i + 1 + elseif timer < intervals[i] then + high = i - 1 + else + return i + end + end + + return i +end + +function Animation:update(dt) + if self.status ~= "playing" then return end + + self.timer = self.timer + dt + local loops = math.floor(self.timer / self.totalDuration) + if loops ~= 0 then + self.timer = self.timer - self.totalDuration * loops + local f = type(self.onLoop) == 'function' and self.onLoop or self[self.onLoop] + f(self, loops) + end + + self.position = seekFrameIndex(self.intervals, self.timer) +end + +function Animation:pause() + self.status = "paused" +end + +function Animation:gotoFrame(position) + self.position = position + self.timer = self.intervals[self.position] +end + +function Animation:pauseAtEnd() + self.position = #self.frames + self.timer = self.totalDuration + self:pause() +end + +function Animation:pauseAtStart() + self.position = 1 + self.timer = 0 + self:pause() +end + +function Animation:resume() + self.status = "playing" +end + +function Animation:draw(image, x, y, r, sx, sy, ox, oy, kx, ky) + love.graphics.draw(image, self:getFrameInfo(x, y, r, sx, sy, ox, oy, kx, ky)) +end + +function Animation:getFrameInfo(x, y, r, sx, sy, ox, oy, kx, ky) + local frame = self.frames[self.position] + if self.flippedH or self.flippedV then + r, sx, sy, ox, oy, kx, ky = r or 0, sx or 1, sy or 1, ox or 0, oy or 0, kx or 0, ky or 0 + local _, _, w, h = frame:getViewport() + + if self.flippedH then + sx = sx * -1 + ox = w - ox + kx = kx * -1 + ky = ky * -1 + end + + if self.flippedV then + sy = sy * -1 + oy = h - oy + kx = kx * -1 + ky = ky * -1 + end + end + return frame, x, y, r, sx, sy, ox, oy, kx, ky +end + +function Animation:getDimensions() + local _, _, w, h = self.frames[self.position]:getViewport() + return w, h +end + +----------------------------------------------------------- + +anim8.newGrid = newGrid +anim8.newAnimation = newAnimation + +return anim8 diff --git a/lib/utils.lua b/lib/utils.lua new file mode 100644 index 0000000..7c64efb --- /dev/null +++ b/lib/utils.lua @@ -0,0 +1,19 @@ +local P = {} + +---List generator +---@generic T +---@param count integer +---@param generator fun(i: integer): T +---@return T[] +function P.generateList(count, generator) + if count <= 0 then return {} end + + local xs = {} + for i = 0, count - 1, 1 do + xs[i] = generator(i) + end + + return xs +end + +return P diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..c5d4767 --- /dev/null +++ b/main.lua @@ -0,0 +1,30 @@ +require "character" + +function love.load() + -- PlayerFaction = Faction + + -- Hero1 = Character:create("Petya", 10) + -- Hero2 = Character:create("Andrysha", 12) + + -- PlayerFaction.characters = { Hero1, Hero2 } +end + +function love.update(dt) + +end + +function love.draw() + +end + +local Level = {} +Level.kakaya_ta_hren = 10 +Level.map = {} + +Faction = {} +Faction.name = "" +Faction.characters = {} +Faction.style = {} + +-- Faction -> Character +-- calculate_order()