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 # Context of the game, either local or remote
91 var context
: GameContext is lazy
do
95 var port
= default_listening_port
97 if args
.not_empty
then
98 # Use first argument as the server address
100 if args
.length
> 1 then port
= args
[1].to_i
102 print
"Looking for a server..."
104 var s
= new UDPSocket
105 s
.enable_broadcast
= true
107 s
.broadcast
(discovery_port
, "Server? {handshake_app_name}")
108 nanosleep
(0, 100_000_000)
110 var ptr
= new Ref[nullable SocketAddress](null)
111 var resp
= s
.recv_from
(1024, ptr
)
114 if not resp
.is_empty
then
115 var words
= resp
.split
(" ")
116 if words
.length
== 3 and words
[0] == "Server!" and words
[1] == handshake_app_name
and words
[2].is_numeric
then
117 address
= src
.address
123 if address
== null then
124 print
"Launching a local server"
127 return new LocalServerContext
129 print
"Connecting to:{address}:{port}"
132 # Args are: tinks server_address {port}
133 #var address = "riph" # args[0]
134 #var port = sys.default_listening_port
135 if args
.length
> 1 then port
= args
[1].to_i
137 # Setup connection config
138 var server_config
= new RemoteServerConfig(address
, port
)
139 var server
= new RemoteServer(server_config
)
141 # Connect then complete handshake
142 assert server
.connect
else print_error
"Connection to server failed with {server.socket.last_error or else "none"}"
143 assert server
.handshake
else print_error
"Handshake with server failed"
145 # Download and setup remote game
146 var context
= new RemoteGameContext(server
)
153 # `Tank` of the local player, if any
154 fun local_tank
: nullable Tank
156 # FIXME use a ? to one line this
157 var local_player
= context
.local_player
158 if local_player
== null then return null
159 return local_player
.tank
162 # Square of the minimum distance from the tank for an object to be "far"
164 # This value influences which sounds are heard,
165 # the strength of vibrations and
166 # whether an arrow points to a far unit
167 private var far_dist2
= 2000.0
169 # Tank tracks tracks on the ground
171 # TODO use particles or at least optimize drawing
172 var tracks
= new List[Couple[Pos, Float]]
174 redef fun frame_core
(display
)
176 var clock
= new Clock
178 var turn
= context
.do_turn
179 sys
.perfs
["do_turn"].add clock
.lapse
184 if down_keys
.has
("left") then camera
.dx
-= 1.0
185 if down_keys
.has
("right") then camera
.dx
+= 1.0
186 if down_keys
.has
("up") then camera
.dy
-= 1.0
187 if down_keys
.has
("down") then camera
.dy
+= 1.0
189 var local_tank
= local_tank
190 if local_tank
!= null then
191 var tank_speed
= local_tank
.direction_forwards
*local_tank
.rule
.max_speed
192 tank_speed
= tank_speed
.min
(0.5).max
(-0.5)
194 var prop_pos
= local_tank
.pos
+ local_tank
.heading
.to_vector
(tank_speed
* 16.0)
195 var old_pos
= camera
.center
(display
)
196 var half
= old_pos
.lerp
(prop_pos
, 0.02)
198 camera
.center_on
(display
, new Pos(half
.x
, half
.y
))
202 display
.clear
(0.0, 0.45, 0.0)
205 for track
in tracks
do
206 var pos
= track
.first
.to_screen
(camera
)
207 display
.blit_rotated
(assets
.drawing
.track
, pos
.x
, pos
.y
, track
.second
)
211 for blast
in context
.game
.world
.blast_sites
do
212 var pos
= blast
.to_screen
(camera
)
213 display
.blit_centered
(assets
.drawing
.blast
, pos
.x
, pos
.y
)
217 var tl
= (new ScreenPos(0.0, 0.0)).to_logic
(camera
)
218 var br
= (new ScreenPos(display
.width
.to_f
, display
.height
.to_f
)).to_logic
(camera
)
219 for x
in [tl
.x
.floor
.to_i
.. br
.x
.ceil
.to_i
] do
220 for y
in [tl
.y
.floor
.to_i
.. br
.y
.ceil
.to_i
] do
221 var feature
= context
.game
.world
[x
, y
]
222 if feature
!= null then
223 var pos
= feature
.pos
.to_screen
(camera
)
224 var image
= feature
.rule
.images
[feature
.image_index
]
225 display
.blit_rotated
(image
, pos
.x
, pos
.y
, feature
.angle
)
231 for tank
in context
.game
.tanks
do
233 if (tank
.direction_heading
!= 0.0 and 40.rand
== 0) or
234 (tank
.direction_forwards
!= 0.0 and 100.rand
== 0) then
236 tracks
.add
new Couple[Pos, Float](tank
.pos
, tank
.heading
)
237 if tracks
.length
> 1000 then tracks
.shift
240 # Get the player stencil
241 var player
= tank
.player
243 if player
!= null then stencil
= assets
.drawing
.stencils
[player
.stencil_index
]
245 if camera
.center
(display
).dist2
(tank
.pos
) > far_dist2
then
246 var hw
= (display
.width
/2).to_f
247 var hh
= (display
.height
/2).to_f
249 var angle
= camera
.center
(display
).atan2
(tank
.pos
)
250 var x
= hw
+ angle
.cos
* (hw-128
.0
)
251 var y
= hh
+ angle
.sin
* (hh-128
.0
)
253 var screen_pos
= new ScreenPos(x
, y
)
254 display
.blit_rotated
(assets
.drawing
.arrow
, screen_pos
.x
, screen_pos
.y
, angle
)
255 if stencil
!= null then display
.blit_rotated
(stencil
, screen_pos
.x
, screen_pos
.y
, angle
)
259 var screen_pos
= tank
.pos
.to_screen
(camera
)
261 var damage
= tank
.rule
.max_health
- tank
.health
262 damage
= damage
.max
(0).min
(tank
.rule
.base_images
.length
)
264 var base_image
= tank
.rule
.base_images
[damage
]
265 display
.blit_rotated
(base_image
, screen_pos
.x
, screen_pos
.y
, tank
.heading
)
266 if stencil
!= null then display
.blit_rotated
(stencil
, screen_pos
.x
, screen_pos
.y
, tank
.heading
)
267 display
.blit_rotated
(tank
.rule
.turret_image
, screen_pos
.x
, screen_pos
.y
, tank
.turret
.heading
)
270 var corners
= tank
.corners_at
(new Couple[Pos, Float](tank
.pos
, tank
.heading
))
272 var p
= c
.to_screen
(camera
)
273 display
.blit_centered
(assets
.drawing
.red_dot
, p
.x
, p
.y
)
279 for event
in turn
.events
do event
.client_react
(display
, turn
)
281 # Gather and show some performance stats!
282 sys
.perfs
["draw"].add clock
.lapse
283 if context
.game
.tick
% 300 == 5 then print sys
.perfs
286 # Keys currently down
288 # TODO find a nice API and move up to mnit/gamnit
289 var down_keys
= new HashSet[String]
293 var local_tank
= local_tank
294 var local_player
= context
.local_player
297 if ie
isa QuitEvent or
298 (ie
isa KeyEvent and ie
.name
== "escape") then
305 if local_tank
== null and local_player
!= null then
306 if (ie
isa KeyEvent and ie
.name
== "space") or
307 (ie
isa PointerEvent and ie
.depressed
) then
309 local_player
.orders
.add
new SpawnTankOrder(local_player
)
314 if ie
isa KeyEvent then
320 else if down_keys
.has
(name
) then
321 down_keys
.remove name
325 var direction_change
= ["w", "a", "s", "d"].has
(ie
.name
)
326 if direction_change
and local_tank
!= null and local_player
!= null then
327 var forward
= down_keys
.has
("w")
328 var backward
= down_keys
.has
("s")
329 var left
= down_keys
.has
("a")
330 var right
= down_keys
.has
("d")
332 # Cancel contradictory commands
333 if forward
and backward
then
338 if left
and right
then
343 # Set movement and direction
347 else if backward
then move
= -0.5
351 ori
= -local_tank
.rule
.max_direction
/2.0
352 else if right
then ori
= local_tank
.rule
.max_direction
/2.0
354 # Activate to invert the orientation on reverse, (for at @R4p4Ss)
355 #if backward then ori = -ori
357 # Bonus when only moving or only turning
358 if not forward
and not backward
then ori
*= 2.0
359 if not left
and not right
then move
*= 2.0
362 local_player
.orders
.add
new TankDirectionOrder(local_tank
, ori
, move
)
367 # On click (or tap), aim and fire
368 if ie
isa PointerEvent then
370 if ie
.pressed
and local_tank
!= null and local_player
!= null then
371 var target
= (new ScreenPos(ie
.x
, ie
.y
)).to_logic
(camera
)
372 local_player
.orders
.add
new AimAndFireOrder(local_tank
, target
)
382 fun client_react
(display
: Display, turn
: TTurn) do end
385 redef class ExplosionEvent
386 redef fun client_react
(display
, turn
)
388 var pos
= pos
.to_screen
(app
.camera
)
389 display
.blit_centered
(app
.assets
.drawing
.explosion
, pos
.x
, pos
.y
)
393 redef class OpenFireEvent
394 redef fun client_react
(display
, turn
)
396 var screen_pos
= tank
.pos
.to_screen
(app
.camera
)
397 display
.blit_rotated
(app
.assets
.drawing
.turret_firing
, screen_pos
.x
, screen_pos
.y
, tank
.turret
.heading
)
399 if tank
.pos
.dist2
(app
.camera
.center
(display
)) < app
.far_dist2
then
401 app
.assets
.turret_fire
.play
406 redef class TurretReadyEvent
407 redef fun client_react
(display
, turn
)
409 if tank
.pos
.dist2
(app
.camera
.center
(display
)) < app
.far_dist2
then
411 app
.assets
.turret_ready
.play