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.
15 # 3D client for Tinks!
18 app_namespace
"org.nitlanguage.tinks3d"
19 app_version
(1, 0, git_revision
)
22 android_manifest
"""<uses-permission android:name="android.permission.INTERNET" />"""
34 # Maximum distance from the camera to hear events and display explosions
35 private var far_dist2
= 2000.0
37 # Approximate maximum distance from the camera to display features
38 private var features_radius
= 24
45 new Model("models/Tall_Rock_1_01.obj"),
46 new Model("models/Tall_Rock_2_01.obj"),
47 new Model("models/Tall_Rock_3_01.obj"),
48 new Model("models/Tall_Rock_4_01.obj")]
52 new Model("models/Oak_Dark_01.obj"),
53 new Model("models/Oak_Green_01.obj"),
54 new Model("models/Large_Oak_Dark_01.obj"),
55 new Model("models/Large_Oak_Green_01.obj")]
57 # Models of the debris left by a destroyed tank
59 new Model("models/debris0.obj"),
60 new Model("models/debris1.obj")]
62 # Model the health pickup
63 var model_health
= new Model("models/health.obj")
65 # Model of the tank base (without the turret)
66 var model_tank_base
= new Model("models/tank.obj")
68 # Model of the tank turret
69 var model_tank_turret
= new Model("models/tank-turret.obj")
71 # Blast effect on the ground after an explosion
72 private var blast_texture
= new Texture("textures/blast.png")
73 private var blast_material
: TexturedMaterial do
74 var mat
= new TexturedMaterial([1.0]*4, [0.0]*4, [0.0]*4)
75 mat
.ambient_texture
= blast_texture
78 private var blast_model
= new LeafModel(new Plane, blast_material
)
83 # Explosion image for particle effect
84 private var texture_explosion
= new Texture("particles/explosion00.png")
87 var explosion_system
= new ParticleSystem(20, explosion_program
, texture_explosion
)
89 # Explosion image for particle effect
90 private var texture_smoke
= new Texture("particles/blackSmoke12.png")
93 var smoke_system
= new ParticleSystem(200, smoke_program
, texture_smoke
)
99 var turret_fire
= new Sound("sounds/turret_fire.wav")
101 # Turret is ready to fire sound
102 var turret_ready
= new Sound("sounds/turret_ready.mp3")
108 var ground
: Actor is noinit
109 private var ground_texture
= new Texture("textures/fastgras01.png")
111 # All `Feature` with an associated model, to be drawn on screen
112 var features_in_sight
= new Set[Feature]
119 var logo
= new Texture("textures/splash.png")
120 show_splash_screen logo
123 for texture
in all_root_textures
do
125 var error
= texture
.error
126 if error
!= null then print_error error
128 for model
in models
do
130 if model
.errors
.not_empty
then print_error model
.errors
.join
("\n")
133 # Modify all textures so they have a higher ambient color
134 for model
in models
do
135 for leaf
in model
.leaves
do
136 var mat
= leaf
.material
137 if mat
isa TexturedMaterial then
139 mat
.ambient_color
[0] = mat
.diffuse_color
[0] * mod
140 mat
.ambient_color
[1] = mat
.diffuse_color
[1] * mod
141 mat
.ambient_color
[2] = mat
.diffuse_color
[2] * mod
143 var tex
= mat
.diffuse_texture
144 if tex
!= null then mat
.ambient_texture
= tex
150 # TODO we may need to move this plane if the player goes far from the center
151 var ground_mesh
= new Plane
152 ground_mesh
.repeat_x
= 1000.0
153 ground_mesh
.repeat_y
= 1000.0
155 var ground_material
= new TexturedMaterial(
156 [0.0, 0.1, 0.0, 1.0], [0.4, 0.4, 0.4, 1.0], [0.0]*4)
157 ground_material
.diffuse_texture
= ground_texture
159 var ground_model
= new LeafModel(ground_mesh
, ground_material
)
160 var ground
= new Actor(ground_model
, new Point3d[Float](0.0, 0.0, 0.0))
161 ground
.scale
= 5000.0
167 world_camera
.near
= 0.1
170 glClearColor
(100.0/256.0, 120.0/256.0, 224.0/256.0, 1.0)
172 # Move the sun a bit off right above
173 light
.position
.x
= 1000.0
174 light
.position
.z
= 500.0
176 # Register our two systems
177 particle_systems
.add explosion_system
178 particle_systems
.add smoke_system
180 # Connect to server (or launch one) and assign models to rules
181 var context
= context
182 context
.game
.story
.assign_models
188 var turn
= context
.do_turn
189 for event
in turn
.events
do event
.client_react
191 # Is the player alive?
192 var local_player
= context
.local_player
193 var local_tank
= null
194 if local_player
!= null then local_tank
= local_player
.tank
196 if local_tank
!= null then
197 # Update camera position above the tank
198 var pos
= local_tank
.pos
199 world_camera
.position
.x
= pos
.x
200 world_camera
.position
.z
= pos
.y
201 world_camera
.yaw
= 1.5 * pi
- local_tank
.heading
202 world_camera
.position
.y
= 1.8
206 redef fun accept_event
(event
)
208 # Let `pressed_keys` be populated first
211 var local_player
= context
.local_player
212 var local_tank
= null
213 if local_player
!= null then local_tank
= local_player
.tank
216 if event
isa QuitEvent or (event
isa KeyEvent and event
.name
== "escape") then
220 if event
isa KeyEvent then
222 var direction_change
= ["w", "a", "s", "d"].has
(event
.name
)
223 if direction_change
and local_tank
!= null and local_player
!= null then
224 var forward
= pressed_keys
.has
("w")
225 var backward
= pressed_keys
.has
("s")
226 var left
= pressed_keys
.has
("a")
227 var right
= pressed_keys
.has
("d")
229 # Cancel contradictory commands
230 if forward
and backward
then
235 if left
and right
then
240 # Set movement and direction
244 else if backward
then move
= -0.5
248 ori
= -local_tank
.rule
.max_direction
/2.0
249 else if right
then ori
= local_tank
.rule
.max_direction
/2.0
251 # Activate to invert the orientation on reverse, (for @R4p4Ss)
252 #if backward then ori = -ori
254 # Bonus when only moving or only turning
255 if not forward
and not backward
then ori
*= 2.0
256 if not left
and not right
then move
*= 2.0
259 local_player
.orders
.add
new TankDirectionOrder(local_tank
, ori
, move
)
264 if event
.name
== "space" and local_tank
!= null and event
.is_down
then
265 if local_player
== null then return false
268 var heading
= local_tank
.heading
270 var target
= new Pos(local_tank
.pos
.x
+ dist
*heading
.cos
, local_tank
.pos
.y
+ dist
*heading
.sin
)
271 local_player
.orders
.add
new AimAndFireOrder(local_tank
, target
)
275 # Open fire with a target?
276 if event
isa PointerEvent and event
.pressed
and not event
.is_move
then
277 if local_player
== null then return false
279 var display
= display
280 if display
== null then return false
282 if local_tank
== null then
283 local_player
.orders
.add
new SpawnTankOrder(local_player
)
287 # Compute approximate target
288 var dx
= event
.x
/ display
.width
.to_f
289 dx
= dx
* 2.0 - 1.0 # center of the screen
290 var fovx
= display
.aspect_ratio
* world_camera
.field_of_view_y
292 var heading
= local_tank
.heading
+ dx
* fovx
294 var dy
= event
.y
/ display
.height
.to_f
297 var ty
= dy
* world_camera
.field_of_view_y
298 var dist
= world_camera
.position
.y
/ ty
.tan
/ 1.6
299 if dist
> 200.0 then dist
= 200.0
302 var target
= new Pos(local_tank
.pos
.x
+ dist
*heading
.cos
, local_tank
.pos
.y
+ dist
*heading
.sin
)
303 local_player
.orders
.add
new AimAndFireOrder(local_tank
, target
)
312 # Story and rules (meta game objects)
314 redef class FeatureRule
315 # Models of different alternatives
316 var models
: Array[Model] is noinit
320 # Models of the tank base
321 var base_model
: Model is noinit
323 # Models of the turret
324 var turret_model
: Model is noinit
329 # Assign models from `app` to the corresponding rules
332 tree
.models
= app
.models_tree
333 rock
.models
= app
.models_rock
334 debris
.models
= app
.models_debris
337 tank
.base_model
= app
.model_tank_base
338 tank
.turret_model
= app
.model_tank_turret
341 health
.models
= [app
.model_health
]
349 # Actor representing this feature, if in sight
350 var actor
: nullable Actor = null
352 # Instantiate `actor` and add it to the 3D world
353 fun add_actor_to_scene
355 app
.features_in_sight
.add
self
358 if actor
!= null then
359 # Reuse existing actor
360 if not app
.actors
.has
(actor
) then app
.actors
.add actor
364 # Apply a random model and rotation to new features
365 actor
= new Actor(rule
.models
.rand
,
366 new Point3d[Float](pos
.x
, 0.0, pos
.y
))
367 actor
.yaw
= 2.0*pi
.rand
374 # Remove `actor` from the `actors` list as it will net be used anymore
378 if actor
!= null then
379 app
.actors
.remove actor
386 # Actors representing this tank, both the base and the turret
387 var actors
: Array[Actor] is lazy
do
388 var actors
= new Array[Actor]
389 var actor
= new Actor(app
.model_tank_base
, new Point3d[Float](0.0, 0.0, 0.0))
393 var tank_turret
= new Actor(app
.model_tank_turret
, new Point3d[Float](0.0, 0.0, 0.0))
394 app
.actors
.add tank_turret
395 actors
.add tank_turret
404 private fun client_react
do end
407 redef class FeatureChangeEvent
408 redef fun client_react
410 var old_feature
= old_feature
411 if old_feature
!= null then old_feature
.destroy_actor
413 var feature
= feature
414 if feature
!= null then feature
.add_actor_to_scene
418 redef class ExplosionEvent
419 redef fun client_react
421 for feature
in destroyed_features
do feature
.destroy_actor
424 app
.explosion_system
.add
(new Point3d[Float](pos
.x
, 1.0, pos
.y
), 4096.0, 0.3)
426 app
.explosion_system
.add
(
427 new Point3d[Float](pos
.x
& 1.0, 1.0 & 1.0, pos
.y
& 1.0),
428 2048.0 & 1024.0, 0.3 & 0.1)
431 # Blast mark on the ground
432 var blast
= new Actor(app
.blast_model
, new Point3d[Float](pos
.x
, 0.05 & 0.04, pos
.y
))
434 blast
.yaw
= 2.0*pi
.rand
439 var dt
= 0.2 * s
.to_f
+ 0.1.rand
440 app
.smoke_system
.add
(
441 new Point3d[Float](pos
.x
& 0.2, 0.0, pos
.y
& 0.2),
442 1024.0 & 512.0, 10.0 & 4.0, dt
)
447 redef class OpenFireEvent
448 redef fun client_react
450 if tank
.pos
.dist2_3d
(app
.world_camera
.position
) < app
.far_dist2
then
456 var a
= tank
.turret
.heading
- 0.025 # Correct to center the art
457 var pos
= new Point3d[Float](tank
.pos
.x
+ d
*a
.cos
, 1.25, tank
.pos
.y
+ d
*a
.sin
)
458 app
.explosion_system
.add
(pos
, 0.75*256.0, 0.15)
463 redef class TurretReadyEvent
464 redef fun client_react
466 if tank
.pos
.dist2_3d
(app
.world_camera
.position
) < app
.far_dist2
then
468 app
.turret_ready
.play
473 redef class TankMoveEvent
474 redef fun client_react
477 for actor
in tank
.actors
do
478 actor
.center
.x
= pos
.x
479 actor
.center
.z
= pos
.y
482 tank
.actors
[0].yaw
= -tank
.heading
+ pi
483 tank
.actors
[1].yaw
= -tank
.turret
.heading
+ pi
485 # Keep going only for the local tank
486 var local_player
= app
.context
.local_player
487 if local_player
!= tank
.player
then return
489 var center
= tank
.pos
490 var d
= app
.features_radius
491 var l
= center
.x
.to_i
- d
492 var r
= center
.x
.to_i
+ d
493 var t
= center
.y
.to_i
- d
494 var b
= center
.y
.to_i
+ d
496 # Remove out of range features
497 for feature
in app
.features_in_sight
.to_a
do
498 var x
= feature
.pos
.x
.to_i
499 var y
= feature
.pos
.y
.to_i
500 if x
< l
or x
> r
or y
< t
or y
> b
then
501 var actor
= feature
.actor
502 app
.actors
.remove actor
503 app
.features_in_sight
.remove feature
507 # Add newly in range features
510 var feature
= app
.context
.game
.world
[x
, y
]
511 if feature
!= null then
512 feature
.add_actor_to_scene
519 redef class TankDeathEvent
520 redef fun client_react
522 for actor
in tank
.actors
do app
.actors
.remove actor
531 # Square of the distance to 3D coordinates `other`
533 # Same as `dist2` but using `other.z` as the Y value
534 # to adapt from the flat plane on X/Y to X/Z.
535 private fun dist2_3d
(other
: Point3d[Numeric]): N
537 var dx
= other
.x
.sub
(x
)
538 var dy
= other
.z
.sub
(y
)
539 var s
= (dx
.mul
(dx
)).add
(dy
.mul
(dy
))
545 # Fuzzy value in `[self-variation..self+variation]`
546 fun &(variation
: Float): Float do return self - variation
+ 2.0*variation
.rand