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