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" />"""
36 # Maximum distance from the camera to hear events and display explosions
37 private var far_dist2
= 2000.0
39 # Approximate maximum distance from the camera to display features
40 private var features_radius
= 24
46 var models_rock
= new Array[Model].with_items
(
47 new Model("models/Tall_Rock_1_01.obj"),
48 new Model("models/Tall_Rock_2_01.obj"),
49 new Model("models/Tall_Rock_3_01.obj"),
50 new Model("models/Tall_Rock_4_01.obj"))
53 var models_tree
= new Array[Model].with_items
(
54 new Model("models/Oak_Dark_01.obj"),
55 new Model("models/Oak_Green_01.obj"),
56 new Model("models/Large_Oak_Dark_01.obj"),
57 new Model("models/Large_Oak_Green_01.obj"))
59 # Models of the debris left by a destroyed tank
60 var models_debris
= new Array[Model].with_items
(
61 new Model("models/debris0.obj"),
62 new Model("models/debris1.obj"))
64 # Model the health pickup
65 var model_health
= new Model("models/health.obj")
67 # Model of the tank base (without the turret)
68 var model_tank_base
= new Model("models/tank.obj")
70 # Model of the tank turret
71 var model_tank_turret
= new Model("models/tank-turret.obj")
73 # Blast effect on the ground after an explosion
74 private var blast_texture
= new Texture("textures/blast.png")
75 private var blast_material
: TexturedMaterial do
76 var mat
= new TexturedMaterial([1.0]*4, [0.0]*4, [0.0]*4)
77 mat
.ambient_texture
= blast_texture
80 private var blast_model
= new LeafModel(new Plane, blast_material
)
85 # Explosion image for particle effect
86 private var texture_explosion
= new Texture("particles/explosion00.png")
89 var explosion_system
= new ParticleSystem(20, explosion_program
, texture_explosion
)
91 # Explosion image for particle effect
92 private var texture_smoke
= new Texture("particles/blackSmoke12.png")
95 var smoke_system
= new ParticleSystem(200, smoke_program
, texture_smoke
)
101 var turret_fire
= new Sound("sounds/turret_fire.wav")
103 # Turret is ready to fire sound
104 var turret_ready
= new Sound("sounds/turret_ready.mp3")
110 var ground
: Actor is noinit
111 private var ground_texture
= new Texture("textures/fastgras01.png")
113 # All `Feature` with an associated model, to be drawn on screen
114 var features_in_sight
= new Set[Feature]
121 var logo
= new Texture("textures/splash.png")
122 show_splash_screen logo
125 for texture
in all_root_textures
do
127 var error
= texture
.error
128 if error
!= null then print_error error
130 for model
in models
do
132 if model
.errors
.not_empty
then print_error model
.errors
.join
("\n")
135 # Modify all textures so they have a higher ambient color
136 for model
in models
do
137 for leaf
in model
.leaves
do
138 var mat
= leaf
.material
139 if mat
isa TexturedMaterial then
141 mat
.ambient_color
[0] = mat
.diffuse_color
[0] * mod
142 mat
.ambient_color
[1] = mat
.diffuse_color
[1] * mod
143 mat
.ambient_color
[2] = mat
.diffuse_color
[2] * mod
145 var tex
= mat
.diffuse_texture
146 if tex
!= null then mat
.ambient_texture
= tex
152 # TODO we may need to move this plane if the player goes far from the center
153 var ground_mesh
= new Plane
154 ground_mesh
.repeat_x
= 1000.0
155 ground_mesh
.repeat_y
= 1000.0
157 var ground_material
= new TexturedMaterial(
158 [0.0, 0.1, 0.0, 1.0], [0.4, 0.4, 0.4, 1.0], [0.0]*4)
159 ground_material
.diffuse_texture
= ground_texture
161 var ground_model
= new LeafModel(ground_mesh
, ground_material
)
162 var ground
= new Actor(ground_model
, new Point3d[Float](0.0, 0.0, 0.0))
163 ground
.scale
= 5000.0
169 world_camera
.near
= 0.1
172 glClearColor
(100.0/256.0, 120.0/256.0, 224.0/256.0, 1.0)
174 # Move the sun a bit off right above
175 light
.position
.x
= 1000.0
176 light
.position
.z
= 500.0
178 # Register our two systems
179 particle_systems
.add explosion_system
180 particle_systems
.add smoke_system
182 # Connect to server (or launch one) and assign models to rules
183 var context
= context
184 context
.game
.story
.assign_models
190 var turn
= context
.do_turn
191 for event
in turn
.events
do event
.client_react
193 # Is the player alive?
194 var local_player
= context
.local_player
195 var local_tank
= null
196 if local_player
!= null then local_tank
= local_player
.tank
198 if local_tank
!= null then
199 # Update camera position above the tank
200 var pos
= local_tank
.pos
201 world_camera
.position
.x
= pos
.x
202 world_camera
.position
.z
= pos
.y
203 world_camera
.yaw
= 1.5 * pi
- local_tank
.heading
204 world_camera
.position
.y
= 1.8
208 redef fun accept_event
(event
)
210 # Let `pressed_keys` be populated first
213 var local_player
= context
.local_player
214 var local_tank
= null
215 if local_player
!= null then local_tank
= local_player
.tank
218 if event
isa QuitEvent or (event
isa KeyEvent and event
.name
== "escape") then
222 if event
isa KeyEvent then
224 var direction_change
= ["w", "a", "s", "d"].has
(event
.name
)
225 if direction_change
and local_tank
!= null and local_player
!= null then
226 var forward
= pressed_keys
.has
("w")
227 var backward
= pressed_keys
.has
("s")
228 var left
= pressed_keys
.has
("a")
229 var right
= pressed_keys
.has
("d")
231 # Cancel contradictory commands
232 if forward
and backward
then
237 if left
and right
then
242 # Set movement and direction
246 else if backward
then move
= -0.5
250 ori
= -local_tank
.rule
.max_direction
/2.0
251 else if right
then ori
= local_tank
.rule
.max_direction
/2.0
253 # Activate to invert the orientation on reverse, (for @R4p4Ss)
254 #if backward then ori = -ori
256 # Bonus when only moving or only turning
257 if not forward
and not backward
then ori
*= 2.0
258 if not left
and not right
then move
*= 2.0
261 local_player
.orders
.add
new TankDirectionOrder(local_tank
, ori
, move
)
266 if event
.name
== "space" and local_tank
!= null and event
.is_down
then
267 if local_player
== null then return false
270 var heading
= local_tank
.heading
272 var target
= new Pos(local_tank
.pos
.x
+ dist
*heading
.cos
, local_tank
.pos
.y
+ dist
*heading
.sin
)
273 local_player
.orders
.add
new AimAndFireOrder(local_tank
, target
)
277 # Open fire with a target?
278 if event
isa PointerEvent and event
.pressed
and not event
.is_move
then
279 if local_player
== null then return false
281 var display
= display
282 if display
== null then return false
284 if local_tank
== null then
285 local_player
.orders
.add
new SpawnTankOrder(local_player
)
289 # Compute approximate target
290 var dx
= event
.x
/ display
.width
.to_f
291 dx
= dx
* 2.0 - 1.0 # center of the screen
292 var fovx
= display
.aspect_ratio
* world_camera
.field_of_view_y
294 var heading
= local_tank
.heading
+ dx
* fovx
296 var dy
= event
.y
/ display
.height
.to_f
299 var ty
= dy
* world_camera
.field_of_view_y
300 var dist
= world_camera
.position
.y
/ ty
.tan
/ 1.6
301 if dist
> 200.0 then dist
= 200.0
304 var target
= new Pos(local_tank
.pos
.x
+ dist
*heading
.cos
, local_tank
.pos
.y
+ dist
*heading
.sin
)
305 local_player
.orders
.add
new AimAndFireOrder(local_tank
, target
)
314 # Story and rules (meta game objects)
316 redef class FeatureRule
317 # Models of different alternatives
318 var models
: Array[Model] is noinit
322 # Models of the tank base
323 var base_model
: Model is noinit
325 # Models of the turret
326 var turret_model
: Model is noinit
331 # Assign models from `app` to the corresponding rules
334 tree
.models
= app
.models_tree
335 rock
.models
= app
.models_rock
336 debris
.models
= app
.models_debris
339 tank
.base_model
= app
.model_tank_base
340 tank
.turret_model
= app
.model_tank_turret
343 health
.models
= [app
.model_health
]
351 # Actor representing this feature, if in sight
352 var actor
: nullable Actor = null
354 # Instantiate `actor` and add it to the 3D world
355 fun add_actor_to_scene
357 app
.features_in_sight
.add
self
360 if actor
!= null then
361 # Reuse existing actor
362 if not app
.actors
.has
(actor
) then app
.actors
.add actor
366 # Apply a random model and rotation to new features
367 actor
= new Actor(rule
.models
.rand
,
368 new Point3d[Float](pos
.x
, 0.0, pos
.y
))
369 actor
.yaw
= 2.0*pi
.rand
376 # Remove `actor` from the `actors` list as it will net be used anymore
380 if actor
!= null then
381 app
.actors
.remove actor
388 # Actors representing this tank, both the base and the turret
389 var actors
: Array[Actor] is lazy
do
390 var actors
= new Array[Actor]
391 var actor
= new Actor(app
.model_tank_base
, new Point3d[Float](0.0, 0.0, 0.0))
395 var tank_turret
= new Actor(app
.model_tank_turret
, new Point3d[Float](0.0, 0.0, 0.0))
396 app
.actors
.add tank_turret
397 actors
.add tank_turret
406 private fun client_react
do end
409 redef class FeatureChangeEvent
410 redef fun client_react
412 var old_feature
= old_feature
413 if old_feature
!= null then old_feature
.destroy_actor
415 var feature
= feature
416 if feature
!= null then feature
.add_actor_to_scene
420 redef class ExplosionEvent
421 redef fun client_react
423 for feature
in destroyed_features
do feature
.destroy_actor
426 app
.explosion_system
.add
(new Point3d[Float](pos
.x
, 1.0, pos
.y
), 4096.0, 0.3)
428 app
.explosion_system
.add
(
429 new Point3d[Float](pos
.x
& 1.0, 1.0 & 1.0, pos
.y
& 1.0),
430 2048.0 & 1024.0, 0.3 & 0.1)
433 # Blast mark on the ground
434 var blast
= new Actor(app
.blast_model
, new Point3d[Float](pos
.x
, 0.05 & 0.04, pos
.y
))
436 blast
.yaw
= 2.0*pi
.rand
441 var dt
= 0.2 * s
.to_f
+ 0.1.rand
442 app
.smoke_system
.add
(
443 new Point3d[Float](pos
.x
& 0.2, 0.0, pos
.y
& 0.2),
444 1024.0 & 512.0, 10.0 & 4.0, dt
)
449 redef class OpenFireEvent
450 redef fun client_react
452 if tank
.pos
.dist2_3d
(app
.world_camera
.position
) < app
.far_dist2
then
458 var a
= tank
.turret
.heading
- 0.025 # Correct to center the art
459 var pos
= new Point3d[Float](tank
.pos
.x
+ d
*a
.cos
, 1.25, tank
.pos
.y
+ d
*a
.sin
)
460 app
.explosion_system
.add
(pos
, 0.75*256.0, 0.15)
465 redef class TurretReadyEvent
466 redef fun client_react
468 if tank
.pos
.dist2_3d
(app
.world_camera
.position
) < app
.far_dist2
then
470 app
.turret_ready
.play
475 redef class TankMoveEvent
476 redef fun client_react
479 for actor
in tank
.actors
do
480 actor
.center
.x
= pos
.x
481 actor
.center
.z
= pos
.y
484 tank
.actors
[0].yaw
= -tank
.heading
+ pi
485 tank
.actors
[1].yaw
= -tank
.turret
.heading
+ pi
487 # Keep going only for the local tank
488 var local_player
= app
.context
.local_player
489 if local_player
!= tank
.player
then return
491 var center
= tank
.pos
492 var d
= app
.features_radius
493 var l
= center
.x
.to_i
- d
494 var r
= center
.x
.to_i
+ d
495 var t
= center
.y
.to_i
- d
496 var b
= center
.y
.to_i
+ d
498 # Remove out of range features
499 for feature
in app
.features_in_sight
.to_a
do
500 var x
= feature
.pos
.x
.to_i
501 var y
= feature
.pos
.y
.to_i
502 if x
< l
or x
> r
or y
< t
or y
> b
then
503 var actor
= feature
.actor
504 app
.actors
.remove actor
505 app
.features_in_sight
.remove feature
509 # Add newly in range features
512 var feature
= app
.context
.game
.world
[x
, y
]
513 if feature
!= null then
514 feature
.add_actor_to_scene
521 redef class TankDeathEvent
522 redef fun client_react
524 for actor
in tank
.actors
do app
.actors
.remove actor
533 # Square of the distance to 3D coordinates `other`
535 # Same as `dist2` but using `other.z` as the Y value
536 # to adapt from the flat plane on X/Y to X/Z.
537 private fun dist2_3d
(other
: Point3d[Numeric]): N
539 var dx
= other
.x
.sub
(x
)
540 var dy
= other
.z
.sub
(y
)
541 var s
= (dx
.mul
(dx
)).add
(dy
.mul
(dy
))
547 # Fuzzy value in `[self-variation..self+variation]`
548 fun &(variation
: Float): Float do return self - variation
+ 2.0*variation
.rand