1-- GunFu Deadlands 2-- Copyright 2009-2011 Christiaan Janssen, September 2009-October 2011 3-- 4-- This file is part of GunFu Deadlands. 5-- 6-- GunFu Deadlands is free software: you can redistribute it and/or modify 7-- it under the terms of the GNU General Public License as published by 8-- the Free Software Foundation, either version 3 of the License, or 9-- (at your option) any later version. 10-- 11-- GunFu Deadlands is distributed in the hope that it will be useful, 12-- but WITHOUT ANY WARRANTY; without even the implied warranty of 13-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14-- GNU General Public License for more details. 15-- 16-- You should have received a copy of the GNU General Public License 17-- along with GunFu Deadlands. If not, see <http://www.gnu.org/licenses/>. 18 19Movement = {} 20 21 22 23function Movement.update_character( dt, who ) 24 -- just for convenience, move to the upperleft corner now, and back to the center at the end 25 if who.dir[1]==0 and who.dir[2]==0 then 26 return 27 end 28 29 local newpos = { 30 who.pos[1] + who.dir[1] * dt * who.speed, 31 who.pos[2] + who.dir[2] * dt * who.speed 32 } 33 34 35 local hl,hr,hu,hd = mymath.round(who.spritesize[1]/2-who.collisionbox[1]), 36 mymath.round(who.spritesize[1]/2-(who.spritesize[1]-who.collisionbox[3])), 37 mymath.round(who.spritesize[2]/2-who.collisionbox[2]), 38 mymath.round(who.spritesize[2]/2-(who.spritesize[2]-who.collisionbox[4])) 39 40 if not Game.smallcollision then 41 hl = who.spritesize[1]/2 42 hr = who.spritesize[1]/2 43 hu = who.spritesize[2]/2 44 hd = who.spritesize[2]/2 45 end 46 47 if newpos[1]<=hl then 48 newpos[1]= hl 49 who.dir[1]=0 50 end 51 52 if newpos[1]>=screensize[1]-hr then 53 newpos[1]=screensize[1]-hr 54 who.dir[1]=0 55 end 56 57 if newpos[2]<=hu then 58 newpos[2]=hu 59 who.dir[2]=0 60 end 61 62 if newpos[2]>=screensize[2]-hd then 63 newpos[2]=screensize[2]-hd 64 who.dir[2]=0 65 end 66 67 newpos = Movement.check_building_collision(who, newpos, {hl, hr, hu, hd}) 68 69 who.pos[1] = newpos[1] 70 who.pos[2] = newpos[2] 71 72end 73 74function Movement.check_building_collision(who, newpos, box) 75 local collided = false 76 local hl, hr, hu, hd = box[1],box[2],box[3],box[4] 77 local containerbox = {newpos[1]-hl, newpos[2]-hu, newpos[1]+hr, newpos[2]+hd} 78 79 -- first check if I'm in the freebox 80 if who.free_box and mymath.check_boxinbox(containerbox, who.free_box) then 81 return newpos 82 end 83 84 -- actual collision check 85 local l = who.collision_buildings 86 while l do 87 local building = l.value 88 if (mymath.check_boxinbuilding(containerbox, building ) or 89 mymath.check_segmentinbuilding({who.pos[1],who.pos[2],newpos[1],newpos[2]}, building) ) 90 then 91 collided = true 92 local dpos = {who.pos[1],who.pos[2]} 93 94 -- if we keep moving horizontally, is the new position valid? 95 if not mymath.check_boxinbuilding({newpos[1]-hl, who.pos[2]-hu, newpos[1]+hr, who.pos[2]+hd}, building ) and 96 not mymath.check_segmentinbuilding({who.pos[1],who.pos[2],newpos[1],who.pos[2]}, building) 97 then 98 -- if so, allow it 99 dpos[1] = newpos[1] 100 end 101 102 -- if we keep moving vertically, is the new position valid? 103 if not mymath.check_boxinbuilding( {who.pos[1]-hl, newpos[2]-hu, who.pos[1]+hr, newpos[2]+hd}, building ) and 104 not mymath.check_segmentinbuilding({who.pos[1],who.pos[2],who.pos[1],newpos[2]}, building) 105 then 106 -- if so, allow it 107 dpos[2] = newpos[2] 108 end 109 110 newpos = {dpos[1],dpos[2]} 111 112 -- push to front and advance 113 local nextOne = l.next 114 who.collision_buildings = List.pushToFront(who.collision_buildings, l) 115 l = nextOne 116 117 if who.pos[1] == newpos[1] and who.pos[2] == newpos[2] then 118 return newpos 119 else 120 containerbox = {newpos[1]-hl, newpos[2]-hu, newpos[1]+hr, newpos[2]+hd} 121 end 122 else 123 l = l.next 124 end 125 end 126 127 if not collided then 128 -- we were out of the free box but we haven't collided: get new free box! 129 local collisionpoint = Movement.get_closest_collision_point( newpos, who.collision_buildings ) 130 -- create a square around the center, with one of its vertexs being the collisionpoint 131 local lhalf = math.max( math.abs( collisionpoint[1] - newpos[1] ), math.abs(collisionpoint[2] - newpos[2] ) ) 132 who.free_box = { newpos[1] - lhalf, newpos[2] - lhalf, newpos[1] + lhalf, newpos[2] + lhalf } 133 end 134 135 return newpos 136end 137 138function Movement.get_closest_collision_point ( point, buildinglist ) 139 140 if List.count(buildinglist) == 0 then 141 -- superbig! 142 return {-screensize[1], -screensize[2] } 143 end 144 145 -- arbitrary upper bound 146 local distance = screensize[1]*screensize[1] + screensize[2]*screensize[2] 147 local result 148 149 local l = buildinglist 150 while l do 151 for i,colli in ipairs(l.value.collision) do 152 local arr = mymath.distanceSq_to_box( point, colli ) 153 if arr[1] < distance then 154 distance = arr[1] 155 result = {arr[2], arr[3] } 156 end 157 end 158 l = l.next 159 end 160 161 return result 162end 163 164function Movement.get_prediction( who, dt ) 165 166 -- assume that the player did not stop 167 if who.dir[1]~=0 or who.dir[2]~=0 then 168 who.dir_prediction = { who.dir[1], who.dir[2] } 169 end 170 171 -- prediction: if the character walks straight ahead X time, where will he be? 172 local prediction = { 173 who.pos[1] + who.dir_prediction[1] * dt * who.speed, 174 who.pos[2] + who.dir_prediction[2] * dt * who.speed 175 } 176 177 local hl,hr,hu,hd = mymath.round(who.spritesize[1]/2-who.collisionbox[1]), 178 mymath.round(who.spritesize[1]/2-(who.spritesize[1]-who.collisionbox[3])), 179 mymath.round(who.spritesize[2]/2-who.collisionbox[2]), 180 mymath.round(who.spritesize[2]/2-(who.spritesize[2]-who.collisionbox[4])) 181 182 if not Game.smallcollision then 183 hl = who.spritesize[1]/2 184 hr = who.spritesize[1]/2 185 hu = who.spritesize[2]/2 186 hd = who.spritesize[2]/2 187 end 188 189 if prediction[1]<=hl then 190 prediction[1]= hl 191 end 192 193 if prediction[1]>=screensize[1]-hr then 194 prediction[1]=screensize[1]-hr 195 end 196 197 if prediction[2]<=hu then 198 prediction[2]=hu 199 end 200 201 if prediction[2]>=screensize[2]-hd then 202 prediction[2]=screensize[2]-hd 203 end 204 205 -- if the prediction crosses a wall it is not valid 206 for i,building in ipairs(Level.buildings) do 207 if (building.solid==1 or building.solid==2 or 208 (building.solid==3 and not who.isplayer)) and 209 mymath.check_segmentinbuilding({who.pos[1], who.pos[2], prediction[1], prediction[2]}, building) or 210 mymath.check_boxinbuilding({prediction[1]-hl, prediction[2]-hu, prediction[1]+hr, prediction[2]+hd}, building ) 211 then 212 prediction = {who.pos[1],who.pos[2]} 213 return prediction 214 end 215 end 216 217 return prediction 218end 219 220 221function Movement.teleport( thing ) 222-- make a thing disappear by teleporting it far outside of the screen 223 thing.pos = { 10000, 10000 } 224 thing.dir = { 0,0 } 225 226end 227 228-- ======================== BULLETS! 229 230function Movement.newbullet_hero( from, to ) 231 return List.push(Bullets, { 232 pos = {from[1], from[2]}, 233 dir = mymath.get_dir_vector( from[1], from[2], to[1], to[2] ), 234 good = true, -- "true" for player fire, "false" for enemy fire 235 speed = 1000, -- or whatever 236 }) 237end 238 239function Movement.newbullet_enemy( from, direction ) 240 if not Level.enemiescanshoot then 241 return Bullets 242 end 243 244 return List.push(Bullets, { 245 pos = {from[1], from[2]}, 246 dir = {direction[1],direction[2]}, 247 good = false, -- "true" for player fire, "false" for enemy fire 248 speed = 1000, -- or whatever 249 }) 250end 251 252function Movement.newbullet_enemy_( from, to, deviation ) 253 local direction = mymath.get_dir_vector( from[1], from[2], to[1], to[2] ) 254 local normal_dist = mymath.randn2() 255 direction = {direction[1] + normal_dist[1] * deviation, direction[2] + normal_dist[2] * deviation } 256 -- force normalization again 257 direction = mymath.get_dir_vector( 0, 0, direction[1], direction[2] ) 258 259 return List.push(Bullets, { 260 pos = {from[1], from[2]}, 261 dir = direction, 262 good = false, -- "true" for player fire, "false" for enemy fire 263 speed = 1000, -- or whatever 264 }) 265end 266 267function Movement.newbullet_enemy_displaced( from, to, deviation, displacement_angle ) 268 local direction = mymath.get_dir_vector( from[1], from[2], to[1], to[2] ) 269 local normal_dist = mymath.randn2() 270 direction = {direction[1] + normal_dist[1] * deviation, direction[2] + normal_dist[2] * deviation } 271 -- force normalization again 272 direction = mymath.get_dir_vector( 0, 0, direction[1], direction[2] ) 273 274 displacement_matrix = mymath.get_rotation_matrix( displacement_angle ) 275 direction = {direction[1]*displacement_matrix[1]+direction[2]*displacement_matrix[2], 276 direction[1]*displacement_matrix[3]+direction[2]*displacement_matrix[4]} 277 278 return List.push(Bullets, { 279 pos = {from[1], from[2]}, 280 dir = direction, 281 good = false, -- "true" for player fire, "false" for enemy fire 282 speed = 1000, -- or whatever 283 }) 284end 285 286function Movement.update_bullets( dt ) 287 288 List.applydel( Bullets, Movement.update_singlebullet, dt ) 289 -- for each bullet, update position 290end 291 292function Movement.update_singlebullet( bullet, dt ) 293 local formerpos = {bullet.pos[1], bullet.pos[2]} 294 bullet.pos[1] = bullet.pos[1] + bullet.dir[1] * dt * bullet.speed 295 bullet.pos[2] = bullet.pos[2] + bullet.dir[2] * dt * bullet.speed 296 297 if bullet.pos[1]<0 or bullet.pos[1]>screensize[1] or 298 bullet.pos[2]<0 or bullet.pos[2]>screensize[2] then 299 Movement.teleport( bullet ) 300 return false 301 end 302 303 local bullet_trajectory = { formerpos[1], formerpos[2], bullet.pos[1], bullet.pos[2]} 304 305 -- ********* hit a building? 306 for i,building in ipairs(Level.buildings) do 307 if building.solid==1 and mymath.check_segmentinbuilding( bullet_trajectory, building) 308 then 309 Movement.teleport( bullet ) 310 return false 311 end 312 end 313 314 --- hit an enemy? 315 if bullet.good then 316 for i,enemy in ipairs(Level.enemies) do 317 if enemy.state ~= 0 and 318 mymath.check_intersection(bullet_trajectory, 319 {enemy.pos[1]-enemy.spritesize[1]/2, enemy.pos[2]-enemy.spritesize[2]/2, 320 enemy.pos[1]+enemy.spritesize[1]/2, enemy.pos[2]+enemy.spritesize[2]/2, } ) 321 then 322 Level.enemy_dies( enemy, { bullet.dir[1], bullet.dir[2] } ) 323 324 --Movement.teleport( enemy ) 325 Movement.teleport( bullet ) 326 return false 327 end 328 end 329 end 330 331 -- hit me? 332 333 if not bullet.good and mymath.check_intersection( bullet_trajectory, 334 {Player.pos[1]-Player.spritesize[1]/2, Player.pos[2]-Player.spritesize[2]/2, 335 Player.pos[1]+Player.spritesize[1]/2, Player.pos[2]+Player.spritesize[2]/2, } ) 336 then 337 Level.player_dies( { bullet.dir[1], bullet.dir[2] } ) 338 return false 339 end 340 return true 341end 342 343