3
################################################################################
4
# copyright 2008 Gabriel Pettier <gabriel.pettier@gmail.com> #
6
# This file is part of UltimateSmashFriends #
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#
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 #
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
################################################################################
31
from loaders import image
38
from level import Level
39
from controls import Controls
40
from config import config
42
from debug_utils import LOG, draw_rect
44
if not pygame.font: LOG().log('Warning, fonts disabled')
45
if not pygame.mixer: LOG().log('Warning, sound disabled')
47
class BadPlayersNetworkParamError(Exception):
49
Raised when the player params of a network server game is not correct.
56
The game base object, initiate and update everything when in game (not in
60
def __init__(self, screen, level="biglevel", players_=(None, None, None, None)):
62
Initialize a game with a list of player and a level,
63
level is the basename of the level in levels/
75
# time for a loading screen ain't it?
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'],
85
self.heart = os.path.join(
86
config['MEDIA_DIRECTORY'],
91
self.screen.blit(image(image_src)[0],(0,0))
93
self.game_font.render(
96
pygame.color.Color("white")
98
( 30, 4*config['SIZE'][1]/5 )
101
pygame.display.flip()
103
self.level = Level(level)
104
self.tmp_surface = self.screen.copy()
107
self.screen.blit(image(image_src)[0],(0,0))
109
self.game_font.render(
112
pygame.color.Color("white")
114
( 30, 4*config['SIZE'][1]/5 )
117
pygame.display.flip()
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:
125
if player.split(os.sep)[1][:2] == "AI":
130
player.split('/')[0]\
132
+player.split('/')[1][2:],
133
((i+1)*config['SIZE'][0]/5,100)
142
((i+1)*config['SIZE'][0]/5,100)
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)
151
#self.testimage=load_image(os.path.join(MEDIA_DIRECTORY,'items','item-heal'+os.extsep+'png')[0]
155
## adding test events.
157
timed_event.ItemShower(
166
# insert players in game
167
#LOG().log('players insertion in game')
168
for pl in self.players:
170
timed_event.DropPlayer(
171
(None, self.gametime),
175
'gametime' : self.gametime
180
# a countdown to the game end
186
destructor method of game, free as much resources as possible.
189
LOG().log('game deleted')
193
def addItem(self, item='heal', place=(550,50), reversed=False,vector=(0,0)):
195
Insert an item into game.
201
config['MEDIA_DIRECTORY'],
206
the_item = entity.Entity(
209
os.path.join( 'items', item,),
214
the_item.present = True
215
the_item.visible = True
216
self.items.append(the_item)
221
self.LOG.log(item+' is not a valid item.')
226
self.LOG.log(item+' is not a valid item directory.')
229
def update_events(self, dt):
231
Called every frame, update every instancied event.
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)
240
def draw(self, debug_params={}):
242
Draw every parts of the game on the screen.
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)
251
for block in self.level.vector_blocs:
252
block.draw( self.tmp_surface, self.level_place, self.zoom)
254
for entity in self.players+self.items:
255
entity.draw( self.level_place, self.zoom, self.tmp_surface )
257
self.level.draw_foreground(self.tmp_surface,self.level_place, self.zoom)
258
self.screen.blit(self.tmp_surface,(0,0) )
261
for rect in self.level.map:
273
# draw players portraits at bottom of screen
274
for num, player in enumerate(self.players):
276
player.entity_skin.image,
278
-0.5*self.icon_space+player.num*self.icon_space,
284
self.font.render(str(player.percents*10)[:3]+"%",
286
pygame.color.Color("white")),
288
-0.5*self.icon_space+player.num*self.icon_space,
292
# draw player's lives.
293
for i in range(player.lives):
295
image(self.heart)[0],
297
-0.5*self.icon_space+player.num*\
298
self.icon_space+i*self.icon_space/40,
299
config['SIZE'][1]*.95
303
# displays coords of player, usefull for debuging
304
if 'coords' in debug_params:
307
str(player.place[0])+
309
str(player.place[1]),
311
pygame.color.Color('red')
314
config['SIZE'][0] * 3 / 4,
315
num*config['SIZE'][1] / 4
318
if 'action' in debug_params:
319
# displays current key movement of player, usefull for debuging
322
player.entity_skin.current_animation,
324
pygame.color.Color('red')
328
num*config['SIZE'][1] / 4
331
if 'controls' in debug_params:
332
# displays current key sequence of player, usefull for debuging
335
str(debug_params['controls'].player_sequences[num+1]),
337
pygame.color.Color('red')
341
num*config['SIZE'][1] / 4
345
if len([player for player in self.players if player.lives > 0]) == 1:
346
self.screen.blit(self.game_font.render(
348
player for player in self.players if
350
][0].name.capitalize()+" WON!",
352
pygame.color.Color("#"+
353
str(math.sin(self.ending/10)) [3:5]+
355
str(math.sin(self.ending/10)) [3:5]+
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(
367
pygame.color.Color("#"+
368
str(math.sin(self.ending/10)) [3:5]+
370
str(math.sin(self.ending/10)) [3:5]+
379
def update(self, debug_params={}):
381
sync everything to current time. Return "game" if we are still in game
382
mode, return "menu" otherwise.
385
# calculate elapsed time
389
while deltatime < 1.0/config['MAX_FPS']:
390
deltatime = time.time() - self.last_clock
392
self.gametime += deltatime
393
sys.stdout.write("\r"+str(self.gametime))
396
self.last_clock = time.time()
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!")
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]
409
self.zoom = int(precise_zoom * 0.70 *
410
config['ZOOM_SHARPNESS'])/(config['ZOOM_SHARPNESS']*
412
# center the level around the barycenter of present players.
414
ordered = [ i.rect[0] for i in present_players ]
416
leftist = max( 1, ordered[0] )
417
rightwing = max( 1, ordered[-1] )
418
L = max( config['SIZE'][0], rightwing - leftist )
420
ordered = [ i.rect[1] for i in self.players ]
422
upper,lower = ordered[0], ordered[-1]
423
H = max( config['SIZE'][1], lower - upper)
426
1.0*config['SIZE'][1] / H,
427
1.0*config['SIZE'][0] / L
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.
435
int( precise_zoom * 0.70 * config['ZOOM_SHARPNESS'] )/
436
(config['ZOOM_SHARPNESS'] * 1.0)
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)
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
448
-(players_barycenter[0])*self.zoom+config['SIZE'][0]/2 ,
449
-(players_barycenter[1])*self.zoom+config['SIZE'][1]/2
454
self.update_events( deltatime )
457
self.level.update(self.gametime)
460
for player in (p for p in self.players if p.present ):
470
# if the player is out of the level zone
471
if player.rect.collidelist([self.level.border,]) == -1:
473
timed_event.PlayerOut(
478
'gametime' : self.gametime
482
if player.lives <= 0:
483
#LOG().log("player's DEAD")
484
player.present = False
486
# FIXME: would be good to relocate this in an entity method, and
487
# just loop on all the entities here.
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\
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)]
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 -
507
pl.entity_skin.change_animation(
515
# collision between players and items -- tests and
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(
531
for item in self.items:
540
if item.rect.collidelist([self.level.rect,]) == -1:
543
del(self.items[self.items.index(item)])
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
549
self.ending -= deltatime
551
# if animation time elapsed, return to menu
562
class NetworkServerGame(Game):
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.
569
def __init__(self, players_=2):
571
Initialize a game with a list of player and a level,
572
level is the basename of the level in media/levels/
577
def begin(self, level, players_):
579
Stop waiting for players, and start the real game.
586
As we are in server mode, there will be no drawing.
591
def update_game_state_string(self):
593
create a string describing the update world to send to every clients.
600
sync everything to current time. Return "game" if we are still in game
601
mode, return "menu" otherwise. send updates to clients.
606
class NetworkClientGame(Game):
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.
614
def __init__(self, screen, serverAddress, serverPort,
615
players_=(None, None, None, None), votemap='maryoland'):
617
We connect to the server and send information about our players.
622
def begin( self, players=[], level='' ):
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
632
def update(self, time):