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.
16 # This program is a fun game but also a good example of the scene2d module
21 # The ship of the player
25 # Where the player is going
26 var going_target
= new GoingTarget
28 # Activate the `going_target`
29 fun goes_to
(x
,y
: Int, speed
: Int)
33 going_target
.active
= true
34 var angle
= angle_to
(going_target
)
35 set_velocity
(angle
, speed
)
38 # Current forture of the player
39 var money
: Int = 0 is writable
41 # Number of basic bullets fired together
42 var nbshoots
: Int = 1 is writable
44 # Time bebore the player shoot again a basic bullet (cooldown)
49 var nbmissiles
: Int = 0 is writable
51 # Time bebore the player shoot again a missile (cooldown)
55 # Remainind time when the player is protected from impacts
58 # The associated play scene
59 # (mainly used to registed shoots)
62 init(scene
: PlayScene) do
76 else if self.y
> scene
.height
then
83 else if self.x
> scene
.width
then
88 # Update of the player protection if any
89 if protected_ttl
> 0 then protected_ttl
-= 1
91 # Need to shoot basic bullets?
96 for i
in [0..nbshoots
[ do
97 var shoot
= new Shoot(scene
)
101 shoot
.vx
= (i
- nbshoots
/ 2) * 100
102 scene
.player_shoots
.add
(shoot
)
106 # Need to shoot missiles?
107 if missile_ttl
> 0 then
109 else if nbmissiles
> 0 then
110 missile_ttl
= 500 / nbmissiles
111 var shoot
= new Missile(scene
)
116 scene
.player_shoots
.add
(shoot
)
121 # Time before the player is respawned by the scene
122 var respawn_ttl
: Int = 0
126 if self.protected_ttl
> 0 then return
127 self.scene
.explosion
(self.x
, self.y
, 10)
130 # Reset the position for respawn
131 self.x
= scene
.width
/ 2
132 self.y
= scene
.height
- 10000
135 self.respawn_ttl
= 50
140 # Sprites that may be hit by the player.
141 # Eq. enemies, bullets, loots, etc.
145 # What do do when self is hit by the player.
146 # By defaut, do nothing
147 fun hit_by_player
(player
: Player) do end
150 # Destination for the player (pointer position)
154 # true in on move, false if player is at rest
155 var active
= false is writable
162 redef fun hit_by_player
(player
)
164 if not active
then return
171 # A bullet shooted by a ship
175 # Was the shoot fired by an enemy.
176 # Since there is no frendly fire, it is important to distinguish ownership
177 var enemy
: Bool = false
179 # The scene of the sprite
180 # Is used with bound limits
183 init(scene
: PlayScene)
195 if self.y
< -100 * 100 or self.y
> scene
.height
+ 10000 or self.x
< -100 * 100 or self.x
> scene
.width
+ 10000 then
200 redef fun hit_by_player
(player
)
207 # A advanced bullet that aims a target (player or enemy)
211 # The target aquired by the missile
212 var target
: nullable Sprite = null
214 # When ttl is 0 then the angle stay fixed
215 # The angle is updated toward the target if ttl>0
222 # Do we still update the angle ?
223 if ttl
<= 0 then return
226 # Do we have a target?
227 var target
= self.target
228 if target
== null or not target
.exists
then return
230 # Just update the angle
231 var angle
= self.angle_to
(target
)
232 self.set_velocity
(angle
, 300)
237 # Various enemies exists, each kind has its own subclass
241 # The scene of the ship
242 # Is used to store created bullets or to get info about the player
245 # Time bebore the enemy shoot again (cooldown)
247 # The default value is used as a grace period to avoid a first shoot on
251 init(scene
: PlayScene)
256 scene
.enemies
.add
(self)
264 if self.y
> scene
.height
+ 10000 or self.x
< -100 * 100 or self.x
> scene
.width
+ 10000 then
265 # Note: no control on the top to let ennemies appear
270 if shoot_ttl
> 0 then
277 # Each enemy has its own kind of shoot strategy
278 # Note: is automatically called by update when shoot_ttl is expired
281 # Money given when the enemy is destroyed
282 fun loot
: Int is abstract
284 # What to do when the enemy is hit by a player shoot (or by the player himself)?
285 # By default it kill the enemy in an explosion and generate a loot
289 scene
.explosion
(self.x
, self.y
, 5)
291 var upmissile
= new UpMissile(scene
)
296 scene
.loots
.add
(upmissile
)
297 scene
.hitables
.add
(new LootArea(upmissile
, 2000))
299 for i
in [0..self.loot
[ do
300 var money
= new Money(scene
)
303 money
.set_velocity
(100.rand
.to_f
*pi
/50.0, (500+self.loot
).rand
)
304 scene
.loots
.add
(money
)
305 scene
.hitables
.add
(new LootArea(money
, 2000))
310 redef fun hit_by_player
(player
)
317 # Basic enemy, do not shoot
321 redef fun loot
do return 3
330 # Simple shooter of pairs of basic bullets
345 # two bullets shoot each time
346 for dx
in [-11, 11] do
347 var shoot
= new Shoot(scene
)
349 shoot
.x
= self.x
+ dx
* 100
350 shoot
.y
= self.bottom
352 scene
.enemy_shoots
.add
(shoot
)
356 redef fun loot
do return 5
359 # Enemy that shoot missiles
374 # The missile targets the player
375 var shoot
= new Missile(scene
)
378 shoot
.y
= self.bottom
380 shoot
.target
= scene
.player
381 scene
.enemy_shoots
.add
(shoot
)
384 redef fun loot
do return 10
387 # Enemy that shoot rings of basic bullets
403 var shoot
= new Shoot(scene
)
406 shoot
.y
= self.bottom
407 shoot
.set_velocity
(pi
/5.0*i
.to_f
, 500)
408 scene
.enemy_shoots
.add
(shoot
)
412 redef fun loot
do return 20
415 # Enemy with a turret that shoot burst of bullets toward the player
419 # The angle of the turret
420 var angle
: Float = 0.0
432 # Rotate the turret toward the player
433 var target
= scene
.player
434 if target
.exists
then
435 angle
= self.angle_to
(target
)
439 # Shoots come in burst
440 var nbshoots
: Int = 0
444 # Next shoot: is there still bullets in the burst?
445 if self.nbshoots
< 10 then
454 # Shoot with the turret angle
455 var shoot
= new Shoot(scene
)
459 shoot
.set_velocity
(angle
, 500)
460 scene
.enemy_shoots
.add
(shoot
)
463 redef fun loot
do return 20
466 # Enemy that rush directly on the player
480 # Try to target the player
481 var target
= scene
.player
482 if not target
.exists
then return
484 var angle
= self.angle_to
(target
)
485 self.set_velocity
(angle
, 600)
488 redef fun loot
do return 5
491 # The boss has two semi-independent arms
496 var left_part
: BossPart
499 var right_part
: BossPart
504 self.width
= 140 * 100
505 self.height
= 96 * 100
506 self.x
= scene
.width
/ 2
508 self.left_part
= new BossPart(self, -66*100)
509 self.right_part
= new BossPart(self, 66*100)
512 var flick_ttl
: Int = 0
516 if flick_ttl
> 0 then flick_ttl
-= 1
518 # Path of the boss (down then left<->right)
519 if self.y
< 20000 then
522 else if self.vx
== 0 then
525 else if self.x
> scene
.width
- 10000 and self.vx
> 0 then
527 else if self.x
< 10000 and self.vx
< 0 then
536 # Do not shoot if not ready
537 if self.vy
!= 0 then return
539 # Try to target the player
540 var target
= scene
.player
541 if not target
.exists
then return
543 # Next shoot: burst if no arms remains
544 if left_part
.exists
or right_part
.exists
then
550 # Shoot the player with a basic bullet
551 var shoot
= new Shoot(scene
)
554 shoot
.y
= self.bottom
555 var angle
= shoot
.angle_to
(target
)
556 shoot
.set_velocity
(angle
, 500)
557 scene
.enemy_shoots
.add
(shoot
)
560 redef fun loot
do return 100
566 # Protected while an arm remains
567 if left_part
.exists
or right_part
.exists
then return
574 scene
.explosion
(self.x
, self.y
, 30)
583 # The associated boss
586 # Relative x coordonate (center to center) of the arm
589 # Relative y coordonate (center to center) of the arm
590 var rely
: Int = 36 * 100
594 init(boss
: Boss, relx
: Int)
599 self.width
= 38 * 100
600 self.height
= 48 * 100
602 # Alternate the shoots of the arms
606 self.x
= boss
.x
+ relx
607 self.y
= boss
.y
+ rely
612 self.x
= boss
.x
+ relx
613 self.y
= boss
.y
+ rely
617 if flick_ttl
> 0 then flick_ttl
-= 1
622 # Do not shoot if not ready
623 if self.boss
.vy
!= 0 then return
628 # Shoot a missile that targets the player
629 var shoot
= new Missile(scene
)
632 shoot
.y
= self.bottom
634 shoot
.target
= scene
.player
635 scene
.enemy_shoots
.add
(shoot
)
638 var flick_ttl
: Int = 0
650 redef fun loot
do return 10
653 # Whatever reward or bonus that can be picked by the player
659 init(scene
: PlayScene)
666 # Magnet effect: The loot will move to the target if set
667 # See LootArea for details
668 var target
: nullable Sprite = null
675 if self.y
> scene
.height
+ 10000 then
679 var target
= self.target
680 if target
== null then
681 # Not magneted: deploy
683 # Heavy fuild friction to stops the explosion
684 # Loots are placed with a explosion, see `Enemy::hit'
685 self.vx
= self.vx
*7/8
686 self.vy
= self.vy
*7/8
688 # Background scroling
691 else if target
.exists
then
692 # Magneted: rush toward the target
693 var angle
= self.angle_to
(target
)
694 self.set_velocity
(angle
, 800)
697 # Magneted but dead target: reset the loot
709 redef fun hit_by_player
(player
)
713 if player
.money
> 100 then
720 # Increase the number of missiles
724 redef fun hit_by_player
(player
)
727 player
.nbmissiles
+= 1
731 # A loot area is an invisible field used to implement the magnet effets of loots
733 # * the loot is an invisible sprite with a hitbox larger than the loot hitbox
734 # * the lootbox remains centered on the loot
735 # * when the player hit the lootarea, then the loot is set to target the player
736 # * when the player hit the loot, then the player gains effectively the loot
740 # The associated loot
743 init(loot
: Loot, radius
: Int)
746 self.width
= radius
* 2 + loot
.width
747 self.height
= radius
* 2 + loot
.height
752 # position remains centered on the loot
757 if not loot
.exists
then self.exists
= false
759 # the super is useless but it is a good practice to call it
763 redef fun hit_by_player
(player
)
768 # The loot now targets the player
773 # A non interactive element of an explosion
774 # A real explosion is made of many Explosion object
775 # Use the `PlayScene::explosion` method to generate a full explosion
779 # Time before the sprite vanishes
784 # Heavy fuild friction to stops the explosion
785 self.vx
= self.vx
*7/8
786 self.vy
= self.vy
*7/8
788 # Background scrolling
802 # A star is a non-interactive background element
803 # Stars are used to simulate a continuous global scroling
807 # The scene of the sprite
808 # Is used with bound limits
811 init(scene
: ShotScene)
814 # Randomely places stars on the plane
815 self.x
= scene
.width
.rand
816 self.y
= scene
.height
.rand
817 self.vy
= 40.rand
+ 11
824 # Replace the star on the top
825 if self.y
> scene
.height
then
826 self.y
= 200.rand
* -100
827 self.x
= scene
.width
.rand
828 self.vy
= 40.rand
+ 11
836 # When a scene need to be replaced, just assign the next_scene to a non null value
837 var next_scene
: nullable ShotScene = null is writable
839 # The width of the whole scene
840 var width
: Int is writable
842 # The height of the whole scene
843 var height
: Int is writable
852 # The main play state
859 # Shoots of the player
860 var player_shoots
= new LiveGroup[Shoot]
863 var enemies
= new LiveGroup[Enemy]
866 var enemy_shoots
= new LiveGroup[Shoot]
869 var loots
= new LiveGroup[Loot]
871 # Non active stuff like explosions
872 var pasive_stuff
= new LiveGroup[LiveObject]
874 # Background stuff like stars
875 var background
= new LiveGroup[LiveObject]
877 # All other hitable sprites
878 var hitables
= new LiveGroup[Hitable]
881 var sprites
= new LiveGroup[LiveObject]
886 self.player
= new Player(self)
887 player
.x
= self.width
/ 2
888 player
.y
= self.height
- 10000
889 self.sprites
.add
(background
)
890 self.sprites
.add
(pasive_stuff
)
891 self.sprites
.add
(loots
)
892 self.sprites
.add
(player_shoots
)
893 self.sprites
.add
(enemy_shoots
)
894 self.sprites
.add
(enemies
)
895 self.sprites
.add
(self.player
)
896 self.sprites
.add
(hitables
)
899 background
.add
(new Star(self))
902 hitables
.add
(player
.going_target
)
905 # Generate an explosion
906 fun explosion
(x
, y
: Int, radius
: Int)
908 # Project explosion parts from the given position
909 # The strong friction and the short ttl of each part will achieve the effect
910 for i
in [0..radius
[ do
911 var ex
= new Explosion
914 ex
.set_velocity
(100.rand
.to_f
*pi
/50.0, (50*radius
).rand
)
915 ex
.ttl
+= radius
.rand
920 var enemy_remains
: Int = 15
921 var boss_wait_ttl
: Int = 0
922 var boss
: nullable Boss
929 if enemy_remains
== 0 then
930 if boss_wait_ttl
> 0 then
932 else if boss
== null then
933 boss
= new Boss(self)
935 else if not boss
.exists
then
938 else if 100.rand
< 1 then
940 if enemy_remains
== 0 then
946 enemy
= new Enemy0(self)
947 else if rnd
< 60 then
948 enemy
= new Enemy1(self)
949 else if rnd
< 70 then
950 enemy
= new EnemyKamikaze(self)
951 else if rnd
< 90 then
952 enemy
= new Enemy2(self)
953 else if rnd
< 95 then
954 enemy
= new Enemy3(self)
956 enemy
= new Enemy4(self)
958 enemy
.x
= (self.width
- 20000).rand
+ 10000
959 enemy
.vy
= 200.rand
+ 100
961 enemy
.vx
= 200.rand
- 100
965 for ps
in player_shoots
do
966 if not ps
.exists
then continue
967 var target
: nullable Enemy = null
968 var td
= 100000 # big int
970 if not e
.exists
then continue
971 if ps
.overlaps
(e
) then
975 var d
= (e
.x
- ps
.x
).abs
+ (e
.y
- ps
.y
).abs
981 if ps
isa Missile and (ps
.target
== null or not ps
.target
.exists
) then
987 if not e
.exists
then continue
988 if player
.exists
and player
.overlaps
(e
) then
989 e
.hit_by_player
(player
)
992 for s
in enemy_shoots
do
993 if not s
.exists
then continue
994 if player
.exists
and player
.overlaps
(s
) then
995 s
.hit_by_player
(player
)
999 if not l
.exists
then continue
1000 if player
.exists
and player
.overlaps
(l
) then
1001 l
.hit_by_player
(player
)
1004 for l
in hitables
do
1005 if not l
.exists
then continue
1006 if player
.exists
and player
.overlaps
(l
) then
1007 l
.hit_by_player
(player
)
1010 if not player
.exists
then
1011 if player
.respawn_ttl
> 0 then
1012 player
.respawn_ttl
-= 1
1014 player
.exists
= true
1015 player
.protected_ttl
= 100
1016 self.sprites
.add
(self.player
)
1027 var sprites
= new LiveGroup[LiveObject]
1032 for i
in [0..100[ do
1033 sprites
.add
(new Star(self))
1037 var play
: Bool = false is writable
1044 if not play
then return
1049 next_scene
= new PlayScene(width
,height
)
1056 print
"Headless run"
1057 # Only run the playscene
1058 var scene
= new PlayScene(80000,60000)
1060 scene
.player
.nbshoots
= 5
1061 scene
.player
.nbmissiles
= 5
1065 if args
.length
> 0 then
1066 turns
= args
.first
.to_i
1068 for i
in [0..turns
[ do
1069 for j
in [0..10000[ do
1072 print
"{i}: money={scene.player.money} enemies={scene.enemies.length} shoots={scene.player_shoots.length}"