~alaxa27/ultimate-smash-friends/mirror_trunk

« back to all changes in this revision

Viewing changes to pkg/ultimate-smash-friends_1.0-beta-1/usr/lib/usf_modules/game.py

  • Committer: gaby
  • Date: 2009-11-30 17:03:16 UTC
  • Revision ID: gaby@ks22672.kimsufi.com-20091130170316-6lm3v7q0torulfab
adding code package

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# set encoding: utf-8
 
3
################################################################################
 
4
# copyright 2008 Gabriel Pettier <gabriel.pettier@gmail.com>                   #
 
5
#                                                                              #
 
6
# This file is part of UltimateSmashFriends                                    #
 
7
#                                                                              #
 
8
# UltimateSmashFriends is free software: you can redistribute it and/or modify #
 
9
# it under the terms of the GNU General Public License as published by the Free#
 
10
# Software Foundation, either version 3 of the License, or (at your option) any#
 
11
# later version.                                                               #
 
12
#                                                                              #
 
13
# UltimateSmashFriends is distributed in the hope that it will be useful, but  #
 
14
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or#
 
15
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for    #
 
16
# more details.                                                                #
 
17
#                                                                              #
 
18
# You should have received a copy of the GNU General Public License along with #
 
19
# UltimateSmashFriends.  If not, see <http://www.gnu.org/licenses/>.           #
 
20
################################################################################
 
21
 
 
22
# standards import
 
23
import pygame
 
24
import time
 
25
import math
 
26
import os
 
27
 
 
28
import sys
 
29
 
 
30
# my modules import
 
31
from loaders import image
 
32
import music
 
33
import animations
 
34
import entity
 
35
#from AI import AI
 
36
import timed_event
 
37
import network
 
38
from level import Level
 
39
from controls import Controls
 
40
from config import config
 
41
 
 
42
from debug_utils import LOG, draw_rect
 
43
 
 
44
if not pygame.font: LOG().log('Warning, fonts disabled')
 
45
if not pygame.mixer: LOG().log('Warning, sound disabled')
 
46
 
 
47
class BadPlayersNetworkParamError(Exception):
 
48
    """
 
49
    Raised when the player params of a network server game is not correct.
 
50
 
 
51
    """
 
52
    pass
 
53
 
 
54
class Game (object):
 
55
    """
 
56
    The game base object, initiate and update everything when in game (not in
 
57
    menu).
 
58
 
 
59
    """
 
60
    def __init__(self, screen, level="biglevel", players_=(None, None, None, None)):
 
61
        """
 
62
        Initialize a game with a list of player and a level,
 
63
        level is the basename of the level in levels/
 
64
 
 
65
        """
 
66
        self.ended = False
 
67
        self.type = 'local'
 
68
        self.screen = screen
 
69
 
 
70
        self.LOG = LOG()
 
71
        self.events = []
 
72
 
 
73
        self.gametime = 0
 
74
 
 
75
        # time for a loading screen ain't it?
 
76
        # loading level
 
77
        self.level_place = [0, 0]
 
78
        self.game_font = pygame.font.Font(None, 50)
 
79
        image_src = os.path.join(
 
80
                    config['MEDIA_DIRECTORY'],
 
81
                    'misc',
 
82
                    'loading.png'
 
83
                    )
 
84
 
 
85
        self.heart = os.path.join(
 
86
                    config['MEDIA_DIRECTORY'],
 
87
                    'misc',
 
88
                    'heart.png'
 
89
                    )
 
90
 
 
91
        self.screen.blit(image(image_src)[0],(0,0))
 
92
        self.screen.blit(
 
93
                self.game_font.render(
 
94
                    "level...",
 
95
                    True,
 
96
                    pygame.color.Color("white")
 
97
                    ),
 
98
                ( 30, 4*config['SIZE'][1]/5 )
 
99
                )
 
100
 
 
101
        pygame.display.flip()
 
102
 
 
103
        self.level = Level(level)
 
104
        self.tmp_surface = self.screen.copy()
 
105
 
 
106
        # loading players
 
107
        self.screen.blit(image(image_src)[0],(0,0))
 
108
        self.screen.blit(
 
109
                self.game_font.render(
 
110
                    "players...",
 
111
                    True,
 
112
                    pygame.color.Color("white")
 
113
                    ),
 
114
                ( 30, 4*config['SIZE'][1]/5 )
 
115
                )
 
116
 
 
117
        pygame.display.flip()
 
118
 
 
119
        self.players = []
 
120
        LOG().log('loading players')
 
121
        for i,player in enumerate(players_):
 
122
            LOG().log('player '+str(i)+' loaded')
 
123
            if player is not None:
 
124
                #LOG().log(player)
 
125
                if player.split(os.sep)[1][:2] == "AI":
 
126
                    self.players.append(
 
127
                            AI(
 
128
                                i+1,
 
129
                                self,
 
130
                                player.split('/')[0]\
 
131
                                +'/'\
 
132
                                +player.split('/')[1][2:],
 
133
                                ((i+1)*config['SIZE'][0]/5,100)
 
134
                              )
 
135
                            )
 
136
                else:
 
137
                    self.players.append(
 
138
                            entity.Entity(
 
139
                                i+1,
 
140
                                self,
 
141
                                player,
 
142
                                ((i+1)*config['SIZE'][0]/5,100)
 
143
                                )
 
144
                            )
 
145
 
 
146
        # various other initialisations
 
147
        self.last_clock = time.time()
 
148
        self.icon_space = config['SIZE'][0]/len(self.players)
 
149
        self.font = pygame.font.Font(None, 20)
 
150
        self.zoom = 1
 
151
        #self.testimage=load_image(os.path.join(MEDIA_DIRECTORY,'items','item-heal'+os.extsep+'png')[0]
 
152
 
 
153
        self.items = []
 
154
 
 
155
        ## adding test events.
 
156
        self.events.append(
 
157
            timed_event.ItemShower(
 
158
                (None, None),
 
159
                {
 
160
                'freq': 15,
 
161
                'world': self
 
162
                }
 
163
                )
 
164
            )
 
165
 
 
166
        # insert players in game
 
167
        #LOG().log('players insertion in game')
 
168
        for pl in self.players:
 
169
            self.events.append(
 
170
                timed_event.DropPlayer(
 
171
                    (None, self.gametime),
 
172
                    params={
 
173
                    'world':self,
 
174
                    'entity':pl,
 
175
                    'gametime' : self.gametime
 
176
                    }
 
177
                    )
 
178
                )
 
179
 
 
180
        # a countdown to the game end
 
181
        self.ending = 5.0
 
182
        #LOG().log('DONE')
 
183
 
 
184
    def __del__(self):
 
185
        """
 
186
        destructor method of game, free as much resources as possible.
 
187
 
 
188
        """
 
189
        LOG().log('game deleted')
 
190
        del(self.__dict__)
 
191
        del(self)
 
192
 
 
193
    def addItem(self, item='heal', place=(550,50), reversed=False,vector=(0,0)):
 
194
        """
 
195
        Insert an item into game.
 
196
 
 
197
        """
 
198
        try:
 
199
            os.listdir(
 
200
                    os.path.join(
 
201
                        config['MEDIA_DIRECTORY'],
 
202
                        'items',
 
203
                        item
 
204
                        )
 
205
                    )
 
206
            the_item = entity.Entity(
 
207
                    None,
 
208
                    self,
 
209
                    os.path.join( 'items', item,),
 
210
                    place=place,
 
211
                    vector=vector,
 
212
                    reversed=reversed
 
213
                    )
 
214
            the_item.present = True
 
215
            the_item.visible = True
 
216
            self.items.append(the_item)
 
217
            return the_item
 
218
 
 
219
        except OSError, e:
 
220
            if e.errno is 22:
 
221
                self.LOG.log(item+' is not a valid item.')
 
222
            else:
 
223
                raise
 
224
        except IOError, e:
 
225
            if e.errno is 2:
 
226
                self.LOG.log(item+' is not a valid item directory.')
 
227
                raise
 
228
 
 
229
    def update_events(self, dt):
 
230
        """
 
231
        Called every frame, update every instancied event.
 
232
 
 
233
        """
 
234
        # FIXME: the index is not updated when we remove and element, so we may
 
235
        # skip outdated events until next frame. (it's a dit dirty).
 
236
        for event in self.events:
 
237
            if not event.update( dt, self.gametime ):
 
238
                self.events.remove(event)
 
239
 
 
240
    def draw(self, debug_params={}):
 
241
        """
 
242
        Draw every parts of the game on the screen.
 
243
 
 
244
        """
 
245
        self.level.draw_background( self.tmp_surface, (0,0))
 
246
        self.level.draw_level( self.tmp_surface ,self.level_place, self.zoom )
 
247
        #LOG().log(self.level.moving_blocs)
 
248
        for block in self.level.moving_blocs:
 
249
            block.draw( self.tmp_surface, self.level_place, self.zoom)
 
250
 
 
251
        for block in self.level.vector_blocs:
 
252
            block.draw( self.tmp_surface, self.level_place, self.zoom)
 
253
 
 
254
        for entity in self.players+self.items:
 
255
            entity.draw( self.level_place, self.zoom, self.tmp_surface )
 
256
 
 
257
        self.level.draw_foreground(self.tmp_surface,self.level_place, self.zoom)
 
258
        self.screen.blit(self.tmp_surface,(0,0) )
 
259
 
 
260
        # minimap
 
261
        for rect in self.level.map:
 
262
            draw_rect(
 
263
                    self.screen,
 
264
                    pygame.Rect(
 
265
                        (rect[0])/8,
 
266
                        (rect[1])/8,
 
267
                        rect[2]/8,
 
268
                        rect[3]/8
 
269
                        ),
 
270
                    pygame.Color('grey')
 
271
                    )
 
272
 
 
273
        # draw players portraits at bottom of screen
 
274
        for num, player in enumerate(self.players):
 
275
            self.screen.blit(
 
276
                     player.entity_skin.image,
 
277
                        (
 
278
                        -0.5*self.icon_space+player.num*self.icon_space,
 
279
                        config['SIZE'][1]*.9
 
280
                        )
 
281
                    )
 
282
 
 
283
            self.screen.blit(
 
284
                     self.font.render(str(player.percents*10)[:3]+"%",
 
285
                     True,
 
286
                     pygame.color.Color("white")),
 
287
                        (
 
288
                        -0.5*self.icon_space+player.num*self.icon_space,
 
289
                        420
 
290
                        )
 
291
                    )
 
292
            # draw player's lives.
 
293
            for i in range(player.lives):
 
294
                self.screen.blit(
 
295
                                 image(self.heart)[0],
 
296
                                    (
 
297
                                    -0.5*self.icon_space+player.num*\
 
298
                                    self.icon_space+i*self.icon_space/40,
 
299
                                    config['SIZE'][1]*.95
 
300
                                    )
 
301
                                )
 
302
 
 
303
            # displays coords of player, usefull for debuging
 
304
            if 'coords' in debug_params:
 
305
                self.screen.blit(
 
306
                        self.font.render(
 
307
                            str(player.place[0])+
 
308
                            ':'+
 
309
                            str(player.place[1]),
 
310
                            True,
 
311
                            pygame.color.Color('red')
 
312
                            ),
 
313
                        (
 
314
                         config['SIZE'][0] * 3 / 4,
 
315
                         num*config['SIZE'][1] / 4
 
316
                        )
 
317
                        )
 
318
            if 'action' in debug_params:
 
319
                # displays current key movement of player, usefull for debuging
 
320
                self.screen.blit(
 
321
                        self.font.render(
 
322
                            player.entity_skin.current_animation,
 
323
                            True,
 
324
                            pygame.color.Color('red')
 
325
                            ),
 
326
                        (
 
327
                         0,
 
328
                         num*config['SIZE'][1] / 4
 
329
                        )
 
330
                        )
 
331
            if 'controls' in debug_params:
 
332
                # displays current key sequence of player, usefull for debuging
 
333
                self.screen.blit(
 
334
                        self.font.render(
 
335
                            str(debug_params['controls'].player_sequences[num+1]),
 
336
                            True,
 
337
                            pygame.color.Color('red')
 
338
                            ),
 
339
                        (
 
340
                         0,
 
341
                         num*config['SIZE'][1] / 4
 
342
                        )
 
343
                        )
 
344
 
 
345
        if len([player for player in self.players if player.lives > 0]) == 1:
 
346
            self.screen.blit(self.game_font.render(
 
347
                                        [
 
348
                                         player for player in self.players if
 
349
                                         player.lives > 0
 
350
                                        ][0].name.capitalize()+" WON!",
 
351
                                        True,
 
352
                                        pygame.color.Color("#"+
 
353
                                            str(math.sin(self.ending/10)) [3:5]+
 
354
                                            "50"+
 
355
                                            str(math.sin(self.ending/10)) [3:5]+
 
356
                                            "30"
 
357
                                        )), (
 
358
                                              config['SIZE'][0]/2,
 
359
                                              config['SIZE'][1]/2)
 
360
                                            )
 
361
 
 
362
        if len([player for player in self.players if player.lives > 0]) == 0:
 
363
            # there is no more player in games, the game is tailed.
 
364
            self.screen.blit(self.game_font.render(
 
365
                                        "OOPS... DRAW!!!",
 
366
                                        True,
 
367
                                        pygame.color.Color("#"+
 
368
                                            str(math.sin(self.ending/10)) [3:5]+
 
369
                                            "50"+
 
370
                                            str(math.sin(self.ending/10)) [3:5]+
 
371
                                            "30"
 
372
                                        )),
 
373
                                            (
 
374
                                              config['SIZE'][0]/2,
 
375
                                              config['SIZE'][1]/2
 
376
                                            )
 
377
                                        )
 
378
 
 
379
    def update(self, debug_params={}):
 
380
        """
 
381
        sync everything to current time. Return "game" if we are still in game
 
382
        mode, return "menu" otherwise.
 
383
 
 
384
        """
 
385
        # calculate elapsed time
 
386
        deltatime = 0
 
387
 
 
388
        # frame limiter
 
389
        while deltatime < 1.0/config['MAX_FPS']:
 
390
            deltatime = time.time() - self.last_clock
 
391
 
 
392
        self.gametime += deltatime
 
393
        sys.stdout.write("\r"+str(self.gametime))
 
394
        sys.stdout.flush()
 
395
 
 
396
        self.last_clock = time.time()
 
397
 
 
398
        if deltatime > .25:
 
399
            # if true we are lagging, prevent anything from happening until next
 
400
            # frame (and forget about passed time).
 
401
            LOG().log("too slow, forget this frame!")
 
402
            return "game"
 
403
 
 
404
        present_players = [ i for i in self.players if i.present ]
 
405
        if len(present_players) is not 0:
 
406
            if len(present_players) == 1:
 
407
                players_barycenter = present_players[0].rect[0:2]
 
408
                precise_zoom = 1
 
409
                self.zoom = int(precise_zoom * 0.70 *
 
410
                            config['ZOOM_SHARPNESS'])/(config['ZOOM_SHARPNESS']*
 
411
                            1.0 )
 
412
            # center the level around the barycenter of present players.
 
413
            else:
 
414
                ordered = [ i.rect[0] for i in present_players ]
 
415
                ordered.sort()
 
416
                leftist = max( 1, ordered[0] )
 
417
                rightwing = max( 1, ordered[-1] )
 
418
                L = max( config['SIZE'][0], rightwing - leftist )
 
419
 
 
420
                ordered = [ i.rect[1] for i in self.players ]
 
421
                ordered.sort()
 
422
                upper,lower = ordered[0], ordered[-1]
 
423
                H = max( config['SIZE'][1], lower - upper)
 
424
 
 
425
                precise_zoom = min (
 
426
                        1.0*config['SIZE'][1] / H,
 
427
                        1.0*config['SIZE'][0] / L
 
428
                        )
 
429
 
 
430
                # there is a trade between zoom sharpness and speed so we force
 
431
                # the zoom level to be a limited precision value here, so the
 
432
                # cache in level drawing is more useful.
 
433
 
 
434
                self.zoom = (
 
435
                    int( precise_zoom * 0.70 * config['ZOOM_SHARPNESS'] )/
 
436
                    (config['ZOOM_SHARPNESS'] * 1.0)
 
437
                )
 
438
 
 
439
                players_barycenter = (
 
440
                    sum( i.rect[0] for i in self.players ) / len(self.players),
 
441
                    sum( i.rect[1] for i in self.players ) / len(self.players)
 
442
                    )
 
443
 
 
444
            #LOG().log(( self.zoom, lower - upper, rightwing - leftist))
 
445
            # calculate coordinates of top left corner of level
 
446
            # rect the barycenter of players at the center of the screen
 
447
            self.level_place = [
 
448
                 -(players_barycenter[0])*self.zoom+config['SIZE'][0]/2 ,
 
449
                 -(players_barycenter[1])*self.zoom+config['SIZE'][1]/2 
 
450
                 ]
 
451
 
 
452
        #sounds.playqueu()
 
453
 
 
454
        self.update_events( deltatime )
 
455
 
 
456
        # update level
 
457
        self.level.update(self.gametime)
 
458
 
 
459
        # update players
 
460
        for player in (p for p in self.players if p.present ):
 
461
            player.update(
 
462
                    deltatime,
 
463
                    self.gametime,
 
464
                    self.tmp_surface,
 
465
                    self,
 
466
                    self.level_place,
 
467
                    self.zoom
 
468
                    )
 
469
 
 
470
            # if the player is out of the level zone
 
471
            if player.rect.collidelist([self.level.border,]) == -1:
 
472
                self.events.append(
 
473
                        timed_event.PlayerOut(
 
474
                            (self.gametime, 0),
 
475
                            params={
 
476
                            'entity': player,
 
477
                            'world': self,
 
478
                            'gametime' : self.gametime
 
479
                            }
 
480
                            )
 
481
                        )
 
482
            if player.lives <= 0:
 
483
                #LOG().log("player's DEAD")
 
484
                player.present = False
 
485
 
 
486
        # FIXME: would be good to relocate this in an entity method, and
 
487
        # just loop on all the entities here.
 
488
 
 
489
        # agressive point collision between entities players.
 
490
        for entity in self.players+self.items:
 
491
            for point in entity.entity_skin.animation.agressivpoints:
 
492
                for pl in [ i for i in self.players+self.items\
 
493
                                if i is not entity\
 
494
                                and i.invincible is False ]:
 
495
                    if pl.collide_point([point[0][0]+entity.rect[0],
 
496
                                         point[0][1]+entity.rect[1]] )is not -1:
 
497
                        if entity.reversed != pl.reversed:
 
498
                            pl.vector = [-point[1][0]*(1+pl.percents),
 
499
                                          point[1][1]*(1+pl.percents)]
 
500
                        else:
 
501
                            pl.vector = [ point[1][0]*(1+pl.percents),
 
502
                                          point[1][1]*(1+pl.percents) ]
 
503
                        pl.percents += math.sqrt( point[1][0]**2\
 
504
                                                 +point[1][1]**2)/(30 * (100 -
 
505
                                                 pl.armor ))
 
506
 
 
507
                        pl.entity_skin.change_animation(
 
508
                                "take",
 
509
                                self,
 
510
                                params={
 
511
                                'entity': pl
 
512
                                }
 
513
                                )
 
514
 
 
515
        # collision between players and items -- tests and
 
516
        # consequences
 
517
        for player in self.players:
 
518
            for item in self.items:
 
519
                if player.rect.collidelist([item.rect,]) != -1 \
 
520
                and player.entity_skin.current_animation == "pick":
 
521
                        item.entity_skin.change_animation(
 
522
                                'triger',
 
523
                                self,
 
524
                                params={
 
525
                                'player': player,
 
526
                                'entity': item
 
527
                                }
 
528
                                )
 
529
 
 
530
        #update items
 
531
        for item in self.items:
 
532
            item.update(
 
533
                         deltatime,
 
534
                         self.gametime,
 
535
                         self.tmp_surface,
 
536
                         self,
 
537
                         self.level_place,
 
538
                         self.zoom
 
539
                        )
 
540
            if item.rect.collidelist([self.level.rect,]) == -1:
 
541
                item.lives = 0
 
542
            if item.lives <= 0:
 
543
                del(self.items[self.items.index(item)])
 
544
 
 
545
        if len([player for player in self.players if player.lives > 0]) <= 1:
 
546
            # there is only one player left then the game need to end after a
 
547
            # short animation
 
548
            #decount time
 
549
            self.ending -= deltatime
 
550
 
 
551
        # if animation time elapsed, return to menu
 
552
        if self.ending <= 0:
 
553
            self.ended = True
 
554
            del(self.game_font)
 
555
            del(self.level)
 
556
            del(self.players)
 
557
            del(self.events)
 
558
            return 'menu'
 
559
 
 
560
        return "game"
 
561
 
 
562
class NetworkServerGame(Game):
 
563
    """
 
564
    This particular version of the game class will accept connection of client,
 
565
    listen their information about keys hit by the players, update physics and
 
566
    send new postions of every entities, to every network players.
 
567
 
 
568
    """
 
569
    def __init__(self, players_=2):
 
570
        """
 
571
        Initialize a game with a list of player and a level,
 
572
        level is the basename of the level in media/levels/
 
573
 
 
574
        """
 
575
        pass
 
576
 
 
577
    def begin(self, level, players_):
 
578
        """
 
579
        Stop waiting for players, and start the real game.
 
580
 
 
581
        """
 
582
        pass
 
583
 
 
584
    def draw(self):
 
585
        """
 
586
        As we are in server mode, there will be no drawing.
 
587
 
 
588
        """
 
589
        pass
 
590
 
 
591
    def update_game_state_string(self):
 
592
        """
 
593
        create a string describing the update world to send to every clients.
 
594
 
 
595
        """
 
596
        pass
 
597
 
 
598
    def update(self):
 
599
        """
 
600
        sync everything to current time. Return "game" if we are still in game
 
601
        mode, return "menu" otherwise. send updates to clients.
 
602
 
 
603
        """
 
604
        pass
 
605
 
 
606
class NetworkClientGame(Game):
 
607
    """
 
608
    This particular version of the game class will try to connect to a server
 
609
    game, will send information about the player, his skin and the updates
 
610
    about the local player(s)'s movements. And draw the game based on
 
611
    informations sent by the server.
 
612
 
 
613
    """
 
614
    def __init__(self, screen, serverAddress, serverPort,
 
615
                 players_=(None, None, None, None), votemap='maryoland'):
 
616
        """
 
617
        We connect to the server and send information about our players.
 
618
 
 
619
        """
 
620
        pass
 
621
 
 
622
    def begin( self, players=[], level='' ):
 
623
        """
 
624
        Initiation of the game itself, we load the level, and the skins of the
 
625
        player we know of, we create a pool of entities skin on demand, to be
 
626
        able to display any required entity skin without dubble loading the
 
627
        same artworks.
 
628
 
 
629
        """
 
630
        pass
 
631
 
 
632
    def update(self, time):
 
633
        pass