added tileGrid neighbors information to debug screen

export ^

require "tile"
require "rooms/rock"
require "rooms/corridor"
require "rooms/reactor"
require "rooms/extractor"
shapes = require "rooms/shape"

class Spacebase
    kBASE_SIZE: 20  -- in tile²
    totalSize: ->
        return Spacebase.kBASE_SIZE * Tile.kTILE_SIZE

    new: =>
        @tileGrid = {}
        for i=1, @kBASE_SIZE
            table.insert(@tileGrid, {})
            for j=1, @kBASE_SIZE
                table.insert(@tileGrid[i], nil)
        @rooms = {}
        @mousePosition = {0, 0} -- updated by external class
        @floatingRoom = nil

        -- build the first rooms
        startupRooms = {}
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 + 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2, @kBASE_SIZE/2 - 1}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Corridor(shapes.t, {@kBASE_SIZE / 2, @kBASE_SIZE/2}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Reactor(shapes.t, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2 + 3}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Extractor(shapes.o, {@kBASE_SIZE / 2 + 2, @kBASE_SIZE/2 + 2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        for room in *startupRooms
            @addRoom(room)

        @crew = {}

    update: (dt) =>
        if @floatingRoom
            mx, my = @mousePosition[1], @mousePosition[2]
            {wx, wy} = @worldToTile(mx, my)
            @floatingRoom\updatePosition(wx, wy)
        for room in *@rooms
            room\update(dt)

    draw: =>
        tileSize = Tile.kTILE_SIZE
        totalSize = Spacebase.totalSize()
        for i=1, @kBASE_SIZE + 1
            love.graphics.setColor(255, 255, 255, 150)
            love.graphics.setLineWidth(1)
            i -= 1 -- begins at 0
            love.graphics.line(0, i * tileSize, totalSize, i * tileSize)
            love.graphics.line(i * tileSize, 0, i * tileSize, totalSize)
        for room in *@rooms
            @drawRoom(room)
        if @floatingRoom
            @drawRoom(@floatingRoom)
        -- debug: see neighbors
        if kDEBUG
            love.graphics.setLineWidth(2)
            love.graphics.setColor({50,50,50})
            neighbors = {Tile.kRIGHT, Tile.kLEFT, Tile.kUP, Tile.kDOWN}
            for i=1,@kBASE_SIZE
                for j=1,@kBASE_SIZE
                    for n in *neighbors
                        if @tileGrid[i][j] and @tileGrid[i][j].neighbors[n]
                            neigh = @tileGrid[i][j].neighbors[n]
                            {x1, y1} = @tileToWorld(i, j)
                            {x2, y2} = @tileToWorld(neigh.row, neigh.col)
                            love.graphics.line(x1, y1, x2, y2)

    drawRoom: (room) =>
        i = room.row - 1
        j = room.col - 1
        love.graphics.push()
        love.graphics.translate(i * Tile.kTILE_SIZE, j * Tile.kTILE_SIZE)
        room\draw()
        love.graphics.pop()

    placeFloatingRoom: =>
        -- TODO: assert that floating room is valid
        assert(@floatingRoom)
        @floatingRoom\confirm()
        @addRoom(@floatingRoom)
        @floatingRoom = nil

    addRoom: (room) =>
        table.insert(@rooms, room)
        for tile in *room.tiles
            @tileGrid[tile.row][tile.col] = tile
        @updateNeighbors()

    worldToTile: (x, y) =>
        i = math.ceil(x / Tile.kTILE_SIZE)
        j = math.ceil(y / Tile.kTILE_SIZE)
        return {i, j}

    tileToWorld: (i, j) =>
        x = (i - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        y = (j - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        return {x, y}

    getItems: (fromCrew=true, fromRooms=true) =>
        items = {}
        if fromCrew
            for minion in *@crew
                item = minion\getItem()
                if item
                    table.insert(items, item)
        if fromRooms
            for room in *@rooms
                for item in *room\getItems()
                    table.insert(items, item)
        return items

    updateNeighbors: =>
        for i=1,@kBASE_SIZE
            for j=1,@kBASE_SIZE
                tile = @tileGrid[i][j]
                if tile
                    if i < @kBASE_SIZE
                        tile.neighbors[Tile.kRIGHT] = @tileGrid[i + 1][j]
                    if i > 1
                        tile.neighbors[Tile.kLEFT] = @tileGrid[i - 1][j]
                    if j > 1
                        tile.neighbors[Tile.kUP] = @tileGrid[i][j - 1]
                    if j < @kBASE_SIZE
                        tile.neighbors[Tile.kDOWN] = @tileGrid[i][j + 1]

export ^

require "tile"
require "rooms/rock"
require "rooms/corridor"
require "rooms/reactor"
require "rooms/extractor"
shapes = require "rooms/shape"

class Spacebase
    kBASE_SIZE: 20  -- in tile²
    totalSize: ->
        return Spacebase.kBASE_SIZE * Tile.kTILE_SIZE

    new: =>
        @tileGrid = {}
        for i=1, @kBASE_SIZE
            table.insert(@tileGrid, {})
            for j=1, @kBASE_SIZE
                table.insert(@tileGrid[i], nil)
        @rooms = {}
        @mousePosition = {0, 0} -- updated by external class
        @floatingRoom = nil

        -- build the first rooms
        startupRooms = {}
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 + 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2, @kBASE_SIZE/2 - 1}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Corridor(shapes.t, {@kBASE_SIZE / 2, @kBASE_SIZE/2}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Reactor(shapes.t, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2 + 3}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Extractor(shapes.o, {@kBASE_SIZE / 2 + 2, @kBASE_SIZE/2 + 2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        for room in *startupRooms
            @addRoom(room)

        @crew = {}

    update: (dt) =>
        if @floatingRoom
            mx, my = @mousePosition[1], @mousePosition[2]
            {wx, wy} = @worldToTile(mx, my)
            @floatingRoom\updatePosition(wx, wy)
        for room in *@rooms
            room\update(dt)

    draw: =>
        tileSize = Tile.kTILE_SIZE
        totalSize = Spacebase.totalSize()
        for i=1, @kBASE_SIZE + 1
            love.graphics.setColor(255, 255, 255, 150)
            love.graphics.setLineWidth(1)
            i -= 1 -- begins at 0
            love.graphics.line(0, i * tileSize, totalSize, i * tileSize)
            love.graphics.line(i * tileSize, 0, i * tileSize, totalSize)
        for room in *@rooms
            @drawRoom(room)
        if @floatingRoom
            @drawRoom(@floatingRoom)
        -- debug: see neighbors
        if false
            love.graphics.setLineWidth(2)
            love.graphics.setColor({50,50,50})
            neighbors = {Tile.kRIGHT, Tile.kLEFT, Tile.kUP, Tile.kDOWN}
            for i=1,@kBASE_SIZE
                for j=1,@kBASE_SIZE
                    for n in *neighbors
                        if @tileGrid[i][j] and @tileGrid[i][j].neighbors[n]
                            neigh = @tileGrid[i][j].neighbors[n]
                            {x1, y1} = @tileCenterCoordinates(i, j)
                            {x2, y2} = @tileCenterCoordinates(neigh.x, neigh.y)
                            love.graphics.line(x1, y1, x2, y2)

    drawRoom: (room) =>
        i = room.row - 1
        j = room.col - 1
        love.graphics.push()
        love.graphics.translate(i * Tile.kTILE_SIZE, j * Tile.kTILE_SIZE)
        room\draw()
        love.graphics.pop()

    placeFloatingRoom: =>
        -- TODO: assert that floating room is valid
        assert(@floatingRoom)
        @floatingRoom\confirm()
        @addRoom(@floatingRoom)
        @floatingRoom = nil

    addRoom: (room) =>
        table.insert(@rooms, room)
        for tile in *room.tiles
            @tileGrid[tile.row][tile.col] = tile
        @updateNeighbors()

    worldToTile: (x, y) =>
        i = math.ceil(x / Tile.kTILE_SIZE)
        j = math.ceil(y / Tile.kTILE_SIZE)
        return {i, j}

    tileToWorld: (i, j) =>
        x = (i - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        y = (j - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        return {x, y}

    getItems: (fromCrew=true, fromRooms=true) =>
        items = {}
        if fromCrew
            for minion in *@crew
                item = minion\getItem()
                if item
                    table.insert(items, item)
        if fromRooms
            for room in *@rooms
                for item in *room\getItems()
                    table.insert(items, item)
        return items

    updateNeighbors: =>
        for i=1,@kBASE_SIZE
            for j=1,@kBASE_SIZE
                tile = @tileGrid[i][j]
                if tile
                    if i < @kBASE_SIZE
                        tile.neighbors[Tile.kRIGHT] = @tileGrid[i + 1][j]
                    if i > 1
                        tile.neighbors[Tile.kLEFT] = @tileGrid[i - 1][j]
                    if j > 1
                        tile.neighbors[Tile.kUP] = @tileGrid[i][j - 1]
                    if j < @kBASE_SIZE
                        tile.neighbors[Tile.kDOWN] = @tileGrid[i][j + 1]


moved comment

moved comment

export ^

require "tile"
require "rooms/rock"
require "rooms/corridor"
require "rooms/reactor"
require "rooms/extractor"
shapes = require "rooms/shape"

class Spacebase
    kBASE_SIZE: 20  -- in tile²
    totalSize: ->
        return Spacebase.kBASE_SIZE * Tile.kTILE_SIZE

    new: =>
        @tileGrid = {}
        for i=1, @kBASE_SIZE
            table.insert(@tileGrid, {})
            for j=1, @kBASE_SIZE
                table.insert(@tileGrid[i], nil)
        @rooms = {}
        @mousePosition = {0, 0} -- updated by external class
        @floatingRoom = nil

        -- build the first rooms
        startupRooms = {}
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 + 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2, @kBASE_SIZE/2 - 1}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Corridor(shapes.t, {@kBASE_SIZE / 2, @kBASE_SIZE/2}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Reactor(shapes.t, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2 + 3}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Extractor(shapes.o, {@kBASE_SIZE / 2 + 2, @kBASE_SIZE/2 + 2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        for room in *startupRooms
            @addRoom(room)

        @crew = {}

    update: (dt) =>
        if @floatingRoom
            mx, my = @mousePosition[1], @mousePosition[2]
            {wx, wy} = @worldToTile(mx, my)
            @floatingRoom\updatePosition(wx, wy)
        for room in *@rooms
            room\update(dt)

    draw: =>
        tileSize = Tile.kTILE_SIZE
        totalSize = Spacebase.totalSize()
        for i=1, @kBASE_SIZE + 1
            love.graphics.setColor(255, 255, 255, 150)
            love.graphics.setLineWidth(1)
            i -= 1 -- begins at 0
            love.graphics.line(0, i * tileSize, totalSize, i * tileSize)
            love.graphics.line(i * tileSize, 0, i * tileSize, totalSize)
        for room in *@rooms
            @drawRoom(room)
        if @floatingRoom
            @drawRoom(@floatingRoom)
        if kDEBUG
            -- display neighbours
            love.graphics.setLineWidth(2)
            love.graphics.setColor({50,50,50})
            neighbors = {Tile.kRIGHT, Tile.kLEFT, Tile.kUP, Tile.kDOWN}
            for i=1,@kBASE_SIZE
                for j=1,@kBASE_SIZE
                    for n in *neighbors
                        if @tileGrid[i][j] and @tileGrid[i][j].neighbors[n]
                            neigh = @tileGrid[i][j].neighbors[n]
                            {x1, y1} = @tileToWorld(i, j)
                            {x2, y2} = @tileToWorld(neigh.row, neigh.col)
                            love.graphics.line(x1, y1, x2, y2)

    drawRoom: (room) =>
        i = room.row - 1
        j = room.col - 1
        love.graphics.push()
        love.graphics.translate(i * Tile.kTILE_SIZE, j * Tile.kTILE_SIZE)
        room\draw()
        love.graphics.pop()

    drawPath: (path) =>
        size = #path
        for i = 1, size - 1
            {row, col} = path[i]
            {row2, col2} = path[i + 1]
            {x, y} = @tileToWorld(row, col)
            {x2, y2} = @tileToWorld(row2, col2)
            love.graphics.setColor(0, 0, 0)
            love.graphics.line(x, y, x2, y2)

    placeFloatingRoom: =>
        -- TODO: assert that floating room is valid
        assert(@floatingRoom)
        @floatingRoom\confirm()
        @addRoom(@floatingRoom)
        @floatingRoom = nil

    addRoom: (room) =>
        table.insert(@rooms, room)
        for tile in *room.tiles
            @tileGrid[tile.row][tile.col] = tile
        @updateNeighbors()

    worldToTile: (x, y) =>
        i = math.ceil(x / Tile.kTILE_SIZE)
        j = math.ceil(y / Tile.kTILE_SIZE)
        return {i, j}

    tileToWorld: (i, j) =>
        x = (i - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        y = (j - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        return {x, y}

    getItems: (fromCrew=true, fromRooms=true) =>
        items = {}
        if fromCrew
            for minion in *@crew
                item = minion\getItem()
                if item
                    table.insert(items, item)
        if fromRooms
            for room in *@rooms
                for item in *room\getItems()
                    table.insert(items, item)
        return items

    updateNeighbors: =>
        for i=1,@kBASE_SIZE
            for j=1,@kBASE_SIZE
                tile = @tileGrid[i][j]
                if tile
                    if i < @kBASE_SIZE
                        tile.neighbors[Tile.kRIGHT] = @tileGrid[i + 1][j]
                    if i > 1
                        tile.neighbors[Tile.kLEFT] = @tileGrid[i - 1][j]
                    if j > 1
                        tile.neighbors[Tile.kUP] = @tileGrid[i][j - 1]
                    if j < @kBASE_SIZE
                        tile.neighbors[Tile.kDOWN] = @tileGrid[i][j + 1]

    pathFinding: (row1, col1, row2, col2) =>
        -- return a path (as a table of {row, col} coordinate) between two tiles
        -- Breadth-First
        assert @tileGrid[row1][col1] ~= nil and @tileGrid[row2][col2] ~= nil
        row, col = row1, col1
        tile_queue = {}
        marked = {} -- contains the previous tile of each tile reached
        in_marked = (r, c) -> return marked[r] ~= nil and marked[r][c] ~= nil
        mark_tile = (r, c, prevCoor) ->
            if marked[r] == nil then marked[r] = {}
            marked[r][c] = prevCoor
        -- breadth-first exhaustive search using a queue
        while row ~= row2 or col ~= col2
            assert @tileGrid[row][col] ~= nil
            tile = @tileGrid[row][col]
            for neigh in *tile.neighbors
                if neigh and not in_marked(neigh.row, neigh.col)
                    mark_tile(neigh.row, neigh.col, {tile.row, tile.col})
                    table.insert(tile_queue, neigh)
            if #tile_queue == 0
                return nil -- no path found
            nextTile = table.remove(tile_queue, 1)
            row, col = nextTile.row, nextTile.col
        assert row == row2 and col == col2, "#{row}, #{col}, #{row2}, #{col2}"
        -- retrace path back using marks
        path = {}
        table.insert(path, {row, col})
        loopCounter = 0
        while row ~= row1 and col ~= col1
            loopCounter += 1
            assert loopCounter <= @kBASE_SIZE * @kBASE_SIZE, "Infinite loop?"
            assert @tileGrid[row][col] ~= nil
            {row, col} = marked[row][col]
            table.insert(path, {row, col})
        table.insert(path, {row1, col1})
        return path

export ^

require "tile"
require "rooms/rock"
require "rooms/corridor"
require "rooms/reactor"
require "rooms/extractor"
shapes = require "rooms/shape"

class Spacebase
    kBASE_SIZE: 20  -- in tile²
    totalSize: ->
        return Spacebase.kBASE_SIZE * Tile.kTILE_SIZE

    new: =>
        @tileGrid = {}
        for i=1, @kBASE_SIZE
            table.insert(@tileGrid, {})
            for j=1, @kBASE_SIZE
                table.insert(@tileGrid[i], nil)
        @rooms = {}
        @mousePosition = {0, 0} -- updated by external class
        @floatingRoom = nil

        -- build the first rooms
        startupRooms = {}
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 + 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2, @kBASE_SIZE/2 - 1}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Corridor(shapes.t, {@kBASE_SIZE / 2, @kBASE_SIZE/2}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Reactor(shapes.t, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2 + 3}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Extractor(shapes.o, {@kBASE_SIZE / 2 + 2, @kBASE_SIZE/2 + 2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        for room in *startupRooms
            @addRoom(room)

        @crew = {}

    update: (dt) =>
        if @floatingRoom
            mx, my = @mousePosition[1], @mousePosition[2]
            {wx, wy} = @worldToTile(mx, my)
            @floatingRoom\updatePosition(wx, wy)
        for room in *@rooms
            room\update(dt)

    draw: =>
        tileSize = Tile.kTILE_SIZE
        totalSize = Spacebase.totalSize()
        for i=1, @kBASE_SIZE + 1
            love.graphics.setColor(255, 255, 255, 150)
            love.graphics.setLineWidth(1)
            i -= 1 -- begins at 0
            love.graphics.line(0, i * tileSize, totalSize, i * tileSize)
            love.graphics.line(i * tileSize, 0, i * tileSize, totalSize)
        for room in *@rooms
            @drawRoom(room)
        if @floatingRoom
            @drawRoom(@floatingRoom)
        -- debug: see neighbors
        if kDEBUG
            love.graphics.setLineWidth(2)
            love.graphics.setColor({50,50,50})
            neighbors = {Tile.kRIGHT, Tile.kLEFT, Tile.kUP, Tile.kDOWN}
            for i=1,@kBASE_SIZE
                for j=1,@kBASE_SIZE
                    for n in *neighbors
                        if @tileGrid[i][j] and @tileGrid[i][j].neighbors[n]
                            neigh = @tileGrid[i][j].neighbors[n]
                            {x1, y1} = @tileToWorld(i, j)
                            {x2, y2} = @tileToWorld(neigh.row, neigh.col)
                            love.graphics.line(x1, y1, x2, y2)

    drawRoom: (room) =>
        i = room.row - 1
        j = room.col - 1
        love.graphics.push()
        love.graphics.translate(i * Tile.kTILE_SIZE, j * Tile.kTILE_SIZE)
        room\draw()
        love.graphics.pop()

    drawPath: (path) =>
        size = #path
        for i = 1, size - 1
            {row, col} = path[i]
            {row2, col2} = path[i + 1]
            {x, y} = @tileToWorld(row, col)
            {x2, y2} = @tileToWorld(row2, col2)
            love.graphics.setColor(0, 0, 0)
            love.graphics.line(x, y, x2, y2)

    placeFloatingRoom: =>
        -- TODO: assert that floating room is valid
        assert(@floatingRoom)
        @floatingRoom\confirm()
        @addRoom(@floatingRoom)
        @floatingRoom = nil

    addRoom: (room) =>
        table.insert(@rooms, room)
        for tile in *room.tiles
            @tileGrid[tile.row][tile.col] = tile
        @updateNeighbors()

    worldToTile: (x, y) =>
        i = math.ceil(x / Tile.kTILE_SIZE)
        j = math.ceil(y / Tile.kTILE_SIZE)
        return {i, j}

    tileToWorld: (i, j) =>
        x = (i - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        y = (j - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        return {x, y}

    getItems: (fromCrew=true, fromRooms=true) =>
        items = {}
        if fromCrew
            for minion in *@crew
                item = minion\getItem()
                if item
                    table.insert(items, item)
        if fromRooms
            for room in *@rooms
                for item in *room\getItems()
                    table.insert(items, item)
        return items

    updateNeighbors: =>
        for i=1,@kBASE_SIZE
            for j=1,@kBASE_SIZE
                tile = @tileGrid[i][j]
                if tile
                    if i < @kBASE_SIZE
                        tile.neighbors[Tile.kRIGHT] = @tileGrid[i + 1][j]
                    if i > 1
                        tile.neighbors[Tile.kLEFT] = @tileGrid[i - 1][j]
                    if j > 1
                        tile.neighbors[Tile.kUP] = @tileGrid[i][j - 1]
                    if j < @kBASE_SIZE
                        tile.neighbors[Tile.kDOWN] = @tileGrid[i][j + 1]

    pathFinding: (row1, col1, row2, col2) =>
        -- return a path (as a table of {row, col} coordinate) between two tiles
        -- Breadth-First
        assert @tileGrid[row1][col1] ~= nil and @tileGrid[row2][col2] ~= nil
        row, col = row1, col1
        tile_queue = {}
        marked = {} -- contains the previous tile of each tile reached
        in_marked = (r, c) -> return marked[r] ~= nil and marked[r][c] ~= nil
        mark_tile = (r, c, prevCoor) ->
            if marked[r] == nil then marked[r] = {}
            marked[r][c] = prevCoor
        -- breadth-first exhaustive search using a queue
        while row ~= row2 or col ~= col2
            assert @tileGrid[row][col] ~= nil
            tile = @tileGrid[row][col]
            for neigh in *tile.neighbors
                if neigh and not in_marked(neigh.row, neigh.col)
                    mark_tile(neigh.row, neigh.col, {tile.row, tile.col})
                    table.insert(tile_queue, neigh)
            if #tile_queue == 0
                return nil -- no path found
            nextTile = table.remove(tile_queue, 1)
            row, col = nextTile.row, nextTile.col
        assert row == row2 and col == col2, "#{row}, #{col}, #{row2}, #{col2}"
        -- retrace path back using marks
        path = {}
        table.insert(path, {row, col})
        loopCounter = 0
        while row ~= row1 and col ~= col1
            loopCounter += 1
            assert loopCounter <= @kBASE_SIZE * @kBASE_SIZE, "Infinite loop?"
            assert @tileGrid[row][col] ~= nil
            {row, col} = marked[row][col]
            table.insert(path, {row, col})
        table.insert(path, {row1, col1})
        return path


A lil eye movement improvement

A lil eye movement improvement

--
-- Copyright (C) 2017-2019 DBot

-- 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.

import PPM2 from _G

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_STARE = CreateConVar('ppm2_render_stare', '1', {FCVAR_ARCHIVE}, 'Make eyes follow players and move when idling')

class PonyRenderController extends PPM2.ControllerChildren
	@AVALIABLE_CONTROLLERS = {}
	@MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

	CompileTextures: => @GetTextureController()\CompileTextures() if @GetTextureController and @GetTextureController()
	new: (controller) =>
		super(controller)
		@hideModels = false
		@modelCached = controller\GetModel()
		@IGNORE_DRAW = false
		@CompileTextures() if @GetEntity()\IsValid()
		@CreateLegs() if @GetEntity() == LocalPlayer()
		@socksModel = controller\GetSocksModel()
		@socksModel\SetNoDraw(false) if IsValid(@socksModel)
		@newSocksModel = controller\GetNewSocksModel()
		@newSocksModel\SetNoDraw(false) if IsValid(@newSocksModel)
		@lastStareUpdate = 0
		@staringAt = NULL
		@rotatedHeadTarget = false
		@idleEyes = true
		@idleEyesActive = false
		@nextRollEyes = 0
		if @GetEntity()\IsValid()
			@CreateFlexController()
			@CreateEmotesController()

	GetModel: => @controller\GetModel()

	GetLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		@CreateLegs() if not IsValid()
		return @legsModel

	CreateLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		for _, ent in ipairs ents.GetAll()
			if ent.isPonyLegsModel
				ent\Remove()

		with @legsModel = ClientsideModel(@modelCached)
			.isPonyLegsModel = true
			.lastRedrawFix = 0
			\SetNoDraw(true)
			.__PPM2_PonyData = @GetData()
			--\PPMBonesModifier()

		@GetData()\GetWeightController()\UpdateWeight(@legsModel)

		@lastLegUpdate = CurTimeL()
		@legClipPlanePos = Vector(0, 0, 0)
		@legBGSetup = CurTimeL()
		@legUpdateFrame = 0
		@legClipDot = 0
		@duckOffsetHack = @@LEG_CLIP_OFFSET_STAND
		@legsClipPlane = @@LEG_CLIP_VECTOR
		return @legsModel

	@LEG_SHIFT_CONST = 24
	@LEG_SHIFT_CONST_VEHICLE = 14
	@LEG_Z_CONST = 0
	@LEG_Z_CONST_VEHICLE = 20
	@LEG_ANIM_SPEED_CONST = 1
	@LEG_CLIP_OFFSET_STAND = 28
	@LEG_CLIP_OFFSET_DUCK = 12
	@LEG_CLIP_OFFSET_VEHICLE = 11

	UpdateLegs: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return unless IsValid(@legsModel)
		return if @legUpdateFrame == FrameNumberL()
		@legUpdateFrame = FrameNumberL()
		ctime = CurTimeL()
		ply = @GetEntity()
		seq = ply\GetSequence()
		legsModel = @legsModel

		with @legsModel
			PPM2.EntityBonesModifier.ThinkObject(ply.__ppmBonesModifiers) if ply.__ppmBonesModifiers

			for boneid = 0, ply\GetBoneCount() - 1
				\ManipulateBonePosition(0, ply\GetManipulateBonePosition(0))
				\ManipulateBoneAngles(0, ply\GetManipulateBoneAngles(0))
				\ManipulateBoneScale(0, ply\GetManipulateBoneScale(0))

			if seq ~= @legSeq
				@legSeq = seq
				\ResetSequence(seq)

			if @legBGSetup < ctime
				@legBGSetup = ctime + 1
				for _, group in ipairs ply\GetBodyGroups()
					\SetBodygroup(group.id, ply\GetBodygroup(group.id))

			\FrameAdvance(ctime - @lastLegUpdate)
			\SetPlaybackRate(@@LEG_ANIM_SPEED_CONST * ply\GetPlaybackRate())
			@lastLegUpdate = ctime
			\SetPoseParameter('move_x',       (ply\GetPoseParameter('move_x')     * 2) - 1)
			\SetPoseParameter('move_y',       (ply\GetPoseParameter('move_y')     * 2) - 1)
			\SetPoseParameter('move_yaw',     (ply\GetPoseParameter('move_yaw')   * 360) - 180)
			\SetPoseParameter('body_yaw',     (ply\GetPoseParameter('body_yaw')   * 180) - 90)
			\SetPoseParameter('spine_yaw',    (ply\GetPoseParameter('spine_yaw')  * 180) - 90)

		if ply\InVehicle()
			local bonePos

			if bone = @legsModel\LookupBone('LrigNeck1')
				if boneData = @legsModel\GetBonePosition(bone)
					bonePos = boneData

			veh = ply\GetVehicle()
			vehAng = veh\GetAngles()
			eyepos = EyePos()
			vehAng\RotateAroundAxis(vehAng\Up(), 90)

			clipAng = Angle(vehAng.p, vehAng.y, vehAng.r)
			clipAng\RotateAroundAxis(clipAng\Right(), -90)

			@legsClipPlane = clipAng\Forward()
			@legsModel\SetRenderAngles(vehAng)

			drawPos = Vector(@@LEG_SHIFT_CONST_VEHICLE, 0, @@LEG_Z_CONST_VEHICLE)
			drawPos\Rotate(vehAng)
			@legsModel\SetPos(eyepos - drawPos)
			@legsModel\SetRenderOrigin(eyepos - drawPos)

			if not bonePos
				legClipPlanePos = Vector(0, 0, @@LEG_CLIP_OFFSET_VEHICLE)
				legClipPlanePos\Rotate(vehAng)
				@legClipPlanePos = eyepos - legClipPlanePos
			else
				@legClipPlanePos = bonePos

		else
			@legsClipPlane = @@LEG_CLIP_VECTOR
			eangles = EyeAngles()
			yaw = eangles.y - ply\GetPoseParameter('head_yaw') * 180 + 90
			newAng = Angle(0, yaw, 0)
			rad = math.rad(yaw)
			sin, cos = math.sin(rad), math.cos(rad)
			pos = ply\GetPos()
			{:x, :y, :z} = pos
			newPos = Vector(x - cos * @@LEG_SHIFT_CONST, y - sin * @@LEG_SHIFT_CONST, z + @@LEG_Z_CONST)
			if ply\Crouching()
				@duckOffsetHack = @@LEG_CLIP_OFFSET_DUCK
			else
				@duckOffsetHack = Lerp(0.1, @duckOffsetHack, @@LEG_CLIP_OFFSET_STAND)

			@legsModel\SetRenderAngles(newAng)
			@legsModel\SetAngles(newAng)
			@legsModel\SetRenderOrigin(newPos)
			@legsModel\SetPos(newPos)

			if bone = @legsModel\LookupBone('LrigNeck1')
				if boneData = @legsModel\GetBonePosition(bone)
					@legClipPlanePos = boneData
				else
					@legClipPlanePos = Vector(x, y, z + @duckOffsetHack)
			else
				@legClipPlanePos = Vector(x, y, z + @duckOffsetHack)

		@legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

	@LEG_CLIP_VECTOR = Vector(0, 0, -1)
	@LEGS_MAX_DISTANCE = 60 ^ 2
	DrawLegs: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE

		if USE_RENDER_OVERRIDE\GetBool()
			@legsModel\SetNoDraw(false)
			rTime = RealTimeL()
			if @legsModel.lastRedrawFix < rTime
				@legsModel\DrawModel()
				@legsModel.lastRedrawFix = rTime + 5
			if not @legsModel.RenderOverride
				@legsModel.RenderOverride = -> @DrawLegsOverride()
				@legsModel\DrawModel()
			return
		else
			@legsModel\SetNoDraw(true)

		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
		cam.Start3D() if start3D

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

		if LEGS_RENDER_TYPE\GetBool() and ENABLE_FLASHLIGHT_PASS\GetBool()
			render.PushFlashlightMode(true)
			@GetTextureController()\PreDrawLegs(@legsModel)
			if sizes = @GetData()\GetSizeController()
				sizes\ModifyNeck(@legsModel)
				sizes\ModifyLegs(@legsModel)
				sizes\ModifyScale(@legsModel)
			@legsModel\DrawModel()
			@GetTextureController()\PostDrawLegs(@legsModel)
			render.PopFlashlightMode()

		render.PopCustomClipPlane()
		cam.End3D() if start3D
		render.EnableClipping(oldClip)

	DrawLegsOverride: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

		render.PopCustomClipPlane()
		render.EnableClipping(oldClip)

	DrawLegsDepth: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
		cam.Start3D() if start3D

		@GetTextureController()\PreDrawLegs(@legsModel)
		if sizes = @GetData()\GetSizeController()
			sizes\ModifyNeck(@legsModel)
			sizes\ModifyLegs(@legsModel)
			sizes\ModifyScale(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs()

		render.PopCustomClipPlane()
		cam.End3D() if start3D
		render.EnableClipping(oldClip)

	IsValid: => IsValid(@GetEntity()) and @isValid

	Reset: =>
		@flexes\Reset() if @flexes and @flexes.Reset
		@emotes\Reset() if @emotes and @emotes.Reset
		@GetTextureController()\Reset() if @GetTextureController and @GetTextureController() and @GetTextureController().Reset
		@GetTextureController()\ResetTextures() if @GetTextureController and @GetTextureController()

	Remove: =>
		@flexes\Remove() if @flexes
		@emotes\Remove() if @emotes
		@GetTextureController()\Remove() if @GetTextureController and @GetTextureController()
		@isValid = false

	PlayerDeath: =>
		return if not @isValid
		if @emotes
			@emotes\Remove()
			@emotes = nil
		@HideModels(true) if PPM2.ENABLE_NEW_RAGDOLLS\GetBool()
		@GetTextureController()\ResetTextures() if @GetTextureController() and @GetEntity()\IsPony()

	PlayerRespawn: =>
		return if not @isValid
		@GetEmotesController()
		@HideModels(false) if @GetEntity()\IsPony()
		@flexes\PlayerRespawn() if @flexes
		@GetTextureController()\ResetTextures() if @GetTextureController()

	DrawModels: =>
		@socksModel\DrawModel() if IsValid(@socksModel)
		@newSocksModel\DrawModel() if IsValid(@newSocksModel)

	ShouldHideModels: => @hideModels or @GetEntity()\GetNoDraw()

	DoHideModels: (status) =>
		@socksModel\SetNoDraw(status) if IsValid(@socksModel)
		@newSocksModel\SetNoDraw(status) if IsValid(@newSocksModel)

	HideModels: (status = true) =>
		return if @hideModels == status
		@DoHideModels(status)
		@hideModels = status

	CheckTarget: (epos, pos) =>
		return not util.TraceLine({
			start: epos,
			endpos: pos,
			filter: @GetEntity(),
			mask: MASK_BLOCKLOS
		}).Hit

	UpdateStare: =>
		ctime = RealTimeL()
		return if @lastStareUpdate > ctime

		if (not @idleEyes or not ENABLE_STARE\GetBool()) and @idleEyesActive
			@staringAt = NULL
			@GetEntity()\SetEyeTarget(vector_origin)
			@idleEyesActive = false
			return

		return if not @idleEyes or not ENABLE_STARE\GetBool()
		@idleEyesActive = true
		@lastStareUpdate = ctime + 0.2
		lpos = @GetEntity()\EyePos()

		@staringAt = NULL if IsValid(@staringAt) and @staringAt\IsPlayer() and not @staringAt\Alive()

		if IsValid(@staringAt)
			trNew = util.TraceLine({
				start: lpos,
				endpos: lpos + @GetEntity()\EyeAnglesFixed()\Forward() * 270,
				filter: @GetEntity(),
			})

			if trNew.Entity\IsValid() and trNew.Entity\IsPlayer()
				@staringAt = trNew.Entity

			epos = @staringAt\EyePos()
			if epos\Distance(lpos) < 300 and DLib.combat.inPVS(@GetEntity(), @staringAt) and @CheckTarget(lpos, epos)
				@GetEntity()\SetEyeTarget(epos)
				return
			@staringAt = NULL
			@GetEntity()\SetEyeTarget(vector_origin)

		if player.GetCount() ~= 1
			local last
			max = 300
			local lastpos
			for _, ply in ipairs player.GetAll()
				if @GetEntity() ~= ply and ply\Alive()
					epos = ply\EyePos()
					dist = epos\Distance(lpos)
					if dist < max and DLib.combat.inPVS(@GetEntity(), ply) and @CheckTarget(lpos, epos)
						max = dist
						last = ply
						lastpos = epos
			if last
				@GetEntity()\SetEyeTarget(lastpos)
				@staringAt = last
				return

		return if @nextRollEyes > ctime
		@nextRollEyes = ctime + math.random(4, 8) / 6
		ang = @GetEntity()\EyeAnglesFixed()
		@eyeRollTargetPos = Vector(math.random(200, 400), math.random(-80, 80), math.random(-20, 20))
		@prevRollTargetPos = @prevRollTargetPos or @eyeRollTargetPos
		-- @GetEntity()\SetEyeTarget(@prevRollTargetPos)

	UpdateEyeRoll: =>
		return if not ENABLE_STARE\GetBool() or not @idleEyes or not @eyeRollTargetPos or IsValid(@staringAt)
		@prevRollTargetPos = LerpVector(FrameTime() * 6, @prevRollTargetPos, @eyeRollTargetPos)
		roll = Vector(@prevRollTargetPos)
		roll\Rotate(@GetEntity()\EyeAnglesFixed())
		@GetEntity()\SetEyeTarget(@GetEntity()\EyePos() + roll)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid

		with @GetTextureController()
			\PreDraw(ent, drawingNewTask)

		if drawingNewTask
			with bones = ent\PPMBonesModifier()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, ent, @controller)
				\Think(true)
				ent.__ppmBonesModified = true

		@flexes\Think(ent) if @flexes
		@emotes\Think(ent) if @emotes
		if @GetEntity()\IsPlayer()
			@UpdateStare()
			@UpdateEyeRoll()

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes') and @GrabData('HideManesSocks')
			@socksModel\SetNoDraw(true) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(true) if IsValid(@newSocksModel)
		else
			@socksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@newSocksModel)

	PostDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid
		@GetTextureController()\PostDraw(ent, drawingNewTask)

	@ARMS_MATERIAL_INDEX = 0

	PreDrawArms: (ent, hooves = true) =>
		return if not @isValid
		return if not hooves
		if ent and not @armsWeightSetup
			@armsWeightSetup = true
			weight = 1 + (@GetData()\GetWeight() - 1) * 0.4
			vec = Vector(weight, weight, weight)
			ent\ManipulateBoneScale(i, vec) for i = 1, 13
		ent\SetSubMaterial(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBodyName())

	PostDrawArms: (ent, hooves = true) =>
		return if not @isValid
		ent\SetSubMaterial(@@ARMS_MATERIAL_INDEX, '')

	DataChanges: (state) =>
		return if not @isValid
		return if not @GetEntity()
		@GetTextureController()\DataChanges(state)
		@flexes\DataChanges(state) if @flexes
		@emotes\DataChanges(state) if @emotes
		switch state\GetKey()
			when 'Weight'
				@armsWeightSetup = false
				@GetData()\GetWeightController()\UpdateWeight(@legsModel) if IsValid(@legsModel)
			when 'SocksModel'
				@socksModel = @GetData()\GetSocksModel()
				@socksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@socksModel)
				@GetTextureController()\UpdateSocks(@GetEntity(), @socksModel) if @GetTextureController() and IsValid(@socksModel)
			when 'NewSocksModel'
				@newSocksModel = @GetData()\GetNewSocksModel()
				@newSocksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@newSocksModel)
				@GetTextureController()\UpdateNewSocks(@GetEntity(), @newSocksModel) if @GetTextureController() and IsValid(@newSocksModel)
			when 'NoFlex'
				if state\GetValue()
					@flexes\ResetSequences() if @flexes
					@flexes = nil
				else
					@CreateFlexController()
	GetTextureController: =>
		return @renderController if not @isValid
		if not @renderController
			cls = PPM2.GetTextureController(@modelCached)
			@renderController = cls(@)
		@renderController.ent = @GetEntity()
		return @renderController

	CreateFlexController: =>
		return @flexes if not @isValid
		return if @GetData()\GetNoFlex()
		if not @flexes
			cls = PPM2.GetFlexController(@modelCached)
			return if not cls
			@flexes = cls(@)
		@flexes.ent = @GetEntity()
		return @flexes

	CreateEmotesController: =>
		return @emotes if not @isValid
		if not @emotes or not @emotes\IsValid()
			cls = PPM2.GetPonyExpressionsController(@modelCached)
			return if not cls
			@emotes = cls(@)
		@emotes.ent = @GetEntity()
		return @emotes

	GetFlexController: => @flexes
	GetEmotesController: => @emotes

class NewPonyRenderController extends PonyRenderController
	@MODELS = {'models/ppm/player_default_base_new.mdl', 'models/ppm/player_default_base_new_nj.mdl'}

	new: (data) =>
		super(data)
		@upperManeModel = data\GetUpperManeModel()
		@lowerManeModel = data\GetLowerManeModel()
		@tailModel = data\GetTailModel()
		@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)
	__tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

	DataChanges: (state) =>
		return if not @GetEntity()
		return if not @isValid
		switch state\GetKey()
			when 'UpperManeModel'
				@upperManeModel = @GetData()\GetUpperManeModel()
				@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
				@GetTextureController()\UpdateUpperMane(@GetEntity(), @upperManeModel) if @GetTextureController() and IsValid(@upperManeModel)
			when 'LowerManeModel'
				@lowerManeModel = @GetData()\GetLowerManeModel()
				@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
				@GetTextureController()\UpdateLowerMane(@GetEntity(), @lowerManeModel) if @GetTextureController() and IsValid(@lowerManeModel)
			when 'TailModel'
				@tailModel = @GetData()\GetTailModel()
				@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)
				@GetTextureController()\UpdateTail(@GetEntity(), @tailModel) if @GetTextureController() and IsValid(@tailModel)
		super(state)

	DrawModels: =>
		@upperManeModel\DrawModel() if IsValid(@upperManeModel)
		@lowerManeModel\DrawModel() if IsValid(@lowerManeModel)
		@tailModel\DrawModel() if IsValid(@tailModel)
		super()

	DoHideModels: (status) =>
		super(status)
		@upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(status) if IsValid(@tailModel)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		super(ent, drawingNewTask)

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes')
			@upperManeModel\SetNoDraw(true) if IsValid(@upperManeModel) and @GrabData('HideManesMane')
			@lowerManeModel\SetNoDraw(true) if IsValid(@lowerManeModel) and @GrabData('HideManesMane')
			@tailModel\SetNoDraw(true) if IsValid(@tailModel) and @GrabData('HideManesTail')
		else
			@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
			@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
			@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)

hook.Add 'NotifyShouldTransmit', 'PPM2.RenderController', (should) =>
	if data = @GetPonyData()
		if renderer = data\GetRenderController()
			renderer\HideModels(not should)

PPM2.PonyRenderController = PonyRenderController
PPM2.NewPonyRenderController = NewPonyRenderController
PPM2.GetPonyRenderController = (model = 'models/ppm/player_default_base.mdl') -> PonyRenderController.AVALIABLE_CONTROLLERS[model\lower()] or PonyRenderController
PPM2.GetPonyRendererController = PPM2.GetPonyRenderController
PPM2.GetRenderController = PPM2.GetPonyRenderController
PPM2.GetRendererController = PPM2.GetPonyRenderController

--
-- Copyright (C) 2017-2019 DBot

-- 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.

import PPM2 from _G

ENABLE_FLASHLIGHT_PASS = CreateConVar('ppm2_flashlight_pass', '1', {FCVAR_ARCHIVE}, 'Enable flashlight render pass. This kills FPS.')
ENABLE_LEGS = CreateConVar('ppm2_draw_legs', '1', {FCVAR_ARCHIVE}, 'Draw pony legs.')
USE_RENDER_OVERRIDE = CreateConVar('ppm2_legs_new', '1', {FCVAR_ARCHIVE}, 'Use RenderOverride function for legs drawing')
LEGS_RENDER_TYPE = CreateConVar('ppm2_render_legstype', '0', {FCVAR_ARCHIVE}, 'When render legs. 0 - Before Opaque renderables; 1 - after Translucent renderables')
ENABLE_STARE = CreateConVar('ppm2_render_stare', '1', {FCVAR_ARCHIVE}, 'Make eyes follow players and move when idling')

class PonyRenderController extends PPM2.ControllerChildren
	@AVALIABLE_CONTROLLERS = {}
	@MODELS = {'models/ppm/player_default_base.mdl', 'models/ppm/player_default_base_nj.mdl', 'models/cppm/player_default_base.mdl', 'models/cppm/player_default_base_nj.mdl'}

	CompileTextures: => @GetTextureController()\CompileTextures() if @GetTextureController and @GetTextureController()
	new: (controller) =>
		super(controller)
		@hideModels = false
		@modelCached = controller\GetModel()
		@IGNORE_DRAW = false
		@CompileTextures() if @GetEntity()\IsValid()
		@CreateLegs() if @GetEntity() == LocalPlayer()
		@socksModel = controller\GetSocksModel()
		@socksModel\SetNoDraw(false) if IsValid(@socksModel)
		@newSocksModel = controller\GetNewSocksModel()
		@newSocksModel\SetNoDraw(false) if IsValid(@newSocksModel)
		@lastStareUpdate = 0
		@staringAt = NULL
		@rotatedHeadTarget = false
		@idleEyes = true
		@idleEyesActive = false
		@nextRollEyes = 0
		if @GetEntity()\IsValid()
			@CreateFlexController()
			@CreateEmotesController()

	GetModel: => @controller\GetModel()

	GetLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		@CreateLegs() if not IsValid()
		return @legsModel

	CreateLegs: =>
		return NULL if not @isValid
		return NULL if @GetEntity() ~= LocalPlayer()
		for _, ent in ipairs ents.GetAll()
			if ent.isPonyLegsModel
				ent\Remove()

		with @legsModel = ClientsideModel(@modelCached)
			.isPonyLegsModel = true
			.lastRedrawFix = 0
			\SetNoDraw(true)
			.__PPM2_PonyData = @GetData()
			--\PPMBonesModifier()

		@GetData()\GetWeightController()\UpdateWeight(@legsModel)

		@lastLegUpdate = CurTimeL()
		@legClipPlanePos = Vector(0, 0, 0)
		@legBGSetup = CurTimeL()
		@legUpdateFrame = 0
		@legClipDot = 0
		@duckOffsetHack = @@LEG_CLIP_OFFSET_STAND
		@legsClipPlane = @@LEG_CLIP_VECTOR
		return @legsModel

	@LEG_SHIFT_CONST = 24
	@LEG_SHIFT_CONST_VEHICLE = 14
	@LEG_Z_CONST = 0
	@LEG_Z_CONST_VEHICLE = 20
	@LEG_ANIM_SPEED_CONST = 1
	@LEG_CLIP_OFFSET_STAND = 28
	@LEG_CLIP_OFFSET_DUCK = 12
	@LEG_CLIP_OFFSET_VEHICLE = 11

	UpdateLegs: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return unless IsValid(@legsModel)
		return if @legUpdateFrame == FrameNumberL()
		@legUpdateFrame = FrameNumberL()
		ctime = CurTimeL()
		ply = @GetEntity()
		seq = ply\GetSequence()
		legsModel = @legsModel

		with @legsModel
			PPM2.EntityBonesModifier.ThinkObject(ply.__ppmBonesModifiers) if ply.__ppmBonesModifiers

			for boneid = 0, ply\GetBoneCount() - 1
				\ManipulateBonePosition(0, ply\GetManipulateBonePosition(0))
				\ManipulateBoneAngles(0, ply\GetManipulateBoneAngles(0))
				\ManipulateBoneScale(0, ply\GetManipulateBoneScale(0))

			if seq ~= @legSeq
				@legSeq = seq
				\ResetSequence(seq)

			if @legBGSetup < ctime
				@legBGSetup = ctime + 1
				for _, group in ipairs ply\GetBodyGroups()
					\SetBodygroup(group.id, ply\GetBodygroup(group.id))

			\FrameAdvance(ctime - @lastLegUpdate)
			\SetPlaybackRate(@@LEG_ANIM_SPEED_CONST * ply\GetPlaybackRate())
			@lastLegUpdate = ctime
			\SetPoseParameter('move_x',       (ply\GetPoseParameter('move_x')     * 2) - 1)
			\SetPoseParameter('move_y',       (ply\GetPoseParameter('move_y')     * 2) - 1)
			\SetPoseParameter('move_yaw',     (ply\GetPoseParameter('move_yaw')   * 360) - 180)
			\SetPoseParameter('body_yaw',     (ply\GetPoseParameter('body_yaw')   * 180) - 90)
			\SetPoseParameter('spine_yaw',    (ply\GetPoseParameter('spine_yaw')  * 180) - 90)

		if ply\InVehicle()
			local bonePos

			if bone = @legsModel\LookupBone('LrigNeck1')
				if boneData = @legsModel\GetBonePosition(bone)
					bonePos = boneData

			veh = ply\GetVehicle()
			vehAng = veh\GetAngles()
			eyepos = EyePos()
			vehAng\RotateAroundAxis(vehAng\Up(), 90)

			clipAng = Angle(vehAng.p, vehAng.y, vehAng.r)
			clipAng\RotateAroundAxis(clipAng\Right(), -90)

			@legsClipPlane = clipAng\Forward()
			@legsModel\SetRenderAngles(vehAng)

			drawPos = Vector(@@LEG_SHIFT_CONST_VEHICLE, 0, @@LEG_Z_CONST_VEHICLE)
			drawPos\Rotate(vehAng)
			@legsModel\SetPos(eyepos - drawPos)
			@legsModel\SetRenderOrigin(eyepos - drawPos)

			if not bonePos
				legClipPlanePos = Vector(0, 0, @@LEG_CLIP_OFFSET_VEHICLE)
				legClipPlanePos\Rotate(vehAng)
				@legClipPlanePos = eyepos - legClipPlanePos
			else
				@legClipPlanePos = bonePos

		else
			@legsClipPlane = @@LEG_CLIP_VECTOR
			eangles = EyeAngles()
			yaw = eangles.y - ply\GetPoseParameter('head_yaw') * 180 + 90
			newAng = Angle(0, yaw, 0)
			rad = math.rad(yaw)
			sin, cos = math.sin(rad), math.cos(rad)
			pos = ply\GetPos()
			{:x, :y, :z} = pos
			newPos = Vector(x - cos * @@LEG_SHIFT_CONST, y - sin * @@LEG_SHIFT_CONST, z + @@LEG_Z_CONST)
			if ply\Crouching()
				@duckOffsetHack = @@LEG_CLIP_OFFSET_DUCK
			else
				@duckOffsetHack = Lerp(0.1, @duckOffsetHack, @@LEG_CLIP_OFFSET_STAND)

			@legsModel\SetRenderAngles(newAng)
			@legsModel\SetAngles(newAng)
			@legsModel\SetRenderOrigin(newPos)
			@legsModel\SetPos(newPos)

			if bone = @legsModel\LookupBone('LrigNeck1')
				if boneData = @legsModel\GetBonePosition(bone)
					@legClipPlanePos = boneData
				else
					@legClipPlanePos = Vector(x, y, z + @duckOffsetHack)
			else
				@legClipPlanePos = Vector(x, y, z + @duckOffsetHack)

		@legClipDot = @legsClipPlane\Dot(@legClipPlanePos)

	@LEG_CLIP_VECTOR = Vector(0, 0, -1)
	@LEGS_MAX_DISTANCE = 60 ^ 2
	DrawLegs: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE

		if USE_RENDER_OVERRIDE\GetBool()
			@legsModel\SetNoDraw(false)
			rTime = RealTimeL()
			if @legsModel.lastRedrawFix < rTime
				@legsModel\DrawModel()
				@legsModel.lastRedrawFix = rTime + 5
			if not @legsModel.RenderOverride
				@legsModel.RenderOverride = -> @DrawLegsOverride()
				@legsModel\DrawModel()
			return
		else
			@legsModel\SetNoDraw(true)

		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
		cam.Start3D() if start3D

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

		if LEGS_RENDER_TYPE\GetBool() and ENABLE_FLASHLIGHT_PASS\GetBool()
			render.PushFlashlightMode(true)
			@GetTextureController()\PreDrawLegs(@legsModel)
			if sizes = @GetData()\GetSizeController()
				sizes\ModifyNeck(@legsModel)
				sizes\ModifyLegs(@legsModel)
				sizes\ModifyScale(@legsModel)
			@legsModel\DrawModel()
			@GetTextureController()\PostDrawLegs(@legsModel)
			render.PopFlashlightMode()

		render.PopCustomClipPlane()
		cam.End3D() if start3D
		render.EnableClipping(oldClip)

	DrawLegsOverride: =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)

		@GetTextureController()\PreDrawLegs(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs(@legsModel)

		render.PopCustomClipPlane()
		render.EnableClipping(oldClip)

	DrawLegsDepth: (start3D = false) =>
		return if not @isValid
		return if not ENABLE_LEGS\GetBool()
		return if not @GetEntity()\Alive()
		return if @GetEntity()\InVehicle() and EyeAngles().p < 30
		return if not @GetEntity()\InVehicle() and EyeAngles().p < 60
		@CreateLegs() unless IsValid(@legsModel)
		return unless IsValid(@legsModel)
		return if @GetEntity()\ShouldDrawLocalPlayer()
		return if (@GetEntity()\GetPos() + @GetEntity()\GetViewOffset())\DistToSqr(EyePos()) > @@LEGS_MAX_DISTANCE
		@UpdateLegs()

		oldClip = render.EnableClipping(true)
		render.PushCustomClipPlane(@legsClipPlane, @legClipDot)
		cam.Start3D() if start3D

		@GetTextureController()\PreDrawLegs(@legsModel)
		if sizes = @GetData()\GetSizeController()
			sizes\ModifyNeck(@legsModel)
			sizes\ModifyLegs(@legsModel)
			sizes\ModifyScale(@legsModel)
		@legsModel\DrawModel()
		@GetTextureController()\PostDrawLegs()

		render.PopCustomClipPlane()
		cam.End3D() if start3D
		render.EnableClipping(oldClip)

	IsValid: => IsValid(@GetEntity()) and @isValid

	Reset: =>
		@flexes\Reset() if @flexes and @flexes.Reset
		@emotes\Reset() if @emotes and @emotes.Reset
		@GetTextureController()\Reset() if @GetTextureController and @GetTextureController() and @GetTextureController().Reset
		@GetTextureController()\ResetTextures() if @GetTextureController and @GetTextureController()

	Remove: =>
		@flexes\Remove() if @flexes
		@emotes\Remove() if @emotes
		@GetTextureController()\Remove() if @GetTextureController and @GetTextureController()
		@isValid = false

	PlayerDeath: =>
		return if not @isValid
		if @emotes
			@emotes\Remove()
			@emotes = nil
		@HideModels(true) if PPM2.ENABLE_NEW_RAGDOLLS\GetBool()
		@GetTextureController()\ResetTextures() if @GetTextureController() and @GetEntity()\IsPony()

	PlayerRespawn: =>
		return if not @isValid
		@GetEmotesController()
		@HideModels(false) if @GetEntity()\IsPony()
		@flexes\PlayerRespawn() if @flexes
		@GetTextureController()\ResetTextures() if @GetTextureController()

	DrawModels: =>
		@socksModel\DrawModel() if IsValid(@socksModel)
		@newSocksModel\DrawModel() if IsValid(@newSocksModel)

	ShouldHideModels: => @hideModels or @GetEntity()\GetNoDraw()

	DoHideModels: (status) =>
		@socksModel\SetNoDraw(status) if IsValid(@socksModel)
		@newSocksModel\SetNoDraw(status) if IsValid(@newSocksModel)

	HideModels: (status = true) =>
		return if @hideModels == status
		@DoHideModels(status)
		@hideModels = status

	CheckTarget: (epos, pos) =>
		return not util.TraceLine({
			start: epos,
			endpos: pos,
			filter: @GetEntity(),
			mask: MASK_BLOCKLOS
		}).Hit

	UpdateStare: =>
		ctime = RealTimeL()
		return if @lastStareUpdate > ctime

		if (not @idleEyes or not ENABLE_STARE\GetBool()) and @idleEyesActive
			@staringAt = NULL
			@GetEntity()\SetEyeTarget(Vector())
			@idleEyesActive = false
			return

		return if not @idleEyes or not ENABLE_STARE\GetBool()
		@idleEyesActive = true
		@lastStareUpdate = ctime + 0.2
		lpos = @GetEntity()\EyePos()

		@staringAt = NULL if IsValid(@staringAt) and @staringAt\IsPlayer() and not @staringAt\Alive()

		if IsValid(@staringAt)
			trNew = util.TraceLine({
				start: lpos,
				endpos: lpos + @GetEntity()\EyeAnglesFixed()\Forward() * 270,
				filter: @GetEntity(),
			})

			if trNew.Entity\IsValid() and trNew.Entity\IsPlayer()
				@staringAt = trNew.Entity

			epos = @staringAt\EyePos()
			if epos\Distance(lpos) < 300 and DLib.combat.inPVS(@GetEntity(), @staringAt) and @CheckTarget(lpos, epos)
				@GetEntity()\SetEyeTarget(epos)
				return
			@staringAt = NULL
			@GetEntity()\SetEyeTarget(Vector())

		if player.GetCount() ~= 1
			local last
			max = 300
			local lastpos
			for _, ply in ipairs player.GetAll()
				if @GetEntity() ~= ply and ply\Alive()
					epos = ply\EyePos()
					dist = epos\Distance(lpos)
					if dist < max and DLib.combat.inPVS(@GetEntity(), ply) and @CheckTarget(lpos, epos)
						max = dist
						last = ply
						lastpos = epos
			if last
				@GetEntity()\SetEyeTarget(lastpos)
				@staringAt = last
				return

		return if @nextRollEyes > ctime
		@nextRollEyes = ctime + math.random(4, 16) / 6
		ang = @GetEntity()\EyeAnglesFixed()
		@eyeRollTargetPos = Vector(math.random(200, 400), math.random(-80, 80), math.random(-20, 20))
		@prevRollTargetPos = @prevRollTargetPos or @eyeRollTargetPos
		-- @GetEntity()\SetEyeTarget(@prevRollTargetPos)

	UpdateEyeRoll: =>
		return if not ENABLE_STARE\GetBool() or not @idleEyes or not @eyeRollTargetPos or IsValid(@staringAt)
		@prevRollTargetPos = LerpVector(FrameTime() * 6, @prevRollTargetPos, @eyeRollTargetPos)
		roll = Vector(@prevRollTargetPos)
		roll\Rotate(@GetEntity()\EyeAnglesFixed())
		@GetEntity()\SetEyeTarget(@GetEntity()\EyePos() + roll)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid

		with @GetTextureController()
			\PreDraw(ent, drawingNewTask)

		if drawingNewTask
			with bones = ent\PPMBonesModifier()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, ent, @controller)
				\Think(true)
				ent.__ppmBonesModified = true

		@flexes\Think(ent) if @flexes
		@emotes\Think(ent) if @emotes
		if @GetEntity()\IsPlayer()
			@UpdateStare()
			@UpdateEyeRoll()

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes') and @GrabData('HideManesSocks')
			@socksModel\SetNoDraw(true) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(true) if IsValid(@newSocksModel)
		else
			@socksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@socksModel)
			@newSocksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@newSocksModel)

	PostDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		return if not @isValid
		@GetTextureController()\PostDraw(ent, drawingNewTask)

	@ARMS_MATERIAL_INDEX = 0

	PreDrawArms: (ent, hooves = true) =>
		return if not @isValid
		return if not hooves
		if ent and not @armsWeightSetup
			@armsWeightSetup = true
			weight = 1 + (@GetData()\GetWeight() - 1) * 0.4
			vec = Vector(weight, weight, weight)
			ent\ManipulateBoneScale(i, vec) for i = 1, 13
		ent\SetSubMaterial(@@ARMS_MATERIAL_INDEX, @GetTextureController()\GetBodyName())

	PostDrawArms: (ent, hooves = true) =>
		return if not @isValid
		ent\SetSubMaterial(@@ARMS_MATERIAL_INDEX, '')

	DataChanges: (state) =>
		return if not @isValid
		return if not @GetEntity()
		@GetTextureController()\DataChanges(state)
		@flexes\DataChanges(state) if @flexes
		@emotes\DataChanges(state) if @emotes
		switch state\GetKey()
			when 'Weight'
				@armsWeightSetup = false
				@GetData()\GetWeightController()\UpdateWeight(@legsModel) if IsValid(@legsModel)
			when 'SocksModel'
				@socksModel = @GetData()\GetSocksModel()
				@socksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@socksModel)
				@GetTextureController()\UpdateSocks(@GetEntity(), @socksModel) if @GetTextureController() and IsValid(@socksModel)
			when 'NewSocksModel'
				@newSocksModel = @GetData()\GetNewSocksModel()
				@newSocksModel\SetNoDraw(@ShouldHideModels()) if IsValid(@newSocksModel)
				@GetTextureController()\UpdateNewSocks(@GetEntity(), @newSocksModel) if @GetTextureController() and IsValid(@newSocksModel)
			when 'NoFlex'
				if state\GetValue()
					@flexes\ResetSequences() if @flexes
					@flexes = nil
				else
					@CreateFlexController()
	GetTextureController: =>
		return @renderController if not @isValid
		if not @renderController
			cls = PPM2.GetTextureController(@modelCached)
			@renderController = cls(@)
		@renderController.ent = @GetEntity()
		return @renderController

	CreateFlexController: =>
		return @flexes if not @isValid
		return if @GetData()\GetNoFlex()
		if not @flexes
			cls = PPM2.GetFlexController(@modelCached)
			return if not cls
			@flexes = cls(@)
		@flexes.ent = @GetEntity()
		return @flexes

	CreateEmotesController: =>
		return @emotes if not @isValid
		if not @emotes or not @emotes\IsValid()
			cls = PPM2.GetPonyExpressionsController(@modelCached)
			return if not cls
			@emotes = cls(@)
		@emotes.ent = @GetEntity()
		return @emotes

	GetFlexController: => @flexes
	GetEmotesController: => @emotes

class NewPonyRenderController extends PonyRenderController
	@MODELS = {'models/ppm/player_default_base_new.mdl', 'models/ppm/player_default_base_new_nj.mdl'}

	new: (data) =>
		super(data)
		@upperManeModel = data\GetUpperManeModel()
		@lowerManeModel = data\GetLowerManeModel()
		@tailModel = data\GetTailModel()
		@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)
	__tostring: => "[#{@@__name}:#{@objID}|#{@GetData()}]"

	DataChanges: (state) =>
		return if not @GetEntity()
		return if not @isValid
		switch state\GetKey()
			when 'UpperManeModel'
				@upperManeModel = @GetData()\GetUpperManeModel()
				@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
				@GetTextureController()\UpdateUpperMane(@GetEntity(), @upperManeModel) if @GetTextureController() and IsValid(@upperManeModel)
			when 'LowerManeModel'
				@lowerManeModel = @GetData()\GetLowerManeModel()
				@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
				@GetTextureController()\UpdateLowerMane(@GetEntity(), @lowerManeModel) if @GetTextureController() and IsValid(@lowerManeModel)
			when 'TailModel'
				@tailModel = @GetData()\GetTailModel()
				@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)
				@GetTextureController()\UpdateTail(@GetEntity(), @tailModel) if @GetTextureController() and IsValid(@tailModel)
		super(state)

	DrawModels: =>
		@upperManeModel\DrawModel() if IsValid(@upperManeModel)
		@lowerManeModel\DrawModel() if IsValid(@lowerManeModel)
		@tailModel\DrawModel() if IsValid(@tailModel)
		super()

	DoHideModels: (status) =>
		super(status)
		@upperManeModel\SetNoDraw(status) if IsValid(@upperManeModel)
		@lowerManeModel\SetNoDraw(status) if IsValid(@lowerManeModel)
		@tailModel\SetNoDraw(status) if IsValid(@tailModel)

	PreDraw: (ent = @GetEntity(), drawingNewTask = false) =>
		super(ent, drawingNewTask)

		if ent.RenderOverride and not ent.__ppm2RenderOverride and @GrabData('HideManes')
			@upperManeModel\SetNoDraw(true) if IsValid(@upperManeModel) and @GrabData('HideManesMane')
			@lowerManeModel\SetNoDraw(true) if IsValid(@lowerManeModel) and @GrabData('HideManesMane')
			@tailModel\SetNoDraw(true) if IsValid(@tailModel) and @GrabData('HideManesTail')
		else
			@upperManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@upperManeModel)
			@lowerManeModel\SetNoDraw(@ShouldHideModels()) if IsValid(@lowerManeModel)
			@tailModel\SetNoDraw(@ShouldHideModels()) if IsValid(@tailModel)

hook.Add 'NotifyShouldTransmit', 'PPM2.RenderController', (should) =>
	if data = @GetPonyData()
		if renderer = data\GetRenderController()
			renderer\HideModels(not should)

PPM2.PonyRenderController = PonyRenderController
PPM2.NewPonyRenderController = NewPonyRenderController
PPM2.GetPonyRenderController = (model = 'models/ppm/player_default_base.mdl') -> PonyRenderController.AVALIABLE_CONTROLLERS[model\lower()] or PonyRenderController
PPM2.GetPonyRendererController = PPM2.GetPonyRenderController
PPM2.GetRenderController = PPM2.GetPonyRenderController
PPM2.GetRendererController = PPM2.GetPonyRenderController


showed grid in DEBUG mode only

showed grid in DEBUG mode only

export ^

require "tile"
require "rooms/rock"
require "rooms/corridor"
require "rooms/reactor"
require "rooms/extractor"
shapes = require "rooms/shape"

class Spacebase
    kBASE_SIZE: 20  -- in tile²
    totalSize: ->
        return Spacebase.kBASE_SIZE * Tile.kTILE_SIZE

    new: =>
        @tileGrid = {}
        for i=1, @kBASE_SIZE
            table.insert(@tileGrid, {})
            for j=1, @kBASE_SIZE
                table.insert(@tileGrid[i], nil)
        @rooms = {}
        @mousePosition = {0, 0} -- updated by external class
        @floatingRoom = nil

        -- build the first rooms
        startupRooms = {}
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 + 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2, @kBASE_SIZE/2 - 1}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Corridor(shapes.t, {@kBASE_SIZE / 2, @kBASE_SIZE/2}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Reactor(shapes.t, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2 + 3}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Extractor(shapes.o, {@kBASE_SIZE / 2 + 2, @kBASE_SIZE/2 + 2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        for room in *startupRooms
            @addRoom(room)

        @crew = {}

    update: (dt) =>
        if @floatingRoom
            mx, my = @mousePosition[1], @mousePosition[2]
            {wx, wy} = @worldToTile(mx, my)
            @floatingRoom\updatePosition(wx, wy)
        for room in *@rooms
            room\update(dt)

    draw: =>
        tileSize = Tile.kTILE_SIZE
        totalSize = Spacebase.totalSize()
        if kDEBUG -- show grid
            for i=1, @kBASE_SIZE + 1
                love.graphics.setColor(255, 255, 255, 150)
                love.graphics.setLineWidth(1)
                i -= 1 -- begins at 0
                love.graphics.line(0, i * tileSize, totalSize, i * tileSize)
                love.graphics.line(i * tileSize, 0, i * tileSize, totalSize)
        for room in *@rooms
            @drawRoom(room)
        if @floatingRoom
            @drawRoom(@floatingRoom)
        if kDEBUG
            -- display neighbours
            love.graphics.setLineWidth(2)
            love.graphics.setColor({50,50,50})
            neighbors = {Tile.kRIGHT, Tile.kLEFT, Tile.kUP, Tile.kDOWN}
            for i=1,@kBASE_SIZE
                for j=1,@kBASE_SIZE
                    for n in *neighbors
                        if @tileGrid[i][j] and @tileGrid[i][j].neighbors[n]
                            neigh = @tileGrid[i][j].neighbors[n]
                            {x1, y1} = @tileToWorld(i, j)
                            {x2, y2} = @tileToWorld(neigh.row, neigh.col)
                            love.graphics.line(x1, y1, x2, y2)

    drawRoom: (room) =>
        i = room.row - 1
        j = room.col - 1
        love.graphics.push()
        love.graphics.translate(i * Tile.kTILE_SIZE, j * Tile.kTILE_SIZE)
        room\draw()
        love.graphics.pop()

    drawPath: (path) =>
        size = #path
        for i = 1, size - 1
            {row, col} = path[i]
            {row2, col2} = path[i + 1]
            {x, y} = @tileToWorld(row, col)
            {x2, y2} = @tileToWorld(row2, col2)
            love.graphics.setColor(0, 0, 0)
            love.graphics.line(x, y, x2, y2)

    placeFloatingRoom: =>
        -- TODO: assert that floating room is valid
        assert(@floatingRoom)
        @floatingRoom\confirm()
        @addRoom(@floatingRoom)
        @floatingRoom = nil

    addRoom: (room) =>
        table.insert(@rooms, room)
        for tile in *room.tiles
            @tileGrid[tile.row][tile.col] = tile
        @updateNeighbors()

    worldToTile: (x, y) =>
        i = math.ceil(x / Tile.kTILE_SIZE)
        j = math.ceil(y / Tile.kTILE_SIZE)
        return {i, j}

    tileToWorld: (i, j) =>
        x = (i - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        y = (j - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        return {x, y}

    getItems: (fromCrew=true, fromRooms=true) =>
        items = {}
        if fromCrew
            for minion in *@crew
                item = minion\getItem()
                if item
                    table.insert(items, item)
        if fromRooms
            for room in *@rooms
                for item in *room\getItems()
                    table.insert(items, item)
        return items

    updateNeighbors: =>
        for i=1,@kBASE_SIZE
            for j=1,@kBASE_SIZE
                tile = @tileGrid[i][j]
                if tile
                    if i < @kBASE_SIZE
                        tile.neighbors[Tile.kRIGHT] = @tileGrid[i + 1][j]
                    if i > 1
                        tile.neighbors[Tile.kLEFT] = @tileGrid[i - 1][j]
                    if j > 1
                        tile.neighbors[Tile.kUP] = @tileGrid[i][j - 1]
                    if j < @kBASE_SIZE
                        tile.neighbors[Tile.kDOWN] = @tileGrid[i][j + 1]

    pathFinding: (row1, col1, row2, col2) =>
        -- return a path (as a table of {row, col} coordinate) between two tiles
        -- Breadth-First
        assert @tileGrid[row1][col1] ~= nil and @tileGrid[row2][col2] ~= nil
        row, col = row1, col1
        tile_queue = {}
        marked = {} -- contains the previous tile of each tile reached
        in_marked = (r, c) -> return marked[r] ~= nil and marked[r][c] ~= nil
        mark_tile = (r, c, prevCoor) ->
            if marked[r] == nil then marked[r] = {}
            marked[r][c] = prevCoor
        -- breadth-first exhaustive search using a queue
        while row ~= row2 or col ~= col2
            assert @tileGrid[row][col] ~= nil
            tile = @tileGrid[row][col]
            for neigh in *tile.neighbors
                if neigh and not in_marked(neigh.row, neigh.col)
                    mark_tile(neigh.row, neigh.col, {tile.row, tile.col})
                    table.insert(tile_queue, neigh)
            if #tile_queue == 0
                return nil -- no path found
            nextTile = table.remove(tile_queue, 1)
            row, col = nextTile.row, nextTile.col
        assert row == row2 and col == col2, "#{row}, #{col}, #{row2}, #{col2}"
        -- retrace path back using marks
        path = {}
        table.insert(path, {row, col})
        loopCounter = 0
        while row ~= row1 and col ~= col1
            loopCounter += 1
            assert loopCounter <= @kBASE_SIZE * @kBASE_SIZE, "Infinite loop?"
            assert @tileGrid[row][col] ~= nil
            {row, col} = marked[row][col]
            table.insert(path, {row, col})
        table.insert(path, {row1, col1})
        return path

export ^

require "tile"
require "rooms/rock"
require "rooms/corridor"
require "rooms/reactor"
require "rooms/extractor"
shapes = require "rooms/shape"

class Spacebase
    kBASE_SIZE: 20  -- in tile²
    totalSize: ->
        return Spacebase.kBASE_SIZE * Tile.kTILE_SIZE

    new: =>
        @tileGrid = {}
        for i=1, @kBASE_SIZE
            table.insert(@tileGrid, {})
            for j=1, @kBASE_SIZE
                table.insert(@tileGrid[i], nil)
        @rooms = {}
        @mousePosition = {0, 0} -- updated by external class
        @floatingRoom = nil

        -- build the first rooms
        startupRooms = {}
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2 + 1, @kBASE_SIZE/2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Rock(shapes.dot, {@kBASE_SIZE / 2, @kBASE_SIZE/2 - 1}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Corridor(shapes.t, {@kBASE_SIZE / 2, @kBASE_SIZE/2}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Reactor(shapes.t, {@kBASE_SIZE / 2 - 1, @kBASE_SIZE/2 + 3}, 1)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        temp_room = Extractor(shapes.o, {@kBASE_SIZE / 2 + 2, @kBASE_SIZE/2 + 2}, 0)
        temp_room\confirm()
        temp_room\build(true)
        table.insert(startupRooms, temp_room)
        for room in *startupRooms
            @addRoom(room)

        @crew = {}

    update: (dt) =>
        if @floatingRoom
            mx, my = @mousePosition[1], @mousePosition[2]
            {wx, wy} = @worldToTile(mx, my)
            @floatingRoom\updatePosition(wx, wy)
        for room in *@rooms
            room\update(dt)

    draw: =>
        tileSize = Tile.kTILE_SIZE
        totalSize = Spacebase.totalSize()
        for i=1, @kBASE_SIZE + 1
            love.graphics.setColor(255, 255, 255, 150)
            love.graphics.setLineWidth(1)
            i -= 1 -- begins at 0
            love.graphics.line(0, i * tileSize, totalSize, i * tileSize)
            love.graphics.line(i * tileSize, 0, i * tileSize, totalSize)
        for room in *@rooms
            @drawRoom(room)
        if @floatingRoom
            @drawRoom(@floatingRoom)
        if kDEBUG
            -- display neighbours
            love.graphics.setLineWidth(2)
            love.graphics.setColor({50,50,50})
            neighbors = {Tile.kRIGHT, Tile.kLEFT, Tile.kUP, Tile.kDOWN}
            for i=1,@kBASE_SIZE
                for j=1,@kBASE_SIZE
                    for n in *neighbors
                        if @tileGrid[i][j] and @tileGrid[i][j].neighbors[n]
                            neigh = @tileGrid[i][j].neighbors[n]
                            {x1, y1} = @tileToWorld(i, j)
                            {x2, y2} = @tileToWorld(neigh.row, neigh.col)
                            love.graphics.line(x1, y1, x2, y2)

    drawRoom: (room) =>
        i = room.row - 1
        j = room.col - 1
        love.graphics.push()
        love.graphics.translate(i * Tile.kTILE_SIZE, j * Tile.kTILE_SIZE)
        room\draw()
        love.graphics.pop()

    drawPath: (path) =>
        size = #path
        for i = 1, size - 1
            {row, col} = path[i]
            {row2, col2} = path[i + 1]
            {x, y} = @tileToWorld(row, col)
            {x2, y2} = @tileToWorld(row2, col2)
            love.graphics.setColor(0, 0, 0)
            love.graphics.line(x, y, x2, y2)

    placeFloatingRoom: =>
        -- TODO: assert that floating room is valid
        assert(@floatingRoom)
        @floatingRoom\confirm()
        @addRoom(@floatingRoom)
        @floatingRoom = nil

    addRoom: (room) =>
        table.insert(@rooms, room)
        for tile in *room.tiles
            @tileGrid[tile.row][tile.col] = tile
        @updateNeighbors()

    worldToTile: (x, y) =>
        i = math.ceil(x / Tile.kTILE_SIZE)
        j = math.ceil(y / Tile.kTILE_SIZE)
        return {i, j}

    tileToWorld: (i, j) =>
        x = (i - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        y = (j - 1) * Tile.kTILE_SIZE + 0.5 * Tile.kTILE_SIZE
        return {x, y}

    getItems: (fromCrew=true, fromRooms=true) =>
        items = {}
        if fromCrew
            for minion in *@crew
                item = minion\getItem()
                if item
                    table.insert(items, item)
        if fromRooms
            for room in *@rooms
                for item in *room\getItems()
                    table.insert(items, item)
        return items

    updateNeighbors: =>
        for i=1,@kBASE_SIZE
            for j=1,@kBASE_SIZE
                tile = @tileGrid[i][j]
                if tile
                    if i < @kBASE_SIZE
                        tile.neighbors[Tile.kRIGHT] = @tileGrid[i + 1][j]
                    if i > 1
                        tile.neighbors[Tile.kLEFT] = @tileGrid[i - 1][j]
                    if j > 1
                        tile.neighbors[Tile.kUP] = @tileGrid[i][j - 1]
                    if j < @kBASE_SIZE
                        tile.neighbors[Tile.kDOWN] = @tileGrid[i][j + 1]

    pathFinding: (row1, col1, row2, col2) =>
        -- return a path (as a table of {row, col} coordinate) between two tiles
        -- Breadth-First
        assert @tileGrid[row1][col1] ~= nil and @tileGrid[row2][col2] ~= nil
        row, col = row1, col1
        tile_queue = {}
        marked = {} -- contains the previous tile of each tile reached
        in_marked = (r, c) -> return marked[r] ~= nil and marked[r][c] ~= nil
        mark_tile = (r, c, prevCoor) ->
            if marked[r] == nil then marked[r] = {}
            marked[r][c] = prevCoor
        -- breadth-first exhaustive search using a queue
        while row ~= row2 or col ~= col2
            assert @tileGrid[row][col] ~= nil
            tile = @tileGrid[row][col]
            for neigh in *tile.neighbors
                if neigh and not in_marked(neigh.row, neigh.col)
                    mark_tile(neigh.row, neigh.col, {tile.row, tile.col})
                    table.insert(tile_queue, neigh)
            if #tile_queue == 0
                return nil -- no path found
            nextTile = table.remove(tile_queue, 1)
            row, col = nextTile.row, nextTile.col
        assert row == row2 and col == col2, "#{row}, #{col}, #{row2}, #{col2}"
        -- retrace path back using marks
        path = {}
        table.insert(path, {row, col})
        loopCounter = 0
        while row ~= row1 and col ~= col1
            loopCounter += 1
            assert loopCounter <= @kBASE_SIZE * @kBASE_SIZE, "Infinite loop?"
            assert @tileGrid[row][col] ~= nil
            {row, col} = marked[row][col]
            table.insert(path, {row, col})
        table.insert(path, {row1, col1})
        return path


sylfest: Use lpeg instead of lua patterns

Former-commit-id: 506bd5bbfe58b2946928c9e0770386d9fec53748 [formerly 9882d504c8569aca3c3467f53ab5d941f0f086e6]
Former-commit-id: 03cc7ad98eaebda11ad2888f466c224f05c304e3

lpeg = require 'lpeg'
import match, locale, P, R, V, S, Ct, C, Cg, Cf, Cc, Cp from lpeg
import concat from table

-- Helper for searching anywhere in string for patterns
anywhere = (p) ->
  P{ p + 1 * V(1) }
-- Helper for maybe pattern
maybe = (p) -> p^-1
-- Match norwegian words
word = R('az') + R('AZ') + S('æøåÆØÅ')
-- With an '
apo = P "'"
-- With bad endings
bend = P("en") + P("ene") + P("et") + P("ing") + P("ar") + P("ane")
-- Separated by or line end
wend = S(',. ') + P(-1)
-- Linestart exceptions
bstart = maybe(P(' ')) * S[["'-]]

-- Capture word with bad endings followed by good word end
sylfestpatt = C(word^1 * apo * bend) * P(wend)
-- Search anywhere in string, capture all sylfestwords, do not match if line starts with excepted linestarts
fullpatt = Ct(anywhere(sylfestpatt)^1 - bstart)

-- run our handler on any message that contains '
PRIVMSG:
  ".*'.*": (source, destination, line) =>
    matches = fullpatt\match(line)
    if matches
      reply "Sylfest likar ikkje: %s", concat(matches, ', ')

trim = (s) ->
  string.match s, '^%s*(.*%S)' or ''

-- the words we want to match
word = "[%wæøåÆØÅ]+"

-- These are the endings we don't approve
badends = { "'en", "'ene", "'et", "'ing", "'ar", "'ane" }

-- No [$|%s] in lua patterns so we just construct two patterns
patternends = { '$', '%s', '%.', ',' }

-- Linestart exceptions
lineexceptions = { '^"', "^'", "^-", "^ %-" }

-- Construct all the patterns we need to check
patterns = {}
for wend in *badends
  for pend in *patternends
    table.insert patterns, word .. wend .. pend

-- Check every word against our bad patterns.
checkWord = (source, destination, line) =>
  -- First check for exceptions
  for pattern in *lineexceptions
    if line\match pattern
      -- Line is excepted.
      return

  out = {}
  for pattern in *patterns
    for word in line\gmatch pattern
      table.insert out, trim(word)

  if #out > 0
    @Msg 'privmsg', destination, source, "Sylfest likar ikkje: %s", table.concat(out, ', ')

-- run our handler on any message that contains '
PRIVMSG:
  ".*'.*": checkWord