907b8b1a4058a97426ebf90d0d50eabd47eeca15
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 writable = 0
41 # Number of basic bullets fired together
42 var nbshoots
: Int writable = 1
44 # Time bebore the player shoot again a basic bullet (cooldown)
49 var nbmissiles
: Int writable = 0
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
writable = false
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
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
324 # Simple shooter of paris of basic bullets
333 # two bullets shoot each time
334 for dx
in [-11, 11] do
335 var shoot
= new Shoot(scene
)
337 shoot
.x
= self.x
+ dx
* 100
338 shoot
.y
= self.bottom
340 scene
.enemy_shoots
.add
(shoot
)
344 redef fun loot
do return 5
347 # Enemy that shoot missiles
356 # The missile targets the player
357 var shoot
= new Missile(scene
)
360 shoot
.y
= self.bottom
362 shoot
.target
= scene
.player
363 scene
.enemy_shoots
.add
(shoot
)
366 redef fun loot
do return 10
369 # Enem that shoot rings of basic bullets
379 var shoot
= new Shoot(scene
)
382 shoot
.y
= self.bottom
383 shoot
.set_velocity
(pi
/5.0*i
.to_f
, 500)
384 scene
.enemy_shoots
.add
(shoot
)
388 redef fun loot
do return 20
391 # Enemy with a turret that shoot burst of bullets toward the player
395 # The angle of the turret
396 var angle
: Float = 0.0
402 # Rotate the turret toward the player
403 var target
= scene
.player
404 if target
.exists
then
405 angle
= self.angle_to
(target
)
409 # Shoots come in burst
410 var nbshoots
: Int = 0
414 # Next shoot: is there still bullets in the burst?
415 if self.nbshoots
< 10 then
424 # Shoot with the turret angle
425 var shoot
= new Shoot(scene
)
429 shoot
.set_velocity
(angle
, 500)
430 scene
.enemy_shoots
.add
(shoot
)
433 redef fun loot
do return 20
436 # Enemy that rush directly on the player
444 # Try to target the player
445 var target
= scene
.player
446 if not target
.exists
then return
448 var angle
= self.angle_to
(target
)
449 self.set_velocity
(angle
, 600)
452 redef fun loot
do return 5
455 # The boss has two semi-independent arms
460 var left_part
: BossPart
463 var right_part
: BossPart
468 self.width
= 128 * 100
469 self.height
= 100 * 100
470 self.x
= scene
.width
/ 2
472 self.left_part
= new BossPart(self, -48*100)
473 self.right_part
= new BossPart(self, 48*100)
476 var flick_ttl
: Int = 0
480 if flick_ttl
> 0 then flick_ttl
-= 1
482 # Path of the boss (down then left<->right)
483 if self.y
< 20000 then
486 else if self.vx
== 0 then
489 else if self.x
> scene
.width
- 10000 and self.vx
> 0 then
491 else if self.x
< 10000 and self.vx
< 0 then
500 # Do not shoot if not ready
501 if self.vy
!= 0 then return
503 # Try to target the player
504 var target
= scene
.player
505 if not target
.exists
then return
507 # Next shoot: burst if no arms remains
508 if left_part
.exists
or right_part
.exists
then
514 # Shoot the player with a basic bullet
515 var shoot
= new Shoot(scene
)
518 shoot
.y
= self.bottom
519 var angle
= shoot
.angle_to
(target
)
520 shoot
.set_velocity
(angle
, 500)
521 scene
.enemy_shoots
.add
(shoot
)
524 redef fun loot
do return 100
530 # Protected while an arm remains
531 if left_part
.exists
or right_part
.exists
then return
538 scene
.explosion
(self.x
, self.y
, 30)
547 # The associated boss
550 # Relative x coordonate (center to center) of the arm
553 # Relative y coordonate (center to center) of the arm
554 var rely
: Int = 36 * 100
558 init(boss
: Boss, relx
: Int)
563 self.width
= 32 * 100
564 self.height
= 60 * 100
566 # Alternate the shoots of the arms
570 self.x
= boss
.x
+ relx
571 self.y
= boss
.y
+ rely
576 self.x
= boss
.x
+ relx
577 self.y
= boss
.y
+ rely
581 if flick_ttl
> 0 then flick_ttl
-= 1
586 # Do not shoot if not ready
587 if self.boss
.vy
!= 0 then return
592 # Shoot a missile that targets the player
593 var shoot
= new Missile(scene
)
596 shoot
.y
= self.bottom
598 shoot
.target
= scene
.player
599 scene
.enemy_shoots
.add
(shoot
)
602 var flick_ttl
: Int = 0
614 redef fun loot
do return 10
617 # Whatever reward or bonus that can be picked by the player
623 init(scene
: PlayScene)
630 # Magnet effect: The loot will move to the target if set
631 # See LootArea for details
632 var target
: nullable Sprite = null
639 if self.y
> scene
.height
+ 10000 then
643 var target
= self.target
644 if target
== null then
645 # Not magneted: deploy
647 # Heavy fuild friction to stops the explosion
648 # Loots are placed with a explosion, see `Enemy::hit'
649 self.vx
= self.vx
*7/8
650 self.vy
= self.vy
*7/8
652 # Background scroling
655 else if target
.exists
then
656 # Magneted: rush toward the target
657 var angle
= self.angle_to
(target
)
658 self.set_velocity
(angle
, 800)
661 # Magneted but dead target: reset the loot
673 redef fun hit_by_player
(player
)
677 if player
.money
> 100 then
684 # Increase the number of missiles
688 redef fun hit_by_player
(player
)
691 player
.nbmissiles
+= 1
695 # A loot area is an invisible field used to implement the magnet effets of loots
697 # * the loot is an invisible sprite with a hitbox larger than the loot hitbox
698 # * the lootbox remains centered on the loot
699 # * when the player hit the lootarea, then the loot is set to target the player
700 # * when the player hit the loot, then the player gains effectively the loot
704 # The associated loot
707 init(loot
: Loot, radius
: Int)
710 self.width
= radius
* 2 + loot
.width
711 self.height
= radius
* 2 + loot
.height
716 # position remains centered on the loot
721 if not loot
.exists
then self.exists
= false
723 # the super is useless but it is a good practice to call it
727 redef fun hit_by_player
(player
)
732 # The loot now targets the player
737 # A non interactive element of an explosion
738 # A real explosion is made of many Explosion object
739 # Use the `PlayScene::explosion` method to generate a full explosion
743 # Time before the sprite vanishes
748 # Heavy fuild friction to stops the explosion
749 self.vx
= self.vx
*7/8
750 self.vy
= self.vy
*7/8
752 # Background scrolling
766 # A star is a non-interactive background element
767 # Stars are used to simulate a continuous global scroling
771 # The scene of the sprite
772 # Is used with bound limits
775 init(scene
: ShotScene)
778 # Randomely places stars on the plane
779 self.x
= scene
.width
.rand
780 self.y
= scene
.height
.rand
781 self.vy
= 40.rand
+ 11
788 # Replace the star on the top
789 if self.y
> scene
.height
then
790 self.y
= 200.rand
* -100
791 self.x
= scene
.width
.rand
792 self.vy
= 40.rand
+ 11
800 # When a scene need to be replaced, just assign the next_scene to a non null value
801 var next_scene
: nullable ShotScene writable = null
803 # The width of the whole scene
804 var width
: Int writable
806 # The height of the whole scene
807 var height
: Int writable
816 # The main play state
823 # Shoots of the player
824 var player_shoots
= new LiveGroup[Shoot]
827 var enemies
= new LiveGroup[Enemy]
830 var enemy_shoots
= new LiveGroup[Shoot]
833 var loots
= new LiveGroup[Loot]
835 # Non active stuff like explosions
836 var pasive_stuff
= new LiveGroup[LiveObject]
838 # Background stuff like stars
839 var background
= new LiveGroup[LiveObject]
841 # All other hitable sprites
842 var hitables
= new LiveGroup[Hitable]
845 var sprites
= new LiveGroup[LiveObject]
850 self.player
= new Player(self)
851 player
.x
= self.width
/ 2
852 player
.y
= self.height
- 10000
853 self.sprites
.add
(background
)
854 self.sprites
.add
(pasive_stuff
)
855 self.sprites
.add
(loots
)
856 self.sprites
.add
(player_shoots
)
857 self.sprites
.add
(enemy_shoots
)
858 self.sprites
.add
(enemies
)
859 self.sprites
.add
(self.player
)
860 self.sprites
.add
(hitables
)
863 background
.add
(new Star(self))
866 hitables
.add
(player
.going_target
)
869 # Generate an explosion
870 fun explosion
(x
, y
: Int, radius
: Int)
872 # Project explosion parts from the given position
873 # The strong friction and the short ttl of each part will achieve the effect
874 for i
in [0..radius
[ do
875 var ex
= new Explosion
878 ex
.set_velocity
(100.rand
.to_f
*pi
/50.0, (50*radius
).rand
)
879 ex
.ttl
+= radius
.rand
884 var enemy_remains
: Int = 15
885 var boss_wait_ttl
: Int = 0
886 var boss
: nullable Boss
893 if enemy_remains
== 0 then
894 if boss_wait_ttl
> 0 then
896 else if boss
== null then
897 boss
= new Boss(self)
899 else if not boss
.exists
then
902 else if 100.rand
< 1 then
904 if enemy_remains
== 0 then
910 enemy
= new Enemy0(self)
911 else if rnd
< 60 then
912 enemy
= new Enemy1(self)
913 else if rnd
< 70 then
914 enemy
= new EnemyKamikaze(self)
915 else if rnd
< 90 then
916 enemy
= new Enemy2(self)
917 else if rnd
< 95 then
918 enemy
= new Enemy3(self)
920 enemy
= new Enemy4(self)
922 enemy
.x
= (self.width
- 20000).rand
+ 10000
923 enemy
.vy
= 200.rand
+ 100
925 enemy
.vx
= 200.rand
- 100
929 for ps
in player_shoots
do
930 if not ps
.exists
then continue
931 var target
: nullable Enemy = null
932 var td
= 100000 # big int
934 if not e
.exists
then continue
935 if ps
.overlaps
(e
) then
939 var d
= (e
.x
- ps
.x
).abs
+ (e
.y
- ps
.y
).abs
945 if ps
isa Missile and (ps
.target
== null or not ps
.target
.exists
) then
951 if not e
.exists
then continue
952 if player
.exists
and player
.overlaps
(e
) then
953 e
.hit_by_player
(player
)
956 for s
in enemy_shoots
do
957 if not s
.exists
then continue
958 if player
.exists
and player
.overlaps
(s
) then
959 s
.hit_by_player
(player
)
963 if not l
.exists
then continue
964 if player
.exists
and player
.overlaps
(l
) then
965 l
.hit_by_player
(player
)
969 if not l
.exists
then continue
970 if player
.exists
and player
.overlaps
(l
) then
971 l
.hit_by_player
(player
)
974 if not player
.exists
then
975 if player
.respawn_ttl
> 0 then
976 player
.respawn_ttl
-= 1
979 player
.protected_ttl
= 100
980 self.sprites
.add
(self.player
)
991 var sprites
= new LiveGroup[LiveObject]
997 sprites
.add
(new Star(self))
1001 var play
: Bool writable = false
1008 if not play
then return
1013 next_scene
= new PlayScene(width
,height
)
1019 print
"Headless run"
1020 # Only run the playscene
1021 var scene
= new PlayScene(80000,60000)
1023 scene
.player
.nbshoots
= 5
1024 scene
.player
.nbmissiles
= 5
1028 if args
.length
> 0 then
1029 turns
= args
.first
.to_i
1031 for i
in [0..turns
[ do
1032 for j
in [0..10000[ do
1035 print
"{i}: money={scene.player.money} enemies={scene.enemies.length} shoots={scene.player_shoots.length}"