1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
19 import mnit
::opengles1
20 import performance_analysis
28 # A position within the screen
32 # Convert to a game logic `Pos` by applying camera transformation
33 fun to_logic
(camera
: Camera): Pos do
34 return new Pos(x
/camera
.basic_zoom
+ camera
.dx
, y
/camera
.basic_zoom
+ camera
.dy
)
39 # Convert to a `ScreenPos` by applying camera transformation
40 fun to_screen
(camera
: Camera): ScreenPos do
41 return new ScreenPos((x
- camera
.dx
) * camera
.basic_zoom
, (y
- camera
.dy
) * camera
.basic_zoom
)
45 # Camera managing the screen view on the world
47 # Offset of the top left corner of the screen, X part
50 # Offset of the top left corner of the screen, Y part
53 # Basic zoom, the distance between 2 features
55 # In the world logic, the distance is of 1.
56 # This value depends on the size of the graphical assets.
58 # TODO make it a full zoom by scaling images too, if needed.
61 # Center of the `display` as world `Pos`
62 fun center
(display
: Display): Pos
64 return (new ScreenPos(display
.width
.to_f
* 0.5, display
.height
.to_f
* 0.5)).to_logic
(self)
67 # Center the `display` on the world `pos`
68 fun center_on
(display
: Display, pos
: Pos)
70 self.dx
= pos
.x
- display
.width
.to_f
* 0.5 / basic_zoom
71 self.dy
= pos
.y
- display
.height
.to_f
* 0.5 / basic_zoom
77 # Collection of assets
78 var assets
= new Assets is lazy
84 assets
.assign_images_to_story context
.game
.story
87 # Camera managing transformation between world and screen positions
88 var camera
= new Camera
90 # Square of the minimum distance from the tank for an object to be "far"
92 # This value influences which sounds are heard,
93 # the strength of vibrations and
94 # whether an arrow points to a far unit
95 private var far_dist2
= 2000.0
100 if s
isa RemoteGameContext then maximum_fps
= 0.0
104 # Tank tracks tracks on the ground
106 # TODO use particles or at least optimize drawing
107 var tracks
= new List[Couple[Pos, Float]]
109 redef fun frame_core
(display
)
111 var clock
= new Clock
113 var turn
= context
.do_turn
114 sys
.perfs
["do_turn"].add clock
.lapse
119 if down_keys
.has
("left") then camera
.dx
-= 1.0
120 if down_keys
.has
("right") then camera
.dx
+= 1.0
121 if down_keys
.has
("up") then camera
.dy
-= 1.0
122 if down_keys
.has
("down") then camera
.dy
+= 1.0
124 var local_tank
= local_tank
125 if local_tank
!= null then
126 var tank_speed
= local_tank
.direction_forwards
*local_tank
.rule
.max_speed
127 tank_speed
= tank_speed
.clamp
(-0.5, 0.5)
129 var prop_pos
= local_tank
.pos
+ local_tank
.heading
.to_vector
(tank_speed
* 16.0)
130 var old_pos
= camera
.center
(display
)
131 var half
= old_pos
.lerp
(prop_pos
, 0.02)
133 camera
.center_on
(display
, new Pos(half
.x
, half
.y
))
137 display
.clear
(0.0, 0.45, 0.0)
140 for track
in tracks
do
141 var pos
= track
.first
.to_screen
(camera
)
142 display
.blit_rotated
(assets
.drawing
.track
, pos
.x
, pos
.y
, track
.second
)
146 for blast
in context
.game
.world
.blast_sites
do
147 var pos
= blast
.to_screen
(camera
)
148 display
.blit_centered
(assets
.drawing
.blast
, pos
.x
, pos
.y
)
152 var tl
= (new ScreenPos(0.0, 0.0)).to_logic
(camera
)
153 var br
= (new ScreenPos(display
.width
.to_f
, display
.height
.to_f
)).to_logic
(camera
)
154 for x
in [tl
.x
.floor
.to_i
.. br
.x
.ceil
.to_i
] do
155 for y
in [tl
.y
.floor
.to_i
.. br
.y
.ceil
.to_i
] do
156 var feature
= context
.game
.world
[x
, y
]
157 if feature
!= null then
158 var pos
= feature
.pos
.to_screen
(camera
)
159 var image
= feature
.rule
.images
[feature
.image_index
]
160 display
.blit_rotated
(image
, pos
.x
, pos
.y
, feature
.angle
)
166 for tank
in context
.game
.tanks
do
168 if (tank
.direction_heading
!= 0.0 and 40.rand
== 0) or
169 (tank
.direction_forwards
!= 0.0 and 100.rand
== 0) then
171 tracks
.add
new Couple[Pos, Float](tank
.pos
, tank
.heading
)
172 if tracks
.length
> 1000 then tracks
.shift
175 # Get the player stencil
176 var player
= tank
.player
178 if player
!= null then stencil
= assets
.drawing
.stencils
[player
.stencil_index
]
180 if camera
.center
(display
).dist2
(tank
.pos
) > far_dist2
then
181 var hw
= (display
.width
/2).to_f
182 var hh
= (display
.height
/2).to_f
184 var angle
= camera
.center
(display
).atan2
(tank
.pos
)
185 var x
= hw
+ angle
.cos
* (hw-128
.0
)
186 var y
= hh
+ angle
.sin
* (hh-128
.0
)
188 var screen_pos
= new ScreenPos(x
, y
)
189 display
.blit_rotated
(assets
.drawing
.arrow
, screen_pos
.x
, screen_pos
.y
, angle
)
190 if stencil
!= null then display
.blit_rotated
(stencil
, screen_pos
.x
, screen_pos
.y
, angle
)
194 var screen_pos
= tank
.pos
.to_screen
(camera
)
196 var damage
= tank
.rule
.max_health
- tank
.health
197 damage
= damage
.clamp
(0, tank
.rule
.base_images
.length
)
199 var base_image
= tank
.rule
.base_images
[damage
]
200 display
.blit_rotated
(base_image
, screen_pos
.x
, screen_pos
.y
, tank
.heading
)
201 if stencil
!= null then display
.blit_rotated
(stencil
, screen_pos
.x
, screen_pos
.y
, tank
.heading
)
202 display
.blit_rotated
(tank
.rule
.turret_image
, screen_pos
.x
, screen_pos
.y
, tank
.turret
.heading
)
205 var corners
= tank
.corners_at
(new Couple[Pos, Float](tank
.pos
, tank
.heading
))
207 var p
= c
.to_screen
(camera
)
208 display
.blit_centered
(assets
.drawing
.red_dot
, p
.x
, p
.y
)
214 for event
in turn
.events
do event
.client_react
(display
, turn
)
216 # Gather and show some performance stats!
217 sys
.perfs
["draw"].add clock
.lapse
218 if context
.game
.tick
% 300 == 5 then print sys
.perfs
221 # Keys currently down
223 # TODO find a nice API and move up to mnit/gamnit
224 var down_keys
= new HashSet[String]
228 var local_tank
= local_tank
229 var local_player
= context
.local_player
232 if ie
isa QuitEvent or
233 (ie
isa KeyEvent and ie
.name
== "escape") then
240 if local_tank
== null and local_player
!= null then
241 if (ie
isa KeyEvent and ie
.name
== "space") or
242 (ie
isa PointerEvent and ie
.depressed
) then
244 local_player
.orders
.add
new SpawnTankOrder(local_player
)
249 if ie
isa KeyEvent then
255 else if down_keys
.has
(name
) then
256 down_keys
.remove name
260 var direction_change
= ["w", "a", "s", "d"].has
(ie
.name
)
261 if direction_change
and local_tank
!= null and local_player
!= null then
262 var forward
= down_keys
.has
("w")
263 var backward
= down_keys
.has
("s")
264 var left
= down_keys
.has
("a")
265 var right
= down_keys
.has
("d")
267 # Cancel contradictory commands
268 if forward
and backward
then
273 if left
and right
then
278 # Set movement and direction
282 else if backward
then move
= -0.5
286 ori
= -local_tank
.rule
.max_direction
/2.0
287 else if right
then ori
= local_tank
.rule
.max_direction
/2.0
289 # Activate to invert the orientation on reverse, (for at @R4p4Ss)
290 #if backward then ori = -ori
292 # Bonus when only moving or only turning
293 if not forward
and not backward
then ori
*= 2.0
294 if not left
and not right
then move
*= 2.0
297 local_player
.orders
.add
new TankDirectionOrder(local_tank
, ori
, move
)
302 # On click (or tap), aim and fire
303 if ie
isa PointerEvent then
305 if ie
.pressed
and local_tank
!= null and local_player
!= null then
306 var target
= (new ScreenPos(ie
.x
, ie
.y
)).to_logic
(camera
)
307 local_player
.orders
.add
new AimAndFireOrder(local_tank
, target
)
317 fun client_react
(display
: Display, turn
: TTurn) do end
320 redef class ExplosionEvent
321 redef fun client_react
(display
, turn
)
323 var pos
= pos
.to_screen
(app
.camera
)
324 display
.blit_centered
(app
.assets
.drawing
.explosion
, pos
.x
, pos
.y
)
328 redef class OpenFireEvent
329 redef fun client_react
(display
, turn
)
331 var screen_pos
= tank
.pos
.to_screen
(app
.camera
)
332 display
.blit_rotated
(app
.assets
.drawing
.turret_firing
, screen_pos
.x
, screen_pos
.y
, tank
.turret
.heading
)
334 if tank
.pos
.dist2
(app
.camera
.center
(display
)) < app
.far_dist2
then
336 app
.assets
.turret_fire
.play
341 redef class TurretReadyEvent
342 redef fun client_react
(display
, turn
)
344 if tank
.pos
.dist2
(app
.camera
.center
(display
)) < app
.far_dist2
then
346 app
.assets
.turret_ready
.play