/zoeplat

To get this branch, use:
bzr branch http://9ix.org/bzr/zoeplat
2 by Josh C
basic tiles, map, player, movement
1
STRICT = true
2
DEBUG = true
3
4
require 'zoetrope'
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
5
__ = require 'underscore'
12 by Josh C
only jump when you're on the ground
6
--inspect = require 'inspect'
24 by Josh C
profiling and analysis
7
require 'pepperprof'
31 by Josh C
command line option for playback/record
8
require 'getopt_alt'
2 by Josh C
basic tiles, map, player, movement
9
26 by Josh C
don't go off the edge
10
util = {
11
   dim = function(dir)
12
      if dir == 'x' then
13
         return 'width'
14
      elseif dir == 'y' then
15
         return 'height'
16
      else
29 by Josh C
record/playback system (doesn't really work)
17
         if STRICT then error('dir '..dir) end
26 by Josh C
don't go off the edge
18
      end
19
   end
20
}
21
10 by Josh C
make player an animation
22
Player = Animation:extend {
2 by Josh C
basic tiles, map, player, movement
23
   image = 'data/player.png',
10 by Josh C
make player an animation
24
   height = 32,
25
   width = 32,
26
   sequences = {
27
      stand = { frames = { 1 }, fps = 1 },
28
      walk = { frames = { 2, 3 }, fps = 5 },
22 by Josh C
climbing animation
29
      jump = { frames = { 4 }, fps = 1 },
30
      climbLeft = { frames = { 5, 6 }, fps = 5 },
31
      climbRight = { frames = { 7, 8 }, fps = 5 }
10 by Josh C
make player an animation
32
   },
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
33
   collisions = {},
34
   onWall = false,
23 by Josh C
wall jump
35
   leftWallAt = 0,
3 by Josh C
jump
36
   onNew = function (self)
37
              self.velocity.y = 0
5 by Josh C
use built-in maxVelocity system
38
              self.maxVelocity.y = 400
3 by Josh C
jump
39
           end,
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
40
   doPhysics = function (self, dir, elapsed)
41
                  local vel = self.velocity
42
                  local acc = self.acceleration
43
                  local drag = self.drag
44
                  local minVel = self.minVelocity
45
                  local maxVel = self.maxVelocity
46
47
                  -- check existence of properties
48
49
                  if STRICT then
50
                     assert(vel, 'active sprite has no velocity property')
51
                     assert(acc, 'active sprite has no acceleration property')
52
                     assert(drag, 'active sprite has no drag property')
53
                     assert(minVel, 'active sprite has no minVelocity property')
54
                     assert(maxVel, 'active sprite has no maxVelocity property')
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
55
                     assert(__.include({'x','y','rotation'}, dir), 'direction should be x, y, or rotation')
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
56
                  end
57
58
                  vel.x = vel.x or 0
59
                  vel.y = vel.y or 0
60
                  vel.rotation = vel.rotation or 0
61
62
                  -- physics
63
64
                  if acc[dir] and acc[dir] ~= 0 then
65
                     vel[dir] = vel[dir] + acc[dir] * elapsed
66
                  else
67
                     if drag[dir] then
68
                        if vel[dir] > 0 then
69
                           vel[dir] = vel[dir] - drag[dir] * elapsed
70
                           if vel[dir] < 0 then vel[dir] = 0 end
71
                        elseif vel[dir] < 0 then
72
                           vel[dir] = vel[dir] + drag[dir] * elapsed
73
                           if vel[dir] > 0 then vel[dir] = 0 end
74
                        end
75
                     end
76
                  end
77
78
                  if minVel[dir] and vel[dir] < minVel[dir] then vel[dir] = minVel[dir] end
79
                  if maxVel[dir] and vel[dir] > maxVel[dir] then vel[dir] = maxVel[dir] end
80
27 by Josh C
hack to not fall through floor on long first tick, monkey patch to turn
81
                  -- ugly hack for falling through floor on really slow frames
82
                  if math.abs(vel[dir] * elapsed) > 32 then
83
                     print('skip')
84
                     return
85
                  end
86
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
87
                  if vel[dir] ~= 0 then self[dir] = self[dir] + vel[dir] * elapsed end
26 by Josh C
don't go off the edge
88
89
                  if self[dir] < 0 then self[dir] = 0 end
90
                  local edge = the.view.map[util.dim(dir)] -
91
                               the.player[util.dim(dir)]
92
                  -- TODO: take map position into account
93
                  if self[dir] > edge then self[dir] = edge end
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
94
               end,
4 by Josh C
fix jitter caused by focus shift happening in the wrong order. Looks
95
   onStartFrame = function (self)
96
                     -- this is all in startframe so it happens before
97
                     -- physics calc at beginning of update
3 by Josh C
jump
98
12 by Josh C
only jump when you're on the ground
99
                     -- jumping/falling updates could go in EndFrame...
100
                     self.falling = self.velocity.y > 0
101
                     if self.falling then self.jumping = false end
102
                     --print(self.jumping, self.falling)
103
19 by Josh C
climb walls
104
                     if (not self.onGround) and (not self.onWall) then
13 by Josh C
reapply jump animation after Y collision. (there's a frame of no
105
                        self:play('jump')
106
                     end
107
6 by Josh C
whitespace cleanup
108
                     self.acceleration.y = 800
109
19 by Josh C
climb walls
110
                     if self.onWall then
111
                        self.acceleration.y = 0
112
22 by Josh C
climbing animation
113
                        if self.onWall == 'right' then
114
                           self:play('climbRight')
115
                        elseif self.onWall == 'left' then
116
                           self:play('climbLeft')
117
                        end
118
19 by Josh C
climb walls
119
                        if the.keys:pressed('up') then
120
                           self.velocity.y = -200
121
                        elseif the.keys:pressed('down') then
122
                           self.velocity.y = 200
123
                        else
124
                           self.velocity.y = 0
22 by Josh C
climbing animation
125
                           self:freeze(self.sequences[self.currentName].frames[1])
19 by Josh C
climb walls
126
                        end
127
                     end
128
6 by Josh C
whitespace cleanup
129
                     if the.keys:pressed('left') then
130
                        self.velocity.x = -200
15 by Josh C
more reliable onGround calc
131
                        if self.onGround then self:play('walk') end
23 by Josh C
wall jump
132
                        if self.onWall == 'right' then
133
                           self.onWall = false
134
                           self.leftWallAt = love.timer.getTime()
135
                        end
6 by Josh C
whitespace cleanup
136
                     elseif the.keys:pressed('right') then
137
                        self.velocity.x = 200
15 by Josh C
more reliable onGround calc
138
                        if self.onGround then self:play('walk') end
23 by Josh C
wall jump
139
                        if self.onWall == 'left' then
140
                           self.onWall = false
141
                           self.leftWallAt = love.timer.getTime()
142
                        end
10 by Josh C
make player an animation
143
                     else
21 by Josh C
really easy version of knowing when we reached the top of a wall.
144
                        if not self.onWall then
22 by Josh C
climbing animation
145
                           if self.onGround then self:play('stand') end
21 by Josh C
really easy version of knowing when we reached the top of a wall.
146
                           self.velocity.x = 0
147
                        end
6 by Josh C
whitespace cleanup
148
                     end
149
23 by Josh C
wall jump
150
                     if the.keys:justPressed('up') and
25 by Josh C
build more level
151
                      (self.onGround or the.console.visible or
23 by Josh C
wall jump
152
                       (love.timer.getTime() - self.leftWallAt < .1) ) then
6 by Josh C
whitespace cleanup
153
                        self.velocity.y = -400
12 by Josh C
only jump when you're on the ground
154
                        self.jumping = true
6 by Josh C
whitespace cleanup
155
                     end
156
                  end,
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
157
   update = function (self, elapsed)
158
               -- NOTE: this is an override, not a callback
159
160
               self:doPhysics('x', elapsed)
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
161
               self:collide(the.view.map)
162
163
               -- handle X collisions
21 by Josh C
really easy version of knowing when we reached the top of a wall.
164
               self.onWall = false
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
165
               for _, col in ipairs(self.collisions) do
166
                  col.other:displaceDir(self, 'x')
167
                  if self.velocity.x > 0 then
168
                     self.onWall = 'right'
169
                  elseif self.velocity.x < 0 then
170
                     self.onWall = 'left'
171
                  else
172
                     print 'x ??'
173
                  end
174
               end
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
175
176
               self.onGround = false -- right before Y collision callbacks
177
               self:doPhysics('y', elapsed)
178
               self:collide(the.view.map)
18 by Josh C
call Animation.update so we actually get animations
179
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
180
               -- handle Y collisions
181
               for _, col in ipairs(self.collisions) do
182
                  if self.velocity.y > 0 then
183
                     self.onGround = true
184
                  end
185
186
                  col.other:displaceDir(self, 'y')
187
                  self.velocity.y = 0
188
                  self.jumping = false
189
               end
190
30 by Josh C
playing with text (disabled)
191
               -- text blob
192
               if not self.text then
193
                  self.text = Text:new{wordWrap = true, width = 50, tint = {0,0,0}}
194
                  self.textfill = Fill:new{width = 54, border = {0,0,255}}
195
                  --the.view:add(self.textfill)
196
                  --the.view:add(self.text)
197
               end
198
               self.text.text = "Blah blah big text etc etc wrapping"
199
               self.text:centerAround(self.x+16, self.y+16, 'horizontal')
200
               _, texth = self.text:getSize()
201
               self.text.y = self.y - texth - 4
202
               self.textfill.x = self.text.x - 2
203
               self.textfill.y = self.text.y - 2
204
               self.textfill.height = texth + 4
205
18 by Josh C
call Animation.update so we actually get animations
206
               Animation.update(self, elapsed)
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
207
            end,
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
208
   collide = function (self, ...)
209
                self.collisions = {}
210
                Animation.collide(self, ...)
211
                -- I could return a true/false value here if I wanted to...
212
             end,
8 by Josh C
some basic collision (and workarounds)
213
   onCollide = function (self, other, xOverlap, yOverlap)
214
                  if other == the.view.map then return end
215
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
216
                  table.insert(self.collisions, {other = other,
217
                                                 xOverlap = xOverlap,
218
                                                 yOverlap = yOverlap })
219
               end
2 by Josh C
basic tiles, map, player, movement
220
}
221
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
222
-- displace on a specific axis (monkey patch Sprite)
223
function Sprite:displaceDir(other, dir)
224
   if not self.solid or self == other or not other.solid then return end
225
   if STRICT then assert(other:instanceOf(Sprite), 'asked to displace a non-sprite') end
226
227
   if other.sprites then
228
      -- handle groups
229
230
      for _, spr in pairs(other.sprites) do
231
         self:displace(spr, dir)
232
      end
233
   else
234
      -- handle sprites
26 by Josh C
don't go off the edge
235
      local dim = util.dim(dir)
20 by Josh C
fairly major overhaul of collision handling to track whether we're on a
236
237
      local negMove = (other[dir] - self[dir]) + other[dim]
238
      local posMove = (self[dir] + self[dim]) - other[dir]
239
240
      -- TODO: re-add hinting?
241
      if negMove < posMove then
242
         chg = - negMove
243
      else
244
         chg = posMove
245
      end
246
   end
247
248
   other[dir] = other[dir] + chg
249
end
250
27 by Josh C
hack to not fall through floor on long first tick, monkey patch to turn
251
-- don't use zoetrope physics
252
function Sprite:update (elapsed)
253
   if self.onUpdate then self:onUpdate(elapsed) end
254
end
255
2 by Josh C
basic tiles, map, player, movement
256
GameView = View:extend {
257
   onNew = function (self)
258
              self:loadLayers('data/map.lua')
259
              self.focus = the.player
260
              self:clampTo(self.map)
29 by Josh C
record/playback system (doesn't really work)
261
262
              the.recorder = Recorder:new{mousePosInterval = 9999}
263
              the.app.meta:add(the.recorder)
31 by Josh C
command line option for playback/record
264
              if the.app.record then
29 by Josh C
record/playback system (doesn't really work)
265
                 the.recorder:startRecording()
31 by Josh C
command line option for playback/record
266
              elseif the.app.playback then
29 by Josh C
record/playback system (doesn't really work)
267
                 local storage = Storage:new{filename = 'record.lua'}
268
                 storage:load()
269
                 --print(inspect(storage.data))
270
                 the.recorder.record = storage.data
271
                 the.recorder:startPlaying()
272
              end
8 by Josh C
some basic collision (and workarounds)
273
           end,
274
   onUpdate = function (self)
27 by Josh C
hack to not fall through floor on long first tick, monkey patch to turn
275
                 --print('drawTook: ', the.drawTook)
8 by Josh C
some basic collision (and workarounds)
276
                 --print('tick')
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
277
                 --the.player:collide(self.map)
8 by Josh C
some basic collision (and workarounds)
278
                 --self.map:collide(the.player)
28 by Josh C
fps indicator, maybe a new tile
279
              end,
29 by Josh C
record/playback system (doesn't really work)
280
   -- draw = function (self, x, y)
281
   --           View.draw(self, x, y)
28 by Josh C
fps indicator, maybe a new tile
282
29 by Josh C
record/playback system (doesn't really work)
283
   --           love.graphics.print('FPS:' .. love.timer.getFPS(), 20, 20)
284
   --        end
2 by Josh C
basic tiles, map, player, movement
285
}
286
287
the.app = App:new {
31 by Josh C
command line option for playback/record
288
   record = true,
2 by Josh C
basic tiles, map, player, movement
289
   onRun = function (self)
290
              self.view = GameView:new()
15 by Josh C
more reliable onGround calc
291
              self.console:watch('onGround', 'the.player.onGround')
16 by Josh C
try to track X collisions. break out Sprite's physics in prep for
292
              self.console:watch('onWall', 'the.player.onWall')
24 by Josh C
profiling and analysis
293
              self.console:watch('updateTook', 'the.updateTook')
294
              self.console:watch('drawTook', 'the.drawTook')
29 by Josh C
record/playback system (doesn't really work)
295
              self.console:watch('recorder state', 'the.recorder.state')
24 by Josh C
profiling and analysis
296
297
              --the.profiler = newProfiler('time', 2000)
298
              --the.profiler = newProfiler()
299
              --the.profiler:start()
2 by Josh C
basic tiles, map, player, movement
300
           end,
301
   onUpdate = function (self, dt)
24 by Josh C
profiling and analysis
302
                 if the.keys:justPressed('escape') then
303
                    if the.profiler then
304
                       the.profiler:stop()
305
                       local outfile = io.open( "profile.txt", "w+" )
306
                       the.profiler:report( outfile )
307
                       outfile:close()
308
                    end
309
31 by Josh C
command line option for playback/record
310
                    if self.record then
29 by Josh C
record/playback system (doesn't really work)
311
                       if not love.filesystem.remove('record.lua') then
31 by Josh C
command line option for playback/record
312
                          print('could not remove record.lua')
29 by Josh C
record/playback system (doesn't really work)
313
                       end
314
                       local storage = Storage:new{
315
                          data = the.recorder.record,
316
                          filename = 'record.lua'
317
                       }
318
                       storage:save(false)
319
                       --print(inspect(the.recorder.record))
320
                    end
321
17 by Josh C
reorganize code - separate X and Y physics so we can collide in each
322
                    self.quit()
12 by Josh C
only jump when you're on the ground
323
                 end
24 by Josh C
profiling and analysis
324
              end,
325
   update = function (self, dt)
326
               the.updateStart = love.timer.getMicroTime()
327
               App.update(self, dt)
328
               if the.updateStart then
329
                  the.updateTook = love.timer.getMicroTime() - the.updateStart
330
               end
331
            end
332
}
31 by Josh C
command line option for playback/record
333
334
function love.load (arg)
335
   opts = getopt(arg, '')
336
   if opts['p'] then
337
      the.app.playback = true
338
      the.app.record = false
339
   elseif opts['r'] then
340
      the.app.record = true
341
   end
342
343
   the.app:run()
344
end