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 and scoring when applicable
232 # Calls `destroy` by default.
233 fun die
(world
: World)
239 # Destroy this objects and most references to it
240 fun destroy
(world
: World) do end
245 redef fun top
do return center
.y
+ height
/ 2.0
246 redef fun bottom
do return center
.y
- height
/ 2.0
247 redef fun left
do return center
.x
- width
/ 2.0
248 redef fun right
do return center
.x
+ width
/ 2.0
251 # Something to stand on
252 abstract class Platform
255 redef fun mass
do return 20.0
257 redef fun affected_by_gravity
do return false
259 # Planes slow down when close to the player, `old_inertia` is the speed before it slowed down
260 private var old_inertia_y
: nullable Float = null
262 # Enemy the spawned on the plane
263 var enemy
: nullable WalkingEnemy = null is writable
268 world
.explode
(center
, width
)
270 if 100.0.rand
> 50.0 then world
.powerups
.add
(new Powerup(self.center
, world
))
273 redef fun destroy
(world
)
275 world
.planes
.remove
self
279 # Distance to the player
280 fun player_dist
(world
: World): Float do
282 if p
== null then return 0.0
284 var dst
= center
.x
- px
288 # Has this plane slowed down because it is close to the player?
289 private var slowed_down
= false
291 # Has this plane already accelerated because it got far from the player?
292 private var accelerated
= false
294 redef fun update
(dt
, world
)
296 # High friction on the Y axis to stabilize after an `apply_force`
301 # Slow down if close to the player
302 var dst
= player_dist
(world
)
304 if not slowed_down
then
305 old_inertia_y
= inertia
.y
307 var speed
= 10.0 + 15.0.rand
308 if inertia
.x
< 0.0 then
310 else inertia
.x
= speed
315 if enemy
!= null then
316 enemy
.inertia
.x
= inertia
.x
317 enemy
.inertia
.y
= inertia
.y
318 enemy
.inertia
.z
= inertia
.z
321 else if dst
> 30.0 and not accelerated
then
322 var oi
= old_inertia_y
323 if oi
== null then return
331 # Airplane, the basic `Platform`
336 # Helicopter, the player rotates on its blades
341 # Parachute to slow down the player
345 redef var affected_by_gravity
= false
347 redef fun update
(dt
, world
) do
352 center
.x
= world
.player
.center
.x
353 center
.y
= world
.player
.center
.y
+ 5.0
361 # Input direction in `[-1.0 .. 1.0]`
362 var moving
= 0.0 is writable
364 # `moving` speed when on a plane, applied directly to `center`
365 var walking_speed
= 20.0
367 # `moving` speed when in freefall, applied to `inertia`
368 var freefall_accel
= 150.0
370 # Acceleration on the X axis applied when jumping
371 var jump_accel
= 24.0
373 # On which plane is standing `self`? if any.
374 var plane
: nullable Platform = null
376 # Position in relation to `plane`
377 private var dx_to_plane
= 0.0
382 # Rotation status when on a copter bladers, used by `update`
383 private var ltr
= false
385 # Is the parachute currently deployed?
386 var parachute_deployed
= false
388 redef var affected_by_gravity
= true
390 # Altitude (in meters)
391 fun altitude
: Float do return center
.y
393 # Apply a jump from input
397 if plane
!= null then
398 # On solid plane, jump
400 inertia
.x
= plane
.inertia
.x
+ moving
* jump_accel
406 # Deploy parachute on input
410 if plane
== null and not parachute_deployed
then
412 parachute_deployed
= true
419 redef fun update
(dt
, world
)
421 if not is_alive
then return
423 if altitude
>= world
.boss_altitude
then
425 affected_by_gravity
= false
435 if on_plane
!= null then
437 if not on_plane
.is_alive
then
443 if on_plane
!= null then
444 # On a plane, applying special physics do not call super!
447 center
.x
= on_plane
.center
.x
+ dx_to_plane
+ moving
* walking_speed
* dt
448 center
.y
= on_plane
.top
+ height
/ 2.0
449 if plane
isa Helicopter then
450 center
.y
= plane
.top
+ height
/ 2.0 + 1.5
451 var left_blade
= plane
.center
.x
- 5.0
452 var right_blade
= plane
.center
.x
+ 5.0
454 var blade_speed
= 0.5
456 if px
>= right_blade
then ltr
= false
457 center
.x
= on_plane
.center
.x
+ dx_to_plane
+ moving
* walking_speed
* dt
+ blade_speed
459 if px
<= left_blade
then ltr
= true
460 center
.x
= on_plane
.center
.x
+ dx_to_plane
+ moving
* walking_speed
* dt
- blade_speed
465 if not (plane
.left
< right
and plane
.right
> left
) then
471 # Only influence the inertia
472 inertia
.x
+= moving
* freefall_accel
* dt
477 if parachute_deployed
then
478 if inertia
.y
< -10.0 then inertia
.y
= -10.0
481 # Detect collision with planes
482 for plane
in world
.planes
do # TODO optimize with quad tree
483 if plane
.left
< right
and plane
.right
> left
then
484 if old_y
> plane
.top
and bottom
<= plane
.top
then
485 if world
.parachute
!= null then
486 world
.parachute
.destroy
(world
)
487 world
.parachute
= null
489 parachute_deployed
= false
491 plane
.inertia
.y
+= inertia
.y
/ plane
.mass
497 center
.y
= plane
.top
+ height
/ 2.0
498 if plane
isa Helicopter then
499 center
.y
= plane
.top
+ height
/ 2.0 + 4.0
507 on_plane
= self.plane
508 if on_plane
!= null then
509 dx_to_plane
= center
.x
- on_plane
.center
.x
512 # Die when hitting the ground
513 if bottom
<= 0.0 then
522 # Is the weapon ready to shoot?
523 fun can_shoot
(world
: World): Bool
525 return is_alive
and world
.t
- weapon
.last_shot
>= weapon
.cooldown
528 # Open fire at `angle`!
529 fun shoot
(angle
: Float, world
: World)
531 if not can_shoot
(world
) then return
533 var x_inertia
= angle
.cos
* weapon
.power
534 var y_inertia
= angle
.sin
* weapon
.power
535 var new_center
= new Point3d[Float](self.center
.x
, self.center
.y
, self.center
.z
- 0.2)
537 var bullet
= register_bullet
(new_center
, angle
, world
)
538 bullet
.inertia
.x
= x_inertia
539 bullet
.inertia
.y
= y_inertia
540 weapon
.last_shot
= world
.t
543 # Add a bullet, which type depends on `self`
544 protected fun register_bullet
(new_center
: Point3d[Float], angle
: Float, world
: World): Bullet
546 var bullet
= new EnemyBullet(new_center
, 2.0, 2.0, angle
, self.weapon
, world
.t
)
547 world
.enemy_bullets
.add
(bullet
)
556 # Basic starting weapon to which `self` reverts when out of bullets for powerup
557 var basic_weapon
= new Ak47
559 redef fun shoot
(angle
, world
)
563 # Consume limited bullets from powerups
564 if can_shoot
(world
) then
565 weapon
.bullet_number
-= 1
566 if weapon
.bullet_number
<= 0 then self.weapon
= basic_weapon
570 redef fun register_bullet
(new_center
, angle
, world
)
572 var bullet
= new PlayerBullet(new_center
, 2.0, 2.0, angle
, self.weapon
, world
.t
)
573 world
.player_bullets
.add
(bullet
)
577 redef fun update
(dt
, world
)
582 for p
in world
.powerups
.reverse_iterator
do
583 if self.intersects
(p
) then
590 redef fun max_health
do return 200.0
593 # Enemy that can shoot
597 redef fun max_health
do return 20.0
603 if 100.0.rand
> 90.0 then world
.powerups
.add
(new Powerup(self.center
, world
))
606 redef fun destroy
(world
)
609 world
.enemies
.remove
self
613 # Enemy walking on a platform
618 # Enemy with a jetpack
622 redef fun affected_by_gravity
do return false
625 # The main boss, the ISS taken over by bad guys
629 # TODO this should not subclass Human!
631 redef fun max_health
do return 2000.0
633 redef fun affected_by_gravity
do return false
649 # When has this powerup been created
650 var created
= 0.0 is writable
652 redef fun affected_by_gravity
do return false
654 new(center
: Point3d[Float], world
: World)
657 var powerup
: nullable Powerup = null
658 if v
== 0 then powerup
= new Ak47PU(center
, 5.0, 5.0)
659 if v
== 1 then powerup
= new RocketLauncherPU(center
, 5.0, 5.0)
660 if v
== 2 then powerup
= new Life(center
, 5.0, 5.0)
661 assert powerup
!= null
663 powerup
.inertia
.y
= -2.0
664 powerup
.created
= world
.t
668 # Apply this powerup to `player`
669 fun apply
(player
: Player) do end
671 redef fun update
(dt
, world
)
674 if world
.t
- created
> lifespan
then die
(world
)
677 redef fun destroy
(world
)
680 world
.powerups
.remove
(self)
684 # Weapon usable by a `Human` and `Boss`
685 abstract class Weapon
687 # Second at which the last shot was taken
690 # Number of bullets in the chamber, the weapon is lost when it reaches 0
691 var bullet_number
: Int is abstract
693 # Damage made by a single bullet
694 fun damage
: Float is abstract
696 # Seconds between each shot
697 fun cooldown
: Float is abstract
699 # Speed of the bullet when leaving the weapon
700 fun power
: Float is abstract
702 # Seconds to live of the bullets
703 fun bullet_lifespan
: Float is abstract
706 # Bullet fired by a `weapon`
707 abstract class Bullet
713 # `Weapon` that fired `self`
716 # Second at which this bullet was fired
717 var creation_time
: Float
719 redef fun affected_by_gravity
do return false
721 redef fun update
(dt
, world
)
724 if world
.t
- creation_time
>= weapon
.bullet_lifespan
then destroy world
728 fun hit_enemy
(body
: Body, world
: World)
730 body
.hit
(self.weapon
.damage
, world
)
735 # `Bullet` shot by the player
739 redef fun update
(dt
, world
)
743 for i
in world
.planes
do if self.intersects
(i
) then hit_enemy
(i
, world
)
744 for i
in world
.enemies
do if self.intersects
(i
) then hit_enemy
(i
, world
)
747 redef fun destroy
(world
) do
749 world
.player_bullets
.remove
self
753 # `Bullet` shot by an enemy
757 redef fun update
(dt
, world
) do
760 var player
= world
.player
761 if player
!= null and self.intersects
(player
) then hit_enemy
(player
, world
)
764 redef fun destroy
(world
) do
766 world
.enemy_bullets
.remove
self
770 # Fast shooting weapon
774 redef var damage
= 10.0
776 redef var cooldown
= 0.1
778 redef var power
= 70.0
780 redef var bullet_lifespan
= 3.0
782 redef var bullet_number
= 200
785 # Powerup to equip an `Ak47`
789 redef fun apply
(player
) do player
.weapon
= new Ak47
792 # Slow but powerful rocket launcher
796 redef var damage
= 500.0
798 redef var cooldown
= 1.5
800 redef var power
= 50.0
802 redef var bullet_lifespan
= 5.0
804 redef var bullet_number
= 20
807 # Powerup to equip a `RocketLauncher`
808 class RocketLauncherPU
811 redef fun apply
(player
) do player
.weapon
= new RocketLauncher
814 # Base weapon, a bit slow
818 redef var damage
= 10.0
820 redef var cooldown
= 0.3
822 redef var power
= 70.0
824 redef var bullet_lifespan
= 3.0
826 redef var bullet_number
= 10000
833 redef fun apply
(player
) do player
.health
+= 50.0
837 # Fuzzy value in `[self-variation..self+variation]`
838 fun &(variation
: Float): Float do return self - variation
+ 2.0*variation
.rand