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 # Core game logic of Action Nitro
20 # Root game object of the whole game logic
22 # Used as a visitor in all methods modifying the state of the world.
25 # The player, if the game has started
26 var player
: nullable Player = null is writable
29 var planes
= new Array[Platform]
32 var enemies
= new Array[Enemy]
34 # All live bullets shot by `enemies`
35 var enemy_bullets
= new Array[Bullet]
37 # All live bullets shot by `player`
38 var player_bullets
= new Array[Bullet]
41 var powerups
= new Array[Powerup]
43 # Current open parachute, if any
44 var parachute
: nullable Parachute = null is writable
46 # Altitude at which to enter space and trigger the boss
47 var boss_altitude
= 1200.0
49 # Boss, the ISS occupied by bad guys
50 var boss
: nullable Boss = null is writable
52 # Runtime of this game, in seconds
58 # Approximate view of the camera, used to spawn stuff outside of the camera
59 fun camera_view
: Box[Float]
61 # TODO update from client
64 if player
!= null then
65 return new Box[Float](
66 player
.center
.x-border
, player
.center
.x
+border
,
67 player
.center
.y
+border
, player
.center
.y-border
)
69 return new Box[Float](
75 # Update the game logic for events over `dt` seconds
80 # Visit all other game logic objects
81 for plane
in planes
.reverse_iterator
do plane
.update
(dt
, self)
82 for enemy
in enemies
.reverse_iterator
do enemy
.update
(dt
, self)
85 if player
!= null then player
.update
(dt
, self)
87 for i
in enemy_bullets
.reverse_iterator
do i
.update
(dt
, self)
88 for i
in player_bullets
.reverse_iterator
do i
.update
(dt
, self)
90 for powerup
in powerups
.reverse_iterator
do powerup
.update
(dt
, self)
91 if parachute
!= null then parachute
.update
(dt
, self)
93 # Check if the player has reached the boss
95 if player
!= null and player
.altitude
>= boss_altitude
and boss
== null then
97 var boss
= new Boss(new Point3d[Float](player
.center
.x
, cam
.top
- 20.0, 0.0), w
, 4.0, new Ak47)
102 var e
= new WalkingEnemy(new Point3d[Float](boss
.center
.x
& (w
/2.0), boss
.center
.y
+ 4.0, -1.0.rand
), 4.0, 4.0, new Pistol)
108 # Explosion at `center` of the given `force`
109 fun explode
(center
: Point3d[Float], force
: Float)
111 var lists
= [planes
, enemies
: Sequence[Body]]
113 if player
!= null then lists
.add
([player
])
117 body
.apply_force
(center
, force
)
122 # Spawn or respawn the player
125 var old_player
= player
127 if old_player
!= null then
128 # Respawn just above the death position
129 pos
= old_player
.center
134 # If `dev` is passed as a command line option, spawn near space
136 if args
.has
("dev") then alt
= boss_altitude
- 10.0
138 pos
= new Point3d[Float](0.0, alt
, 0.0)
141 player
= new Player(pos
, 4.0, 4.0, new Ak47)
145 # A physical object responding to hollywood physics
149 # Position at the center of the body
150 var center
: Point3d[Float]
152 # Inertia of this body
153 var inertia
= new Point3d[Float](0.0, 0.0, 0.0) is writable
155 # Is this body still alive?
158 # Mass of this object, used by `apply_force`
159 fun mass
: Float do return 1.0
161 # Width of this object, used to detect collisions
164 # Height of this object, used to detect collisions
167 # Current health level, starts at `max_health`
168 var health
: Float = max_health
170 # Maximum health for this object
171 fun max_health
: Float do return 100.0
173 # Is this object affected by gravity?
174 fun affected_by_gravity
: Bool do return true
176 # Is this object dead? TODO merge with is_alive
177 fun dead
: Bool do return health
<= 0.0
179 # Apply game logic for the last `dt` seconds within `world`
180 fun update
(dt
: Float, world
: World)
182 if affected_by_gravity
then inertia
.y
-= 4.0
184 center
.x
+= dt
* inertia
.x
185 center
.y
+= dt
* inertia
.y
186 center
.z
+= dt
* inertia
.z
189 if bottom
<= 0.0 and affected_by_gravity
then
190 center
.y
= height
/ 2.0
195 # Apply a force, usually a result of `World::explode`
196 fun apply_force
(origin
: Point3d[Float], force
: Float)
198 var dx
= center
.x
- origin
.x
199 var dy
= center
.y
- origin
.y
201 var d2
= dx
*dx
+ dy
*dy
203 #TODO if d2 > ? then return
205 inertia
.x
+= dx
* force
/ d
/ mass
* 4.0
206 inertia
.y
+= dy
* force
/ d
/ mass
* 12.0
209 if self isa Player then self.plane
= null
212 # Is this object out of `world.camera_view`?
213 fun out_of_screen
(player
: Player, world
: World): Bool
215 var camera
= world
.camera_view
216 if right
< camera
.left
- 20.0 then return true
217 if left
> camera
.right
+ 20.0 then return true
218 if top
< camera
.bottom
- 20.0 then return true
219 if bottom
> camera
.top
+ 20.0 then return true
223 # Apply `damage` to this object
224 fun hit
(damage
: Float, world
: World)
226 self.health
-= damage
227 if self.health
<= 0.0 then die
(world
)
230 # Die in the game logic, with graphical animations
232 # Calls `destroy` by default.
233 fun die
(world
: World)
235 if not is_alive
then return
240 # Destroy this objects and most references to it
241 fun destroy
(world
: World) do end
246 redef fun top
do return center
.y
+ height
/ 2.0
247 redef fun bottom
do return center
.y
- height
/ 2.0
248 redef fun left
do return center
.x
- width
/ 2.0
249 redef fun right
do return center
.x
+ width
/ 2.0
252 # Something to stand on
253 abstract class Platform
256 redef fun mass
do return 20.0
258 redef fun affected_by_gravity
do return false
260 # Planes slow down when close to the player, `old_inertia` is the speed before it slowed down
261 private var old_inertia_y
: nullable Float = null
263 # Enemy the spawned on the plane
264 var enemy
: nullable WalkingEnemy = null is writable
268 if not is_alive
then return
270 world
.explode
(center
, width
)
272 if 100.0.rand
> 50.0 then world
.powerups
.add
(new Powerup(self.center
, world
))
275 redef fun destroy
(world
)
277 world
.planes
.remove
self
281 # Distance to the player
282 fun player_dist
(world
: World): Float do
284 if p
== null then return 0.0
286 var dst
= center
.x
- px
290 # Has this plane slowed down because it is close to the player?
291 private var slowed_down
= false
293 # Has this plane already accelerated because it got far from the player?
294 private var accelerated
= false
296 redef fun update
(dt
, world
)
298 # High friction on the Y axis to stabilize after an `apply_force`
303 # Slow down if close to the player
304 var dst
= player_dist
(world
)
306 if not slowed_down
then
307 old_inertia_y
= inertia
.y
309 var speed
= 10.0 + 15.0.rand
310 if inertia
.x
< 0.0 then
312 else inertia
.x
= speed
317 if enemy
!= null then
318 enemy
.inertia
.x
= inertia
.x
319 enemy
.inertia
.y
= inertia
.y
320 enemy
.inertia
.z
= inertia
.z
323 else if dst
> 30.0 and not accelerated
then
324 var oi
= old_inertia_y
325 if oi
== null then return
333 # Airplane, the basic `Platform`
338 # Helicopter, the player rotates on its blades
343 # Parachute to slow down the player
347 redef var affected_by_gravity
= false
349 redef fun update
(dt
, world
) do
354 center
.x
= world
.player
.center
.x
355 center
.y
= world
.player
.center
.y
+ 5.0
363 # Input direction in `[-1.0 .. 1.0]`
364 var moving
= 0.0 is writable
366 # `moving` speed when on a plane, applied directly to `center`
367 var walking_speed
= 20.0
369 # `moving` speed when in freefall, applied to `inertia`
370 var freefall_accel
= 150.0
372 # Acceleration on the X axis applied when jumping
373 var jump_accel
= 24.0
375 # On which plane is standing `self`? if any.
376 var plane
: nullable Platform = null
378 # Position in relation to `plane`
379 private var dx_to_plane
= 0.0
384 # Rotation status when on a copter bladers, used by `update`
385 private var ltr
= false
387 # Is the parachute currently deployed?
388 var parachute_deployed
= false
390 redef var affected_by_gravity
= true
392 # Altitude (in meters)
393 fun altitude
: Float do return center
.y
395 # Apply a jump from input
399 if plane
!= null then
400 # On solid plane, jump
402 inertia
.x
= plane
.inertia
.x
+ moving
* jump_accel
408 # Deploy parachute on input
412 if plane
== null and not parachute_deployed
then
414 parachute_deployed
= true
421 redef fun update
(dt
, world
)
423 if not is_alive
then return
425 if altitude
>= world
.boss_altitude
then
427 affected_by_gravity
= false
437 if on_plane
!= null then
439 if not on_plane
.is_alive
then
445 if on_plane
!= null then
446 # On a plane, applying special physics do not call super!
449 center
.x
= on_plane
.center
.x
+ dx_to_plane
+ moving
* walking_speed
* dt
450 center
.y
= on_plane
.top
+ height
/ 2.0
451 if plane
isa Helicopter then
452 center
.y
= plane
.top
+ height
/ 2.0 + 1.5
453 var left_blade
= plane
.center
.x
- 5.0
454 var right_blade
= plane
.center
.x
+ 5.0
456 var blade_speed
= 0.5
458 if px
>= right_blade
then ltr
= false
459 center
.x
= on_plane
.center
.x
+ dx_to_plane
+ moving
* walking_speed
* dt
+ blade_speed
461 if px
<= left_blade
then ltr
= true
462 center
.x
= on_plane
.center
.x
+ dx_to_plane
+ moving
* walking_speed
* dt
- blade_speed
467 if not (plane
.left
< right
and plane
.right
> left
) then
473 # Only influence the inertia
474 inertia
.x
+= moving
* freefall_accel
* dt
479 if parachute_deployed
then
480 if inertia
.y
< -10.0 then inertia
.y
= -10.0
483 # Detect collision with planes
484 for plane
in world
.planes
do # TODO optimize with quad tree
485 if plane
.left
< right
and plane
.right
> left
then
486 if old_y
> plane
.top
and bottom
<= plane
.top
then
487 var parachute
= world
.parachute
488 if parachute
!= null then
490 world
.parachute
= null
492 parachute_deployed
= false
494 plane
.inertia
.y
+= inertia
.y
/ plane
.mass
500 center
.y
= plane
.top
+ height
/ 2.0
501 if plane
isa Helicopter then
502 center
.y
= plane
.top
+ height
/ 2.0 + 4.0
510 on_plane
= self.plane
511 if on_plane
!= null then
512 dx_to_plane
= center
.x
- on_plane
.center
.x
515 # Die when hitting the ground
516 if bottom
<= 0.0 then
525 # Is the weapon ready to shoot?
526 fun can_shoot
(world
: World): Bool
528 return is_alive
and world
.t
- weapon
.last_shot
>= weapon
.cooldown
531 # Open fire at `angle`!
532 fun shoot
(angle
: Float, world
: World)
534 if not can_shoot
(world
) then return
536 var x_inertia
= angle
.cos
* weapon
.power
537 var y_inertia
= angle
.sin
* weapon
.power
538 var new_center
= new Point3d[Float](self.center
.x
, self.center
.y
, self.center
.z
- 0.2)
540 var bullet
= register_bullet
(new_center
, angle
, world
)
541 bullet
.inertia
.x
= x_inertia
542 bullet
.inertia
.y
= y_inertia
543 weapon
.last_shot
= world
.t
546 # Add a bullet, which type depends on `self`
547 protected fun register_bullet
(new_center
: Point3d[Float], angle
: Float, world
: World): Bullet
549 var bullet
= new EnemyBullet(new_center
, 2.0, 2.0, angle
, self.weapon
, world
.t
)
550 world
.enemy_bullets
.add
(bullet
)
559 # Basic starting weapon to which `self` reverts when out of bullets for powerup
560 var basic_weapon
= new Ak47
562 redef fun shoot
(angle
, world
)
566 # Consume limited bullets from powerups
567 if can_shoot
(world
) then
568 weapon
.bullet_number
-= 1
569 if weapon
.bullet_number
<= 0 then self.weapon
= basic_weapon
573 redef fun register_bullet
(new_center
, angle
, world
)
575 var bullet
= new PlayerBullet(new_center
, 2.0, 2.0, angle
, self.weapon
, world
.t
)
576 world
.player_bullets
.add
(bullet
)
580 redef fun update
(dt
, world
)
585 for p
in world
.powerups
.reverse_iterator
do
586 if self.intersects
(p
) then
593 redef fun max_health
do return 200.0
596 # Enemy that can shoot
600 redef fun max_health
do return 20.0
606 if 100.0.rand
> 90.0 then world
.powerups
.add
(new Powerup(self.center
, world
))
609 redef fun destroy
(world
)
612 world
.enemies
.remove
self
616 # Enemy walking on a platform
621 # Enemy with a jetpack
625 redef fun affected_by_gravity
do return false
628 # The main boss, the ISS taken over by bad guys
632 # TODO this should not subclass Human!
634 redef fun max_health
do return 2000.0
636 redef fun affected_by_gravity
do return false
652 # When has this powerup been created
653 var created
= 0.0 is writable
655 redef fun affected_by_gravity
do return false
657 new(center
: Point3d[Float], world
: World)
660 var powerup
: nullable Powerup = null
661 if v
== 0 then powerup
= new Ak47PU(center
, 5.0, 5.0)
662 if v
== 1 then powerup
= new RocketLauncherPU(center
, 5.0, 5.0)
663 if v
== 2 then powerup
= new Life(center
, 5.0, 5.0)
664 assert powerup
!= null
666 powerup
.inertia
.y
= -2.0
667 powerup
.created
= world
.t
671 # Apply this powerup to `player`
672 fun apply
(player
: Player) do end
674 redef fun update
(dt
, world
)
677 if world
.t
- created
> lifespan
then die
(world
)
680 redef fun destroy
(world
)
683 world
.powerups
.remove
(self)
687 # Weapon usable by a `Human` and `Boss`
688 abstract class Weapon
690 # Second at which the last shot was taken
693 # Number of bullets in the chamber, the weapon is lost when it reaches 0
694 var bullet_number
: Int is abstract
696 # Damage made by a single bullet
697 fun damage
: Float is abstract
699 # Seconds between each shot
700 fun cooldown
: Float is abstract
702 # Speed of the bullet when leaving the weapon
703 fun power
: Float is abstract
705 # Seconds to live of the bullets
706 fun bullet_lifespan
: Float is abstract
709 # Bullet fired by a `weapon`
710 abstract class Bullet
716 # `Weapon` that fired `self`
719 # Second at which this bullet was fired
720 var creation_time
: Float
722 redef fun affected_by_gravity
do return false
724 redef fun update
(dt
, world
)
727 if world
.t
- creation_time
>= weapon
.bullet_lifespan
then die world
731 fun hit_enemy
(body
: Body, world
: World)
733 body
.hit
(self.weapon
.damage
, world
)
738 # `Bullet` shot by the player
742 redef fun update
(dt
, world
)
746 for i
in world
.planes
do if self.intersects
(i
) then hit_enemy
(i
, world
)
747 for i
in world
.enemies
do if self.intersects
(i
) then hit_enemy
(i
, world
)
750 redef fun destroy
(world
) do
752 world
.player_bullets
.remove
self
756 # `Bullet` shot by an enemy
760 redef fun update
(dt
, world
) do
763 var player
= world
.player
764 if player
!= null and self.intersects
(player
) then hit_enemy
(player
, world
)
767 redef fun destroy
(world
) do
769 world
.enemy_bullets
.remove
self
773 # Fast shooting weapon
777 redef var damage
= 10.0
779 redef var cooldown
= 0.1
781 redef var power
= 70.0
783 redef var bullet_lifespan
= 3.0
785 redef var bullet_number
= 200
788 # Powerup to equip an `Ak47`
792 redef fun apply
(player
) do player
.weapon
= new Ak47
795 # Slow but powerful rocket launcher
799 redef var damage
= 500.0
801 redef var cooldown
= 1.5
803 redef var power
= 50.0
805 redef var bullet_lifespan
= 5.0
807 redef var bullet_number
= 20
810 # Powerup to equip a `RocketLauncher`
811 class RocketLauncherPU
814 redef fun apply
(player
) do player
.weapon
= new RocketLauncher
817 # Base weapon, a bit slow
821 redef var damage
= 10.0
823 redef var cooldown
= 0.3
825 redef var power
= 70.0
827 redef var bullet_lifespan
= 3.0
829 redef var bullet_number
= 10000
836 redef fun apply
(player
) do player
.health
+= 50.0
840 # Fuzzy value in `[self-variation..self+variation]`
841 fun &(variation
: Float): Float do return self - variation
+ 2.0*variation
.rand