├── props
├── praca.png
└── pixelTree.png
├── textures
├── ls.png
├── rd.png
├── rdl.png
├── rdr.png
├── rdrd.png
├── rdv.png
├── water.png
├── DirtTile.png
├── GrassTile.png
├── SelectTile.png
└── WaterTile.png
├── minimalDemo.lua
├── LICENSE
├── main.lua
├── JSONMap.json
├── conf.lua
├── README.md
├── isomap.lua
├── lovebird.lua
└── dkjson.lua
/props/praca.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/props/praca.png
--------------------------------------------------------------------------------
/textures/ls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/ls.png
--------------------------------------------------------------------------------
/textures/rd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/rd.png
--------------------------------------------------------------------------------
/textures/rdl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/rdl.png
--------------------------------------------------------------------------------
/textures/rdr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/rdr.png
--------------------------------------------------------------------------------
/textures/rdrd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/rdrd.png
--------------------------------------------------------------------------------
/textures/rdv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/rdv.png
--------------------------------------------------------------------------------
/textures/water.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/water.png
--------------------------------------------------------------------------------
/props/pixelTree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/props/pixelTree.png
--------------------------------------------------------------------------------
/textures/DirtTile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/DirtTile.png
--------------------------------------------------------------------------------
/textures/GrassTile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/GrassTile.png
--------------------------------------------------------------------------------
/textures/SelectTile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/SelectTile.png
--------------------------------------------------------------------------------
/textures/WaterTile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sulunia/isomap-love2d/HEAD/textures/WaterTile.png
--------------------------------------------------------------------------------
/minimalDemo.lua:
--------------------------------------------------------------------------------
1 | isomap = require ("isomap")
2 | function love.load()
3 | --Variables
4 | x = 0
5 | y = 0
6 |
7 | --Set background to deep blue
8 | love.graphics.setBackgroundColor(0, 0, 69)
9 |
10 | --Decode JSON map file
11 | isomap.decodeJson("JSONMap.json")
12 |
13 | --Generate map from JSON file (loads assets and creates tables)
14 | isomap.generatePlayField()
15 | end
16 |
17 | function love.update(dt)
18 | --Get player input so map moves around.
19 | if love.keyboard.isDown("left") then x = x + 900*dt end
20 | if love.keyboard.isDown("right") then x = x - 900*dt end
21 | if love.keyboard.isDown("up") then y = y+900*dt end
22 | if love.keyboard.isDown("down") then y = y-900*dt end
23 | end
24 |
25 | function love.draw()
26 | isomap.draw(x, y, 1)
27 | end
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Pedro Polez
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/main.lua:
--------------------------------------------------------------------------------
1 | isomap = require ("isomap")
2 | function love.load()
3 | --Variables
4 | x = 0
5 | y = 0
6 | zoomL = 1
7 | zoom = 1
8 |
9 | --Set background to deep blue
10 | love.graphics.setBackgroundColor(0, 0, 69)
11 | love.graphics.setDefaultFilter("linear", "linear", 8)
12 |
13 |
14 | --Decode JSON map file
15 | isomap.decodeJson("JSONMap.json")
16 |
17 | --Generate map from JSON file (loads assets and creates tables)
18 | isomap.generatePlayField()
19 | end
20 |
21 | function love.update(dt)
22 | require("lovebird").update()
23 | if love.keyboard.isDown("left") then x = x + 900*dt end
24 | if love.keyboard.isDown("right") then x = x - 900*dt end
25 | if love.keyboard.isDown("up") then y = y+900*dt end
26 | if love.keyboard.isDown("down") then y = y-900*dt end
27 | zoomL = lerp(zoomL, zoom, 0.05*(dt*300))
28 | end
29 |
30 | function love.draw()
31 | isomap.drawGround(x, y, zoomL)
32 | isomap.drawObjects(x, y, zoomL)
33 | info = love.graphics.getStats()
34 | love.graphics.print("FPS: "..love.timer.getFPS())
35 | love.graphics.print("Draw calls: "..info.drawcalls, 0, 12)
36 | love.graphics.print("Texture memory: "..((info.texturememory/1024)/1024).."mb", 0, 24)
37 | love.graphics.print("Zoom level: "..zoom, 0, 36)
38 | love.graphics.print("X: "..math.floor(x).." Y: "..math.floor(y), 0, 48)
39 | end
40 |
41 | function love.wheelmoved(x, y)
42 | if y > 0 then
43 | zoom = zoom + 0.1
44 | elseif y < 0 then
45 | zoom = zoom - 0.1
46 | end
47 |
48 | if zoom < 0.1 then zoom = 0.1 end
49 | end
50 |
51 | function lerp(a, b, rate) --EMPLOYEE OF THE MONTH
52 | local result = (1-rate)*a + rate*b
53 | return result
54 | end
55 |
--------------------------------------------------------------------------------
/JSONMap.json:
--------------------------------------------------------------------------------
1 | {
2 | "textures":
3 | [
4 | {
5 | "file": "ls.png",
6 | "mnemonic": "grass"
7 | },
8 | {
9 | "file": "rd.png",
10 | "mnemonic": "road"
11 | },
12 | {
13 | "file":"rdl.png",
14 | "mnemonic":"roadL"
15 | },
16 | {
17 | "file":"rdv.png",
18 | "mnemonic":"roadV"
19 | },
20 | {
21 | "file":"rdr.png",
22 | "mnemonic":"roadR"
23 | },
24 | {
25 | "file":"rdrd.png",
26 | "mnemonic":"roadD"
27 | },
28 | {
29 | "file":"water.png",
30 | "mnemonic":"water"
31 | }
32 | ],
33 |
34 | "props":
35 | [
36 | {
37 | "file": "pixelTree.png",
38 | "mnemonic": "tree",
39 | "origin" : "72|245"
40 | },
41 | {
42 | "file":"praca.png",
43 | "mnemonic":"post",
44 | "origin":"15|65"
45 | }
46 | ],
47 |
48 | "data":
49 | [
50 | [["water"],["water"],["water"],["water"],["water"],["water"],["water"],["water"],["water"],["water"],["water"],["water"],["water"],["water"],["water"]]
51 | [["grass", "tree"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass", "tree"],["grass"],["grass"],["grass"],["grass"]]
52 | [["grass"],["grass", "tree"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"]]
53 | [["grass"],["grass"],["roadD"],["road"], ["road"], ["road"], ["road"], ["road"], ["road"], ["road"], ["road"], ["roadR"],["grass"],["grass"],["grass"]]
54 | [["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass", "post"],["grass"],["roadV"],["grass"],["grass", "tree"],["grass"]]
55 | [["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"]]
56 | [["grass"],["grass"],["roadV"],["grass"],["grass"],["grass", "tree"],["grass"],["grass"],["grass"],["grass"],["grass"],["roadV"],["grass"],["grass"],["grass", "tree"]]
57 | [["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass", "tree"],["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"]]
58 | [["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"]]
59 | [["grass", "tree"],["grass"],["roadV"],["grass", "tree"],["grass"],["grass"],["grass"],["grass", "tree"],["grass"],["grass"],["grass"],["roadV"],["grass"],["grass", "tree"],["grass"]]
60 | [["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"]]
61 | [["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass", "tree"],["grass"],["roadV"],["grass"],["grass"],["grass"]]
62 | [["grass"],["grass"],["roadV"],["grass"],["grass"],["grass", "tree"],["grass"],["grass"],["grass"],["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"]]
63 | [["grass", "tree"],["grass"],["roadV"],["grass"],["grass"],["grass"],["grass"],["grass", "tree"],["grass"],["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"]]
64 | [["grass"],["grass"],["roadV"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["grass"],["roadV"],["grass"],["grass"],["grass", "tree"]]
65 | [["grass"],["grass"],["roadV"],["grass", "tree"],["grass"],["grass"],["grass"],["grass", "tree"],["grass"],["grass"],["grass"],["roadV"],["grass"],["grass", "tree"],["grass"]]
66 | ],
67 |
68 | "general":
69 | [
70 | {
71 | "name":"Simple road",
72 | "version":"v0.1",
73 | "lighting":"255|255|255"
74 | }
75 | ]
76 | }
--------------------------------------------------------------------------------
/conf.lua:
--------------------------------------------------------------------------------
1 | function love.conf(t)
2 | t.identity = nil -- The name of the save directory (string)
3 | t.version = "0.10.1" -- The L�VE version this game was made for (string)
4 | t.console = true -- Attach a console (boolean, Windows only)
5 | t.accelerometerjoystick = true -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean)
6 | t.externalstorage = false -- True to save files (and read from the save directory) in external storage on Android (boolean)
7 | t.gammacorrect = false -- Enable gamma-correct rendering, when supported by the system (boolean)
8 |
9 | t.window.title = "IsoMap" -- The window title (string)
10 | t.window.icon = nil -- Filepath to an image to use as the window's icon (string)
11 | t.window.width = 800 -- The window width (number)
12 | t.window.height = 600 -- The window height (number)
13 | t.window.borderless = false -- Remove all border visuals from the window (boolean)
14 | t.window.resizable = true -- Let the window be user-resizable (boolean)
15 | t.window.minwidth = 1 -- Minimum window width if the window is resizable (number)
16 | t.window.minheight = 1 -- Minimum window height if the window is resizable (number)
17 | t.window.fullscreen = false -- Enable fullscreen (boolean)
18 | t.window.fullscreentype = "desktop" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string)
19 | t.window.vsync = false -- Enable vertical sync (boolean)
20 | t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number)
21 | t.window.display = 1 -- Index of the monitor to show the window in (number)
22 | t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean)
23 | t.window.x = nil -- The x-coordinate of the window's position in the specified display (number)
24 | t.window.y = nil -- The y-coordinate of the window's position in the specified display (number)
25 |
26 | t.modules.audio = true -- Enable the audio module (boolean)
27 | t.modules.event = true -- Enable the event module (boolean)
28 | t.modules.graphics = true -- Enable the graphics module (boolean)
29 | t.modules.image = true -- Enable the image module (boolean)
30 | t.modules.joystick = true -- Enable the joystick module (boolean)
31 | t.modules.keyboard = true -- Enable the keyboard module (boolean)
32 | t.modules.math = true -- Enable the math module (boolean)
33 | t.modules.mouse = true -- Enable the mouse module (boolean)
34 | t.modules.physics = true -- Enable the physics module (boolean)
35 | t.modules.sound = true -- Enable the sound module (boolean)
36 | t.modules.system = true -- Enable the system module (boolean)
37 | t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update
38 | t.modules.touch = true -- Enable the touch module (boolean)
39 | t.modules.video = true -- Enable the video module (boolean)
40 | t.modules.window = true -- Enable the window module (boolean)
41 | t.modules.thread = true -- Enable the thread module (boolean)
42 | end
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # isomap-love2d
2 | Isometric map engine/library for Love2D
3 |
4 |
5 |
6 | ## What's this?
7 | This is a simple isometric map decoder and renderer. It can load maps with information stored on a JSON file and render the isometric map to the screen with a simple function call and a few parameters.
8 | The example uses mouse wheel to zoom in and zoom out, and arrow keys to move the map around.
9 |
10 | ## What now?
11 | I'll keep on adding things, such as elevation and maybe programmable tiles. I'll make a nice level editor too if needed. Won't be fast though.
12 |
13 | ### Modules used:
14 | * [DKJson](https://github.com/LuaDist/dkjson) for json parsing. Used by the library itself.
15 | * [Lovebird](https://github.com/rxi/lovebird) since it helps wonders with table content viewing. Used in the demo included.
16 | * [Kenney's isometric sprites](https://kenney.nl/) since I suck at drawing. Used in the demo.
17 | * [segfaultd](https://github.com/danielpontello) for drawing an example notice board, can be seen in the demo.
18 |
19 | # Great, but how do I use it?
20 | * First, create a JSON file, a "props" new folder, and a "textures" new folder in the root of your love project. Your project will look like this:
21 |
22 |
23 | * Add all textures you want to use for the ground layer to the *"textures"* folder. They **need** to have absolutely the same dimensions.
24 |
25 | * Add all prop textures you will place on your map to the *"props"* folder. These can be any dimensions you'd like.
26 |
27 | And you should be set up to create everything you need.
28 |
29 | # JSON map structure
30 | The basic JSON map will look a bit like this:
31 | ```JSON
32 | {
33 | "textures":
34 | [
35 | {
36 | "file": "myTexture.*",
37 | "mnemonic": "myTexture"
38 | }
39 | ],
40 |
41 | "props":
42 | [
43 | {
44 | "file": "myProp.*",
45 | "mnemonic": "myProp",
46 | "origin" : "50|72"
47 | }
48 | ],
49 |
50 | "data":
51 | [
52 | [["myTexture"],["myTexture"],["myTexture"]],
53 | [["myTexture"],["myTexture", "myProp"],["myTexture"]],
54 | [["myTexture"],["myTexture"],["myTexture"]]
55 | ],
56 |
57 | "general":
58 | [
59 | {
60 | "name":"Map name",
61 | "version":"string with map version",
62 | "lighting":"255|255|255"
63 | }
64 | ]
65 | }
66 | ```
67 | Lets analyze it's parts.
68 |
69 | ### Textures
70 | ```JSON
71 | "textures":
72 | [
73 | {
74 | "file": "myTexture.png",
75 | "mnemonic": "myTexture"
76 | },
77 | {
78 | "file": "myTexture2.jpg",
79 | "mnemonic": "myTexture2"
80 | },
81 | {
82 | "And so on":"for all textures you'll use"
83 | }
84 | ],
85 | ```
86 | This is where you'll load your ground layer textures. It has 2 fields:
87 | * name: Specifies the texture filename. Can be any kind of image file Love loads. Examples are .PNG and .JPG files.
88 | * mnemonic: Specifies a more friendly name given to the texture. Used in the map matrix. (See below).
89 |
90 | **All ground textures need to have the same exact dimensions!**
91 |
92 | ### Props
93 | ```JSON
94 | "textures":
95 | [
96 | {
97 | "file": "myProp.png",
98 | "mnemonic": "myProp",
99 | "origin": "X|Y"
100 | },
101 | {
102 | "file": "myProp2.jpg",
103 | "mnemonic": "myProp2"
104 | "origin": "X|Y"
105 | },
106 | {
107 | "And so on":"for all props you'll use"
108 | }
109 | ],
110 | ```
111 | This is where you'll load your map props or objects. It has 3 fields:
112 | * name: Specifies the texture used in the prop. Can be any kind of image file Love loads.
113 | * mnemonic: Specifies a more friendly name given to the prop. Used in the map matrix. (See below).
114 | * origin: Specifies the offset used to draw the prop on the tile where it should be. Values should be X and Y separated by a pipe ("|"). To find out this offset, use any image editor you want! Then you modify your values until you can get it right.
115 |
116 | Prop textures can be any size you want.
117 |
118 | ### Data
119 | ```JSON
120 | "data":
121 | [
122 | [["myTexture"],["myMnemonic"],["myTexture"]],
123 | [["myTexture"],["myTexture", "myProp"],["myTexture"]],
124 | [["myTexture"],["myTexture"],["myTexture"]]
125 | ],
126 | ```
127 | Pretty straightforward. Use the *mnemonics* you defined for your props and textures above and put then into a JSON matrix. I'm not sure, but I think only one prop can be placed per tile. In this example, a map with a 3x3 size would be rendered with texture *myTexture*, and *myProp* prop would be placed in the middle tile[2][2]. Obviously, larger data matrixes will yield bigger maps.
128 |
129 | ### General
130 | ```JSON
131 | "general":
132 | [
133 | {
134 | "name":"Map name",
135 | "version":"string with map version",
136 | "lighting":"R|G|B"
137 | }
138 | ]
139 | ```
140 | Specifies general map information, as well as other extras.
141 | * name: Specifies the map name.
142 | * version: Specifies a map version.
143 | * lighting: A small trick. Values are RGB values separated by a pipe ("|"). Defines the color you want to use to draw your map. If you specify, say, a dark blue, it'll give the map a blue tone, faking a night environment. Be creative! Set this value to "255|255|255" if you don't want to use this feature.
144 |
145 | # Isomap methods
146 | ## isomap.decodeJson(filename)
147 | Decodes a given JSON file.
148 |
149 | *Arguments:*
150 | * filename: The JSON filepath.
151 |
152 | *Example*:
153 | ```Lua
154 | isomap.decodeJson("Jsonfile.json")
155 | ```
156 | ## isomap.generatePlayfield()
157 | Generates the playfield with information provided by the preovously loaded JSON file.
158 |
159 | *No arguments.*
160 |
161 | *Example:*
162 | ```Lua
163 | isomap.generatePlayfield()
164 | ```
165 | ## isomap.drawGround(x, y, zoomLevel)
166 | Draws the map with a *X* and a *Y* offset scaled by *zoomLevel*.
167 |
168 | *Arguments:*
169 | * **x** and **y**: X and Y offset for map drawing.
170 | * zoomLevel: a number specifiying the scale the map should be rendered. Used primarily for zooming in and out.
171 |
172 | *Example:*
173 | ```Lua
174 | isomap.drawGround(300, 200, 1.5)
175 | ```
176 |
177 | ## isomap.drawObjects(x, y, zoomLevel)
178 | Sorts ZBuffer and draws objects on the map with a *X* and a *Y* offset scaled by *zoomLevel*.
179 |
180 | *Arguments:*
181 | * **x** and **y**: X and Y offset for object drawing.
182 | * zoomLevel: a number specifiying the scale the objects should be rendered. Used primarily for zooming in and out.
183 |
184 | *Example:*
185 | ```Lua
186 | isomap.drawObjects(300, 200, 1.5)
187 | ```
188 | ## isomap.toIso(x, y)
189 | Converts *x* and *y* from cartesian (2D) to isometric coordinates.
190 |
191 | *Arguments:*
192 | * **x** and **y**: X and Y values to be converted.
193 |
194 | *Example:*
195 | ```Lua
196 | isomap.toIso(420, 350)
197 | ```
198 | ## isomap.toCartesian(x, y)
199 | Converts *x* and *y* from isometric to cartesian (2D) coordinates.
200 |
201 | *Arguments:*
202 | * **x** and **y**: X and Y values to be converted.
203 |
204 | *Example:*
205 | ```Lua
206 | isomap.toCartesian(5, 8)
207 | ```
208 | # Minimal usage example
209 | ```Lua
210 | isomap = require ("isomap")
211 | function love.load()
212 | --Variables
213 | x = 0
214 | y = 0
215 |
216 | --Set background to deep blue
217 | love.graphics.setBackgroundColor(0, 0, 69)
218 |
219 | --Decode JSON map file
220 | isomap.decodeJson("JSONMap.json")
221 |
222 | --Generate map from JSON file (loads assets and creates tables)
223 | isomap.generatePlayField()
224 | end
225 |
226 | function love.update(dt)
227 | --Get player input so map moves around.
228 | if love.keyboard.isDown("left") then x = x + 900*dt end
229 | if love.keyboard.isDown("right") then x = x - 900*dt end
230 | if love.keyboard.isDown("up") then y = y+900*dt end
231 | if love.keyboard.isDown("down") then y = y-900*dt end
232 | end
233 |
234 | function love.draw()
235 | isomap.drawGround(x, y, 1)
236 | isomap.drawObjects(x, y, 1)
237 | end
238 |
239 | ```
240 |
241 | # Personal recommendations
242 | I recommend using [rxi's autobatch](https://github.com/rxi/autobatch) library to reduce draw calls made by this library to draw your map!
243 |
244 | # Contact
245 | Rants, questions, suggestions and anything else, [drop me an email here](mailto:pedrorocha@gec.inatel.br).
246 |
--------------------------------------------------------------------------------
/isomap.lua:
--------------------------------------------------------------------------------
1 | --[[MIT License
2 |
3 | Copyright (c) 2016 Pedro Polez
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.]]--
22 |
23 | local json = require("dkjson")
24 | --TODO: Load dkjson relative to mapDecoder's path.
25 |
26 |
27 | map = {}
28 | mapDec = {}
29 | local mapTextures = {}
30 | mapPositions = {}
31 | mapProps = {}
32 | local mapLighting = {}
33 | mapPropsfield = {}
34 | local tileWidth = 0
35 | local tileHeight = 0
36 |
37 | local mapPlayfieldWidthInTiles = 0
38 | local mapPlayfieldHeightInTiles = 0
39 |
40 | local objectListSize = 0
41 |
42 | local zoomLevel = 1
43 |
44 | function map.decodeJson(filename)
45 | assert(filename, "Filename is nil!")
46 | if not love.filesystem.isFile(filename) then error("Given filename is not a file! Is it a directory? Does it exist?") end
47 |
48 | --Reads file
49 | mapJson = love.filesystem.read(filename)
50 |
51 | --Attempts to decode file
52 | mapDec = json.decode(mapJson)
53 |
54 | end
55 |
56 |
57 | function map.generatePlayField()
58 | --TODO: Maps will be packed as renamed ZIP file extensios and will be able to be installed in users machines. So, textures and props have to be loaded from this directory.
59 | --Currently, the mapDecoder will look for textures in folder named textures in the root of the project, and props in a props folder.
60 |
61 | print("Current map information:")
62 | print("General information: =-=-=-=-=-=-=")
63 | if mapDec.general ~= nil then
64 | print("Map name: "..mapDec.general[1].name)
65 | print("Map version: "..mapDec.general[1].version)
66 | print("Map lighting: "..mapDec.general[1].lighting)
67 | if mapDec.general[1].lighting ~= nil then
68 | mapLighting = string.split_(mapDec.general[1].lighting, "|")
69 | end
70 | print("----")
71 | end
72 |
73 | print("Ground textures: =-=-=-=-=-=-=-=")
74 | for i, texture in ipairs(mapDec.textures) do
75 | --Print table contents for now
76 | print(texture.file)
77 | print(texture.mnemonic)
78 | print("---")
79 |
80 | table.insert(mapTextures, {file = texture.file, mnemonic = texture.mnemonic, image = love.graphics.newImage("textures/"..texture.file)})
81 |
82 | end
83 |
84 | --Get ground texture dimensions
85 | tileWidth = mapTextures[1].image:getWidth()/2
86 | tileHeight = mapTextures[1].image:getHeight()/2
87 |
88 | print("Playfield props: =-=-=-=-=-=-=-=")
89 | if mapDec.props ~= nil then
90 | for i, props in ipairs(mapDec.props) do
91 | print(props.file)
92 | print(props.mnemonic)
93 | print(props.origin)
94 | print("----")
95 | table.insert(mapProps, {file = props.file, mnemonic = props.mnemonic, image = love.graphics.newImage("props/"..props.file), origins = string.split_(props.origin, "|")})
96 | end
97 | else
98 | print("No props found on current map!")
99 | end
100 |
101 |
102 | --Add each ground tile to a table according to their texture
103 | --TODO: the following should be done on a separate thread. I have not tested the performance of the following lines on a colossal map.
104 | timerStart = love.timer.getTime()
105 | for i, groundTexture in ipairs(mapTextures) do
106 | for colunas in ipairs(mapDec.data) do
107 | for linhas in ipairs(mapDec.data[colunas]) do
108 | for i, properties in ipairs(mapDec.data[colunas][linhas]) do
109 |
110 | --Add ground texture if mnemonic is found
111 | if properties == groundTexture.mnemonic then
112 | local xPos = linhas
113 | local yPos = colunas
114 | if mapPositions[colunas] == nil then
115 | mapPositions[colunas] = {}
116 | end
117 | if mapPositions[colunas][linhas] == nil then
118 | mapPositions[colunas][linhas] = {}
119 | end
120 | table.insert(mapPositions[colunas][linhas], {texture = groundTexture.image, x=xPos, y=yPos})
121 | end
122 |
123 | end
124 | end
125 | end
126 | end
127 |
128 | --TODO: Merge these loops, since both save stuff to the same table?
129 | --Add object to map accordingly
130 | for i, props in ipairs(mapProps) do --For each object
131 |
132 | --Loop through map terrain information
133 | for colunas in ipairs(mapDec.data) do
134 | for linhas in ipairs(mapDec.data[colunas]) do
135 |
136 | --Iterate over the objects in a given 2D position
137 | for i, objects in ipairs(mapDec.data[colunas][linhas]) do
138 | if objects == props.mnemonic then
139 | --table.insert(mapPositions[colunas][linhas], {texture=props.image, x=linhas, y=colunas, offX=props.origins[1], offY=props.origins[2]})
140 |
141 | --VERY IMPORTANT NOTE ABOUT THE FOLLOWING LINES
142 | --these control the ZBuffer in some *dark manner*. IT WORKS. I **really** have to figure out why.
143 | pX, pY = map.toIso(linhas, colunas)
144 |
145 | colX = linhas * (tileWidth*zoomLevel)
146 | colY = colunas * (tileWidth*zoomLevel)
147 | colX, colY = map.toIso(colX, colY)
148 | table.insert(mapPropsfield,{texture=props.image, x=linhas, y=colunas, offX=props.origins[1], offY=props.origins[2], mapY = pY, mapX = pX, colX = colX, colY = colY, width = props.image:getWidth(), height = props.image:getHeight(), alpha = false})
149 | end
150 | end
151 |
152 | end
153 | end
154 |
155 | end
156 | --Calculate map dimensions
157 | mapPlayfieldWidthInTiles = #mapPositions
158 | mapPlayfieldHeightInTiles = #mapPositions[1]
159 |
160 | --Store map original object list size without any extra dynamic objects
161 | objectListSize = #mapPropsfield
162 |
163 | timerEnd = love.timer.getTime()
164 | print("Decode loop took "..((timerEnd-timerStart)*100).."ms")
165 |
166 | end
167 |
168 | function map.drawGround(xOff, yOff, size)
169 | assert(xOff)
170 | assert(yOff)
171 | assert(size)
172 | zoomLevel = size
173 | --Apply lighting
174 | love.graphics.setColor(tonumber(mapLighting[1]), tonumber(mapLighting[2]), tonumber(mapLighting[3]), 255)
175 |
176 | --Draw the flat ground layer for the map, without elevation or props.
177 | for i in ipairs(mapPositions) do
178 | for j=1,#mapPositions[i], 1 do
179 | local xPos = mapPositions[i][j][1].x * (tileWidth*zoomLevel)
180 | local yPos = mapPositions[i][j][1].y * (tileWidth*zoomLevel)
181 | local xPos, yPos = map.toIso(xPos, yPos)
182 | love.graphics.draw(mapPositions[i][j][1].texture,xPos+xOff, yPos+yOff, 0, size, size, mapPositions[i][j][1].texture:getWidth()/2, mapPositions[i][j][1].texture:getHeight()/2 )
183 | end
184 | end
185 |
186 | end
187 |
188 | function map.drawObjects(xOff, yOff, size)
189 |
190 | --Figure out dynamic object occlusion
191 | if #mapPropsfield > objectListSize then
192 | for i=objectListSize+1, #mapPropsfield do
193 | for j=1, objectListSize do
194 | if CheckCollision(mapPropsfield[j].colX, mapPropsfield[j].colY, mapPropsfield[j].width, mapPropsfield[j].height, mapPropsfield[i].colX, mapPropsfield[i].colY, mapPropsfield[i].width, mapPropsfield[i].height) and mapPropsfield[i].y < mapPropsfield[j].y and mapPropsfield[i].x < mapPropsfield[j].x then
195 | mapPropsfield[j].alpha = true
196 | end
197 | end
198 | end
199 | end
200 |
201 | --Sort ZBuffer and draw objects.
202 | for k,v in spairs(mapPropsfield, function(t,a,b) return t[b].mapY > t[a].mapY end) do
203 | local xPos = v.x * (tileWidth*zoomLevel)
204 | local yPos = v.y * (tileWidth*zoomLevel)
205 | local xPos, yPos = map.toIso(xPos, yPos)
206 |
207 | if v.alpha then
208 | love.graphics.setColor(255, 255, 255, 90)
209 | else
210 | love.graphics.setColor(255, 255, 255, 255)
211 | end
212 | love.graphics.draw(v.texture, xPos+xOff, yPos+yOff, 0, size, size, v.offX, v.offY)
213 |
214 | --Update values in order to minimize for loops
215 | v.alpha = false
216 | v.colX = xPos-v.offX
217 | v.colY = yPos-v.offY
218 | v.mapX, v.mapY = map.toIso(v.x, v.y)
219 | end
220 | end
221 |
222 |
223 | function map.getTileCoordinates2D(i, j)
224 | local xP = mapPositions[i][j][1].x * (tileWidth*zoomLevel)
225 | local yP = mapPositions[i][j][1].y * (tileWidth*zoomLevel)
226 | xP, yP = map.toIso(xP, yP)
227 | return xP, yP
228 | end
229 |
230 | function map.getPlayfieldWidth()
231 | return mapPlayfieldWidthInTiles
232 | end
233 |
234 | function map.getPlayfieldHeight()
235 | return mapPlayfieldHeightInTiles
236 | end
237 |
238 | function map.getGroundTileWidth()
239 | return tileWidth
240 | end
241 |
242 | --Links used whilst searching for information on isometric maps:
243 | --http://stackoverflow.com/questions/892811/drawing-isometric-game-worlds
244 | --https://gamedevelopment.tutsplus.com/tutorials/creating-isometric-worlds-a-primer-for-game-developers--gamedev-6511
245 | --Give it a good read if you don't understand whats happening over here.
246 |
247 | function map.toIso(x, y)
248 | assert(x, "Position X is nil!")
249 | assert(y, "Position Y is nil!")
250 |
251 | newX = x-y
252 | newY = (x + y)/2
253 | return newX, newY
254 | end
255 |
256 | function map.toCartesian(x, y)
257 | assert(x, "Position X is nil!")
258 | assert(y, "Position Y is nil!")
259 | x = (2 * y + x)/2
260 | y = (2 * y - x)/2
261 | return x, y
262 | end
263 |
264 | function map.insertNewObject(textureI, isoX, isoY, offXR, offYR)
265 | --User checks
266 | if offXR == nil then offXR = 0 end
267 | if offYR == nil then offYR = 0 end
268 | assert(textureI, "Invalid texture file for object!")
269 | assert(isoX, "No X position for object! (Isometric coordinates)")
270 | assert(isoY, "No Y position for object! (Isometric coordinates)")
271 | assert(mapPlayfieldWidthInTiles>=isoX, "Insertion coordinates out of map bounds! (X)")
272 | assert(mapPlayfieldWidthInTiles>=isoY, "Insertion coordinates out of map bounds! (Y)")
273 | local rx, ry = map.toIso(isoX, isoY)
274 |
275 | local colX = isoX * (tileWidth*zoomLevel)
276 | local colY = isoY * (tileWidth*zoomLevel)
277 | colX, colY = map.toIso(colX, colY)
278 | --Insert object on map
279 | table.insert(mapPropsfield, {texture=textureI, x=isoY, y=isoX+0.001, offX=offXR, offY = offYR, mapY = ry, mapX = rx, colX = colX, colY = colY, width = textureI:getWidth(), height = textureI:getHeight(), alpha = false})
280 | end
281 |
282 | function map.removeObject(x, y)
283 | if #mapPositions[x][y] > 1 then
284 | table.remove(mapPositions[x][y], #mapPositions[x][y])
285 | end
286 | end
287 |
288 |
289 | --This next function had the underscore added to avoid collisions with
290 | --any other possible split function the user may want to use.
291 | function string:split_(sSeparator, nMax, bRegexp)
292 | assert(sSeparator ~= '')
293 | assert(nMax == nil or nMax >= 1)
294 |
295 | local aRecord = {}
296 |
297 | if self:len() > 0 then
298 | local bPlain = not bRegexp
299 | nMax = nMax or -1
300 |
301 | local nField, nStart = 1, 1
302 | local nFirst,nLast = self:find(sSeparator, nStart, bPlain)
303 | while nFirst and nMax ~= 0 do
304 | aRecord[nField] = self:sub(nStart, nFirst-1)
305 | nField = nField+1
306 | nStart = nLast+1
307 | nFirst,nLast = self:find(sSeparator, nStart, bPlain)
308 | nMax = nMax-1
309 | end
310 | aRecord[nField] = self:sub(nStart)
311 | end
312 |
313 | return aRecord
314 | --Credit goes to JoanOrdinas @ lua-users.org
315 | end
316 |
317 | function spairs(t, order)
318 | -- collect the keys
319 | local keys = {}
320 | for k in pairs(t) do keys[#keys+1] = k end
321 |
322 | -- if order function given, sort by it by passing the table and keys a, b,
323 | -- otherwise just sort the keys
324 | if order then
325 | table.sort(keys, function(a,b) return order(t, a, b) end)
326 | else
327 | table.sort(keys)
328 | end
329 |
330 | -- return the iterator function
331 | local i = 0
332 | return function()
333 | i = i + 1
334 | if keys[i] then
335 | return keys[i], t[keys[i]]
336 | end
337 | end
338 | --https://stackoverflow.com/questions/15706270/sort-a-table-in-lua
339 | --Function "spairs" by Michal Kottman.
340 | end
341 |
342 | -- Collision detection function;
343 | -- Returns true if two boxes overlap, false if they don't;
344 | -- x1,y1 are the top-left coords of the first box, while w1,h1 are its width and height;
345 | -- x2,y2,w2 & h2 are the same, but for the second box.
346 | function CheckCollision(x1,y1,w1,h1, x2,y2,w2,h2)
347 | return x1 < x2+w2 and
348 | x2 < x1+w1 and
349 | y1 < y2+h2 and
350 | y2 < y1+h1
351 | end
352 |
353 | return map
354 |
--------------------------------------------------------------------------------
/lovebird.lua:
--------------------------------------------------------------------------------
1 | --
2 | -- lovebird
3 | --
4 | -- Copyright (c) 2017 rxi
5 | --
6 | -- This library is free software; you can redistribute it and/or modify it
7 | -- under the terms of the MIT license. See LICENSE for details.
8 | --
9 |
10 | local socket = require "socket"
11 |
12 | local lovebird = { _version = "0.4.2" }
13 |
14 | lovebird.loadstring = loadstring or load
15 | lovebird.inited = false
16 | lovebird.host = "*"
17 | lovebird.buffer = ""
18 | lovebird.lines = {}
19 | lovebird.connections = {}
20 | lovebird.pages = {}
21 |
22 | lovebird.wrapprint = true
23 | lovebird.timestamp = true
24 | lovebird.allowhtml = false
25 | lovebird.echoinput = true
26 | lovebird.port = 8000
27 | lovebird.whitelist = { "127.0.0.1" }
28 | lovebird.maxlines = 200
29 | lovebird.updateinterval = .5
30 |
31 |
32 | lovebird.pages["index"] = [[
33 |
47 |
48 |
49 |
50 |