From: <cd...@us...> - 2007-03-18 13:33:23
|
Revision: 137 http://eos-game.svn.sourceforge.net/eos-game/?rev=137&view=rev Author: cduncan Date: 2007-03-17 00:24:36 -0700 (Sat, 17 Mar 2007) Log Message: ----------- More ai improvements: - Aim toward stationary objects, otherwise if you don't move the ai just floats aimlessly - At longer range, ai approaches but once close in the ai stands off at a distance determined by the ai vessel size (which approximates range). This keeps the ai from sitting on top of their target and creates more realistic dogfights. This also makes the predict_ahead value quite a bit less important, and in fact a lower value works better now. - Fix vessel avoidance by clamping the pursuit velocity so it doesn't overwhelm the avoidance vector. This fixes stacking issues - Revert turning change, it caused ai to turn far too slowly. The effect is interesting though, and I may leverage it later to modulate overall difficulty. - Always send in a friendly when an enemy warship appears, makes the game difficulty more consistent. Modified Paths: -------------- ai.py game.py Modified: ai.py =================================================================== --- ai.py 2007-03-15 23:07:06 UTC (rev 136) +++ ai.py 2007-03-17 07:24:36 UTC (rev 137) @@ -131,7 +131,7 @@ else: return target.position - def pursue(self, predict_ahead=1.4): + def pursue(self, predict_ahead=0.75): """Return desired velocity towards where we predict our target to be @@ -152,25 +152,23 @@ """ velocity = self.vessel.velocity position = self.vessel.position - target_position = self.target.sprite.position - target_velocity = self.target.sprite.velocity - if not target_velocity and ( - position.distance(target_position) < self.target.sprite.radius * 1.5): + target = self.target.sprite + if not target.velocity and position.distance(target.position) < target.radius * 1.5: # close in to stationary target - return self.vessel.heading, target_velocity - new_heading = ((target_position + target_velocity * predict_ahead) - - (position + velocity * predict_ahead)).radians + return (target.position - position).radians, target.velocity predict_ahead = (position + velocity * predict_ahead).distance( - target_position + target_velocity * predict_ahead) / ( - self.vessel.velocity.length or 1) - approach = (target_position + target_velocity * predict_ahead) - ( + target.position + target.velocity * predict_ahead) / self.vessel.max_speed + approach = (target.position + target.velocity * predict_ahead) - ( position + velocity * predict_ahead) - if position.distance(target_position) < self.target.sprite.radius * 6: - heading = (target_position - position).radians + if approach.length > target.radius * 6: + # Far from target, catch up as fast as we can + return approach.radians, (approach * game.avg_fps).clamp(self.vessel.max_speed) else: - heading = approach.radians - return heading, approach * game.avg_fps + # close to target, keep a distance based on target size + return approach.radians, -(approach * + (self.vessel.radius * 6 - approach.length)).clamp(self.vessel.max_speed) + def evade(self): """Return desired velocity away from where we predict our target to be. Under evasion, we still turn toward @@ -225,9 +223,9 @@ turn_rate = self.vessel.turn_rate max_turn_rate = self.vessel.directional_thrusters.max_turn_rate - if heading_diff > turn_rate: + if heading_diff > turn_rate / 3: self.turn = 1 - elif heading_diff < -turn_rate: + elif heading_diff < -turn_rate / 3: self.turn = -1 else: self.turn = 0 Modified: game.py =================================================================== --- game.py 2007-03-15 23:07:06 UTC (rev 136) +++ game.py 2007-03-17 07:24:36 UTC (rev 137) @@ -151,7 +151,8 @@ ai.position.x, ai.position.y = x, y enemies.add(ai) barrier = 300 - while netsync.random.random() * frame_no > barrier: + friendly = warship + while friendly or netsync.random.random() * frame_no > barrier: if netsync.random.random() * frame_no < barrier * 3: ship = 'vessels/rone/drach' else: @@ -160,6 +161,7 @@ friend.position.x = target.position.x - position.x friend.position.y = target.position.y - position.y barrier *= 2 + friendly = False barrier = 200 while 1: if warship and netsync.random.random() * frame_no < barrier: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-03-22 04:31:14
|
Revision: 148 http://eos-game.svn.sourceforge.net/eos-game/?rev=148&view=rev Author: oberon7 Date: 2007-03-21 21:31:12 -0700 (Wed, 21 Mar 2007) Log Message: ----------- Tweaks to music cues; when the soundtrack plays the big guns are out. Modified Paths: -------------- game.py media.py Modified: game.py =================================================================== --- game.py 2007-03-22 02:32:46 UTC (rev 147) +++ game.py 2007-03-22 04:31:12 UTC (rev 148) @@ -11,7 +11,6 @@ import ode import pygame from pygame.locals import * -import random import vector fps = 32 # Desired fps @@ -58,8 +57,6 @@ # Kickstart the soundtrack pygame.mixer.set_reserved(1) soundtrack = pygame.mixer.Channel(0) - soundtrack.set_endevent(media.SOUNDTRACK) - pygame.event.post(pygame.event.Event(media.SOUNDTRACK)) # Get the available display modes and use the best one if fullscreen: @@ -108,8 +105,6 @@ if event.type == QUIT or ( event.type == KEYDOWN and event.key == K_ESCAPE): return False - elif event.type == media.SOUNDTRACK: - media.play_soundtrack('soundtrack%02d.wav' % random.randint(1,7)) return True def check_collision(data, geom1, geom2): @@ -158,12 +153,12 @@ target = netsync.random.choice(players) if not target.alive(): target = netsync.random.choice(planets.sprites()) - if target is local_player: - media.play_sound('suspense%02d.wav' % random.randint(1, 6), volume=0.4) position = vector.Vector2D.unit(netsync.random.random() * vector.fullcircle) * 1000 x = target.position.x + position.x y = target.position.y + position.y warship = netsync.random.random() * frame_no > 800 + if target is local_player: + media.play_soundtrack(large=warship) if warship: ai = AIVessel.load('vessels/naree/lotus') ai.position.x, ai.position.y = x, y Modified: media.py =================================================================== --- media.py 2007-03-22 02:32:46 UTC (rev 147) +++ media.py 2007-03-22 04:31:12 UTC (rev 148) @@ -10,10 +10,9 @@ import pygame from pygame.constants import * import game +import random from vector import fullcircle -SOUNDTRACK = USEREVENT+1 - _empty_rect = pygame.Rect(0, 0, 0, 0) def load_image(name): @@ -96,8 +95,11 @@ sound.set_volume(volume) sound.play() # avoids the reserved soundtrack channel -def play_soundtrack(name, volume=0.3): - sound = load_sound(name) +def play_soundtrack(large=False, volume=0.6): + if large: + sound = load_sound('soundtrack%02d.wav' % random.randint(1,7)) + else: + sound = load_sound('suspense%02d.wav' % random.randint(1, 6)) sound.set_volume(volume) game.soundtrack.play(sound) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-03-22 06:03:19
|
Revision: 149 http://eos-game.svn.sourceforge.net/eos-game/?rev=149&view=rev Author: cduncan Date: 2007-03-21 23:03:17 -0700 (Wed, 21 Mar 2007) Log Message: ----------- - Add some slop into lotus fullerene aiming, I made it a bit too precise I think - Switch secondary weapon key (flak bomb, gnat launch, etc) from LCTRL to LSHIFT - Teach the ai to use the flak cannon effectively Modified Paths: -------------- RELEASE.html bay.py projectile.py vessel.py Modified: RELEASE.html =================================================================== --- RELEASE.html 2007-03-22 04:31:12 UTC (rev 148) +++ RELEASE.html 2007-03-22 06:03:17 UTC (rev 149) @@ -59,8 +59,9 @@ <tr><th>←</th><td>Rotate left</td></tr> <tr><th>→</th><td>Rotate right</td></tr> <tr><th>↓</th><td>Reverse maneuvering thrust</td></tr> - <tr><th>[space]</th><td>Fire weapon (hold to charge)</td></tr> - <tr><th>[r shift]</th><td>Show shield status</td></tr> + <tr><th>[space]</th><td>Fire primary weapon</td></tr> + <tr><th>[left shift]</th><td>Fire secondary weapon</td></tr> + <tr><th>[right shift]</th><td>Show shield status</td></tr> <tr><th>w</th><td>Forward maneuvering thrust</td></tr> <tr><th>w + ↑</th><td>Boost (increases top speed)</td></tr> <tr><th>a</th><td>Left maneuvering thrust</td></tr> Modified: bay.py =================================================================== --- bay.py 2007-03-22 04:31:12 UTC (rev 148) +++ bay.py 2007-03-22 06:03:17 UTC (rev 149) @@ -86,7 +86,8 @@ @property def targeted(self): target = self.vessel.control.target.sprite - if (not target or not target.category & body.everything & ~self.vessel.category): + if (not target or not target.alive() + or not target.category & body.everything & ~self.vessel.category): return False return (abs(self.vessel.bearing(target)) < diagonal and self.vessel.position.distance(target.position) <= self.range) Modified: projectile.py =================================================================== --- projectile.py 2007-03-22 04:31:12 UTC (rev 148) +++ projectile.py 2007-03-22 06:03:17 UTC (rev 149) @@ -14,6 +14,7 @@ from vector import Vector2D, fullcircle, halfcircle, rightangle, diagonal import vessel import media +import ai class FullereneCannon(vessel.Weapon): @@ -64,7 +65,7 @@ target_dist = target.position.distance(self.gunmount.position) if self.firing: if (self.charge >= self.max_charge / 2 - and self.gunmount.bearing(target) < (diagonal / 8.0) + and self.gunmount.bearing(target) < (diagonal / 4.0) and target_dist < Fullerene.range * 2): return False else: @@ -332,9 +333,17 @@ media.play_sound('flak-bomb.wav', position=shooter.position) self.bomb_bay = bomb_bay self.detonating = False + self.target = getattr(self.shooter.control.target, 'sprite', None) def update(self): Shot.update(self) + if (not self.detonating and isinstance(self.shooter, ai.AIVessel) + and game.time > self.min_time): + proximity = self.shard_range / 2 + self.detonating = (not self.target or + self.position.distance(self.target.position) < proximity + or self.shooter.position.distance(self.target.position) < + self.shooter.position.distance(self.position) + proximity) if game.time > self.max_time or self.detonating or ( not self.bomb_bay.firing and game.time > self.min_time): self.detonate() Modified: vessel.py =================================================================== --- vessel.py 2007-03-22 04:31:12 UTC (rev 148) +++ vessel.py 2007-03-22 06:03:17 UTC (rev 149) @@ -55,7 +55,7 @@ self.bw_maneuver = keystate[K_s] or keystate[K_DOWN] self.shield = keystate[K_RSHIFT] self.weapons[0] = keystate[K_SPACE] - self.weapons[1] = keystate[K_LCTRL] + self.weapons[1] = keystate[K_LSHIFT] @classmethod def new_player(cls): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-03-26 15:59:39
|
Revision: 160 http://eos-game.svn.sourceforge.net/eos-game/?rev=160&view=rev Author: cduncan Date: 2007-03-26 08:59:27 -0700 (Mon, 26 Mar 2007) Log Message: ----------- - Load suspense/soundtrack sounds in a background thread to avoid blocking game play. This means there is no initial suspense sound, but I think that's fine. - Avoid playing the same soundtrack/suspense sound twice in a row. Modified Paths: -------------- game.py media.py Modified: game.py =================================================================== --- game.py 2007-03-26 06:50:47 UTC (rev 159) +++ game.py 2007-03-26 15:59:27 UTC (rev 160) @@ -58,6 +58,7 @@ # Kickstart the soundtrack pygame.mixer.set_reserved(1) soundtrack = pygame.mixer.Channel(0) + media.soundtrack_loader.start() # Get the available display modes and use the best one if fullscreen: Modified: media.py =================================================================== --- media.py 2007-03-26 06:50:47 UTC (rev 159) +++ media.py 2007-03-26 15:59:27 UTC (rev 160) @@ -7,10 +7,11 @@ import math import glob +import random +import threading import pygame from pygame.constants import * import game -import random from vector import fullcircle _empty_rect = pygame.Rect(0, 0, 0, 0) @@ -102,12 +103,43 @@ sound.play() # avoids the reserved soundtrack channel _sound_last_played[name] = game.frame_no +class SoundtrackLoader(threading.Thread): + """Load soundtracks in a background thread""" + + def __init__(self): + threading.Thread.__init__(self) + self.soundtracks = [] + self.suspense = [] + + def load(self, file_pattern, sound_list): + for filename in glob.glob(file_pattern): + sound = pygame.mixer.Sound(filename) + sound_list.append(sound) + + def run(self): + self.load('sounds/suspense*.ogg', self.suspense) + self.load('sounds/soundtrack*.ogg', self.soundtracks) + +soundtrack_loader = SoundtrackLoader() +last_soundtrack = None + def play_soundtrack(large=False, volume=0.6): - if large: - sound = load_sound('soundtrack%02d.ogg' % random.randint(1,7)) - else: - sound = load_sound('suspense%02d.ogg' % random.randint(1, 6)) + global soundtrack_loader, last_soundtrack + while 1: + if large: + if soundtrack_loader.soundtracks: + sound = random.choice(soundtrack_loader.soundtracks) + else: + return + else: + if soundtrack_loader.soundtracks: + sound = random.choice(soundtrack_loader.suspense) + else: + return + if sound is not last_soundtrack: + # Avoid playing the same one twice in a row + last_soundtrack = sound + break sound.set_volume(volume) - game.soundtrack.fadeout(1000) + game.soundtrack.fadeout(2000) game.soundtrack.queue(sound) - This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-03-28 04:05:12
|
Revision: 161 http://eos-game.svn.sourceforge.net/eos-game/?rev=161&view=rev Author: cduncan Date: 2007-03-27 21:05:10 -0700 (Tue, 27 Mar 2007) Log Message: ----------- - AI now drops pursuit of an exploding vessel and acquires a new target immediately - Fix fullerene cannon range, and plasma cannon firing rate when frame rate drops Modified Paths: -------------- ai.py projectile.py Modified: ai.py =================================================================== --- ai.py 2007-03-26 15:59:27 UTC (rev 160) +++ ai.py 2007-03-28 04:05:10 UTC (rev 161) @@ -173,7 +173,6 @@ return heading, -(approach * (self.vessel.radius * 6 - approach.length)).clamp(self.vessel.max_speed) - def evade(self): """Return desired velocity away from where we predict our target to be. Under evasion, we still turn toward @@ -253,7 +252,8 @@ def update(self): if (not isinstance(self.target.sprite, Vessel) - or self.target.sprite is self.mothership.sprite): + or self.target.sprite is self.mothership.sprite + or self.target.sprite.explosion is not None): # acquire a target vessel if self.sensor is not None: # Use the sensor to find a target Modified: projectile.py =================================================================== --- projectile.py 2007-03-26 15:59:27 UTC (rev 160) +++ projectile.py 2007-03-28 04:05:10 UTC (rev 161) @@ -45,7 +45,7 @@ or game.time - self.charge_start < self.max_charge_time * 1000)): if self.charge_start is None: self.charge_start = game.time - self.charge = min(self.charge + self.charge_rate / game.avg_fps, self.max_charge) + self.charge = min(self.charge + self.charge_rate / game.fps, self.max_charge) elif self.charge_start and self.charge >= self.min_charge: Fullerene(self.charge, self.gunmount) self.charge = 0 @@ -93,10 +93,10 @@ ) self.charge = charge self.distance = 0 - self.distance_per_frame = self.speed / game.avg_fps + self.distance_per_frame = self.speed / game.fps self.partsize = max(charge * .6, 2) self.partangle = fullcircle/self.particles - self.rot_per_frame = self.rpm * fullcircle / (game.avg_fps * 60) + self.rot_per_frame = self.rpm * fullcircle / (game.fps * 60) self.rot = 0 self.step = range(self.particles) self.colormax = 255 @@ -253,7 +253,7 @@ if damage_loss: # calculate damage loss per frame flight_time = range / self.velocity.length - self.damage_loss = damage_loss / flight_time / game.avg_fps + self.damage_loss = damage_loss / flight_time / game.fps else: self.damage_loss = 0 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-04-02 00:21:53
|
Revision: 164 http://eos-game.svn.sourceforge.net/eos-game/?rev=164&view=rev Author: cduncan Date: 2007-04-01 17:21:51 -0700 (Sun, 01 Apr 2007) Log Message: ----------- Improved selection ordering semantics, not perfect but better Modified Paths: -------------- ai.py selection.py Modified: ai.py =================================================================== --- ai.py 2007-03-30 08:11:09 UTC (rev 163) +++ ai.py 2007-04-02 00:21:51 UTC (rev 164) @@ -348,7 +348,7 @@ self.detect_bits = detect_bits self.geom.setCollideBits(detect_bits) self.last_sweep = None - self.detected = sprite.Group() + self._detected = [] self.closest_vessel = sprite.GroupSingle() self._closest_dist = sys.maxint self._closest_type = None @@ -409,19 +409,24 @@ return if self.last_sweep != game.frame_no and self.detected: # This is a new frame, start a new sensor sweep - self.detected.empty() + self._detected = [] self._closest_dist = sys.maxint self._closest_type = None self.closest_vessel.empty() self.last_sweep = game.frame_no - if isinstance(other, Vessel): - distance = self.vessel.position.distance(other.position) - if (distance < self._closest_dist or self._closest_type == 'missile' - and other.vessel_type != 'missle'): + distance = self.vessel.position.distance(other.position) + self._detected.append((distance, other)) + if (isinstance(other, Vessel) and distance < self._closest_dist + or self._closest_type == 'missile' and other.vessel_type != 'missle'): self.closest_vessel.sprite = other self._closest_dist = distance self._closest_type = other.vessel_type - self.detected.add(other) + + @property + def detected(self): + """Return a list of detected bodies in order by distance from vessel""" + self._detected.sort() + return [body for d, body in self._detected] def setDetect(self, detect_bits): self.geom.setCollideBits(detect_bits) Modified: selection.py =================================================================== --- selection.py 2007-03-30 08:11:09 UTC (rev 163) +++ selection.py 2007-04-02 00:21:51 UTC (rev 164) @@ -22,10 +22,10 @@ def __init__(self, source_vessel): pygame.sprite.Sprite.__init__(self, game.sprites) self.selected = pygame.sprite.GroupSingle() - self.already_selected = pygame.sprite.Group() self._select_timeout = sys.maxint self.rect = pygame.rect.Rect(0, 0, 0, 0) - self.sensor = ai.Sensor(source_vessel, 1000, exclude=self.already_selected) + self._nearest_iter = None + self.sensor = ai.Sensor(source_vessel, 1000) self.sensor.disable() @property @@ -35,25 +35,37 @@ def select(self, object): """Replace the current targeted object""" - media.play_sound('select.wav') - self.selected.add(object) - self.already_selected.add(object) - self._select_timeout = game.frame_no + self.timeout / game.fps + if object is not self.selected.sprite: + media.play_sound('select.wav') + self.selected.add(object) + self._select_timeout = game.frame_no + self.timeout / game.fps - def select_nearest(self, category): + def select_nearest(self, category, cycle=True): """Select the nearest body that is in the category""" + if self._nearest_iter is not None and self._last_category == category: + try: + self.select(self._nearest_iter.next()) + return + except StopIteration: + if cycle: + self._nearest_iter = (body for body in self.sensor.detected if body.alive()) + self.select_nearest(category, cycle=False) + else: + self._nearest_iter = None self.sensor.setDetect(category) + self._last_category = category self.sensor.enable() def update(self): - if (game.frame_no > self._select_timeout - or len(self.sensor.detected) == len(self.already_selected)): + if game.frame_no > self._select_timeout: # forget about all previously selected targets + self._nearest_iter = None self._select_timeout = sys.maxint - self.already_selected.empty() if self.sensor.enabled and self.sensor.closest_vessel: - self.select(self.sensor.closest_vessel.sprite) + # See what our sensor detected + self._nearest_iter = (body for body in self.sensor.detected if body.alive()) self.sensor.disable() # Stop detection + self.select_nearest(self._last_category) def draw(self, surface): target = self.target This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-04-02 07:58:25
|
Revision: 165 http://eos-game.svn.sourceforge.net/eos-game/?rev=165&view=rev Author: cduncan Date: 2007-04-02 00:58:22 -0700 (Mon, 02 Apr 2007) Log Message: ----------- - Frontend wip, working masthead animation - Slight refactor of eos.py to allow it to be used as a module - Add some fonts (not yet used) Modified Paths: -------------- eos.py macos/setup.py Added Paths: ----------- art/masthead.xcf data/dawn-of-light.png data/eos-mast.png data/flare.png data/nebula.jpg fonts/ fonts/forgottenfuturist/ fonts/forgottenfuturist/Forgotbi.ttf fonts/forgottenfuturist/Forgottb.ttf fonts/forgottenfuturist/Forgotte.ttf fonts/forgottenfuturist/Forgotti.ttf fonts/forgottenfuturist/Forgotts.ttf fonts/forgottenfuturist/read_me.html Added: art/masthead.xcf =================================================================== (Binary files differ) Property changes on: art/masthead.xcf ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: data/dawn-of-light.png =================================================================== (Binary files differ) Property changes on: data/dawn-of-light.png ___________________________________________________________________ Name: svn:mime-type + image/png Added: data/eos-mast.png =================================================================== (Binary files differ) Property changes on: data/eos-mast.png ___________________________________________________________________ Name: svn:mime-type + image/png Added: data/flare.png =================================================================== (Binary files differ) Property changes on: data/flare.png ___________________________________________________________________ Name: svn:mime-type + image/png Added: data/nebula.jpg =================================================================== (Binary files differ) Property changes on: data/nebula.jpg ___________________________________________________________________ Name: svn:mime-type + image/jpeg Modified: eos.py =================================================================== --- eos.py 2007-04-02 00:21:51 UTC (rev 164) +++ eos.py 2007-04-02 07:58:22 UTC (rev 165) @@ -9,7 +9,7 @@ import optparse import pygame -if __name__ == '__main__': +def run(): parser = optparse.OptionParser() parser.add_option('-s', '--server', dest='server_game', metavar='GAME_ID', @@ -61,3 +61,5 @@ raise print 'Average fps:', game.frame_no * 1000.0/(game.time - start) +if __name__ == '__main__': + run() Added: fonts/forgottenfuturist/Forgotbi.ttf =================================================================== (Binary files differ) Property changes on: fonts/forgottenfuturist/Forgotbi.ttf ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: fonts/forgottenfuturist/Forgottb.ttf =================================================================== (Binary files differ) Property changes on: fonts/forgottenfuturist/Forgottb.ttf ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: fonts/forgottenfuturist/Forgotte.ttf =================================================================== (Binary files differ) Property changes on: fonts/forgottenfuturist/Forgotte.ttf ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: fonts/forgottenfuturist/Forgotti.ttf =================================================================== (Binary files differ) Property changes on: fonts/forgottenfuturist/Forgotti.ttf ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: fonts/forgottenfuturist/Forgotts.ttf =================================================================== (Binary files differ) Property changes on: fonts/forgottenfuturist/Forgotts.ttf ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: fonts/forgottenfuturist/read_me.html =================================================================== --- fonts/forgottenfuturist/read_me.html (rev 0) +++ fonts/forgottenfuturist/read_me.html 2007-04-02 07:58:22 UTC (rev 165) @@ -0,0 +1,143 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> + <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=shift_jis"> + <TITLE>LARABIE FONTS “README</TITLE> + <META NAME="GENERATOR" CONTENT="StarOffice 8 (Win32)"> + <META NAME="AUTHOR" CONTENT="Rina Larabie"> + <META NAME="CREATED" CONTENT="20061130;11580000"> + <META NAME="CHANGEDBY" CONTENT="Rina Larabie"> + <META NAME="CHANGED" CONTENT="20061207;15120000"> + <STYLE> + <!-- + @page { size: 8.5in 11in; margin-right: 1.25in; margin-top: 1in; margin-bottom: 1in } + P { margin-bottom: 0.08in; direction: ltr; color: #000000; widows: 2; orphans: 2 } + P.western { font-family: "Times New Roman", serif; font-size: 12pt; so-language: en-US } + P.cjk { font-family: "Times New Roman", serif; font-size: 12pt } + P.ctl { font-family: "Times New Roman", serif; font-size: 12pt; so-language: ar-SA } + H3 { margin-top: 0.19in; margin-bottom: 0.19in; direction: ltr; color: #000000; widows: 2; orphans: 2; page-break-after: auto } + H3.western { font-size: 13pt; so-language: en-US } + H3.cjk { font-family: "Times New Roman", serif; font-size: 13pt } + H3.ctl { font-family: "Times New Roman", serif; font-size: 13pt; so-language: ar-SA } + A:link { color: #0000ff } + --> + </STYLE> +</HEAD> +<BODY LANG="en-US" TEXT="#000000" LINK="#0000ff" DIR="LTR"> +<H3 CLASS="western" STYLE="margin-top: 0in"><FONT FACE="Tahoma, sans-serif">LARABIE +FONTS FREEWARE FONTS EULA (End User License Agreement) and SOFTWARE +INCLUSION AGREEMENT</FONT></H3> +<P CLASS="western" STYLE="margin-bottom: 0in"><B><FONT FACE="Tahoma, sans-serif">LARABIE +FONTS FREEWARE FONTS EULA</FONT></B></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Larabie +Fonts Freeware Fonts in TrueType format are free to use for personal +and commercial purposes. No payment is necessary to use Larabie Fonts +Freeware Fonts for personal or commercial use. If you wish to include +Larabie Fonts Freeware Fonts in software see SOFTWARE INCLUSION +AGREEMENT below. </FONT></FONT> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2><B>USAGE</B></FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">You +can offer individual Larabie Fonts Freeware Fonts for download on a +website but do not combine fonts into a single archive or alter them +in any way. We appreciate inclusion of the font name and trademark or +site URL in the credits or documentation but it is not mandatory. </FONT></FONT> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Some +Larabie Fonts Freeware Fonts may have enhanced and/or expanded +families available for sale at </FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.typodermic.com/"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.typodermic.com</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">.</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2><B>PAYMENT</B></FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Payment +is not required for the use of Larabie Fonts Freeware Fonts unless +they are intended to be <I>included</I> with software. More details +follow.</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Donations +to Larabie Fonts are welcome via check or electronic payment via +Paypal. We can accept any amount in US dollars, Canadian dollars, GBP +or euros. If you require an invoice for your donation, please refer +to </FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.larabiefonts.com/donation.html"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.larabiefonts.com/donation.html</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif"> +for instructions. </FONT></FONT> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Donations +can include CDs, magazines, t-shirts, a sample of your merchandise or +anything featuring Larabie Fonts. Mailing information is available at +the link above.</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2><B>SUPPORT</B></FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Font +installation help is available at </FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.myfonts.com/support"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.myfonts.com/support</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">. +If you experience problems with any Larabie Font, please visit +</FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.larabiefonts.com/"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.larabiefonts.com</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif"> +to verify you have the latest version. If you download Larabie Fonts +Freeware Fonts from other websites you may get older versions that +have spacing issues, incomplete character sets or technical problems. +</FONT></FONT> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><B><FONT FACE="Tahoma, sans-serif">SOFTWARE +INCLUSION AGREEMENT </FONT></B> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">The +Larabie Fonts SOFTWARE PRODUCT is protected by copyright laws and +International copyright treaties, as well as other intellectual +property laws and treaties. The SOFTWARE PRODUCT is licensed, not +sold.</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">1. +GRANT OF LICENSE. This document grants you the following rights:</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2>- +Installation and Use. You may install and use an unlimited number of +copies of the SOFTWARE PRODUCT. You may copy and distribute unlimited +copies of the SOFTWARE PRODUCT as you receive them, in any medium, +provided that you publish on each copy an appropriate copyright +notice. Keep intact all the notices that refer to this License and +give any other recipients of the fonts a copy of this License along +with the fonts.</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">2. +LIMITED WARRANTY NO WARRANTIES. Larabie Fonts expressly disclaims any +warranty for the SOFTWARE PRODUCT. The SOFTWARE PRODUCT and any +related documentation is provided "as is" without warranty +of any kind, either express or implied, including, without +limitation, the implied warranties or merchantability, fitness for a +particular purpose, or non-infringement. The entire risk arising out +of use or performance of the SOFTWARE PRODUCT remains with you.</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">NO +LIABILITY FOR CONSEQUENTIAL DAMAGES. In no event shall Ray Larabie or +Larabie Fonts be liable for any damages whatsoever (including, +without limitation, damages for loss of business profits, business +interruption, loss of business information, or any other pecuniary +loss) arising out of the use of or inability to use this product, +even if Larabie Fonts has been advised of the possibility of such +damages.</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2>3. +MISCELLANEOUS</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Should +you have any questions concerning this document or you desire to +contact Larabie Fonts for any reason, please email +</FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.larabiefonts.com/email.html"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.larabiefonts.com/email.html</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">.</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><BR> +</P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">4. +GOVERNING LAW</FONT></FONT></P> +<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2>This +agreement is governed by the laws of Canada and the province of +British Columbia.</FONT></FONT></P> +</BODY> +</HTML> \ No newline at end of file Property changes on: fonts/forgottenfuturist/read_me.html ___________________________________________________________________ Name: svn:executable + * Name: mime-type + text/html Name: svn:keywords + Id Name: svn:eol-style + native Modified: macos/setup.py =================================================================== --- macos/setup.py 2007-04-02 00:21:51 UTC (rev 164) +++ macos/setup.py 2007-04-02 07:58:22 UTC (rev 165) @@ -11,7 +11,7 @@ from setuptools import setup APP = ['eos.py'] -DATA_FILES = ['data', 'sounds', 'vessels'] +DATA_FILES = ['data', 'sounds', 'vessels', 'fonts'] OPTIONS = {'argv_emulation': True, 'iconfile': 'data/eos.icns'} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-04-17 05:37:19
|
Revision: 175 http://eos-game.svn.sourceforge.net/eos-game/?rev=175&view=rev Author: cduncan Date: 2007-04-16 22:37:05 -0700 (Mon, 16 Apr 2007) Log Message: ----------- - Setup netsync.random at module import so that tests can use it - Make sure body has a geom to disable Modified Paths: -------------- body.py netsync.py Modified: body.py =================================================================== --- body.py 2007-04-12 03:46:54 UTC (rev 174) +++ body.py 2007-04-17 05:37:05 UTC (rev 175) @@ -216,9 +216,10 @@ # collision space immediately so they # are no longer considered by ode. self.body.disable() - self.geom.disable() - if self.geom is not None and game.collision_space.query(self.geom): - game.collision_space.remove(self.geom) + if self.geom is not None: + self.geom.disable() + if game.collision_space.query(self.geom): + game.collision_space.remove(self.geom) def push(self, force): """Apply the force vector to the body for this frame""" Modified: netsync.py =================================================================== --- netsync.py 2007-04-12 03:46:54 UTC (rev 174) +++ netsync.py 2007-04-17 05:37:05 UTC (rev 175) @@ -13,7 +13,7 @@ import time from random import Random -random = None +random = Random() players = None def send_keystate(frame_no, keystate): @@ -34,7 +34,6 @@ global random, send_keystate, step, players import vessel if game_id is None: # no need for memcached - random = Random() send_keystate = send_keystate_local step = step_local players = [vessel.KeyboardControl.new_player()] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-04-17 05:41:55
|
Revision: 177 http://eos-game.svn.sourceforge.net/eos-game/?rev=177&view=rev Author: cduncan Date: 2007-04-16 22:41:54 -0700 (Mon, 16 Apr 2007) Log Message: ----------- - Implement smoke trails when vessel systems are damaged. These don't use ode for simplicity, and reduced overhead. They don't interact with anything, so ode is not beneficial for them. Modified Paths: -------------- vessel.py Added Paths: ----------- particle.py Added: particle.py =================================================================== --- particle.py (rev 0) +++ particle.py 2007-04-17 05:41:54 UTC (rev 177) @@ -0,0 +1,134 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Particle Objects +# $Id$ + +import random +import pygame +from pygame.locals import * +import game +import body + + +class Particle(pygame.sprite.Sprite): + key_color = (1, 1, 1) + + _image_cache = {} + + def __init__(self, position, velocity, accel_rate, color, + growth_rate, time_to_live, opacity=255): + pygame.sprite.Sprite.__init__(self, game.sprites) + self.pos_x = float(position[0]) + self.pos_y = float(position[1]) + self.vel_x = float(velocity[0]) / game.fps + self.vel_y = float(velocity[1]) / game.fps + self.accel_rate = float(accel_rate) / game.fps + 1 + self.growth_rate = float(growth_rate) / game.fps + self.color = color + self.size = 2 + self.frames = self.total_frames = time_to_live * game.fps + self.opacity = opacity + self.rect = pygame.Rect(0, 0, 0, 0) + self.set_image() + self.offscreen_img = None + + def set_image(self): + img_key = self.size, self.color + if img_key in self._image_cache: + self.image = self._image_cache[img_key] + else: + self.image = pygame.Surface((self.size, self.size), 0, 8) + self.image.fill(self.key_color) + self.image.set_colorkey(self.key_color) + pygame.draw.ellipse(self.image, self.color, self.image.get_rect()) + self._image_cache[img_key] = self.image + self.rect = self.image.get_rect(center=self.rect.center) + + def update(self): + self.pos_x += self.vel_x + self.pos_y += self.vel_y + if self.accel_rate: + self.vel_x *= self.accel_rate + self.vel_y *= self.accel_rate + if random.random() <= self.growth_rate: + self.size += 1 + self.set_image() + if self.frames > 0: + self.frames -= 1 + camera = game.camera + self.rect.centerx = camera.rect.centerx + ( + (self.pos_x - camera.position.x) * camera.zoom) + self.rect.centery = camera.rect.centery + ( + (self.pos_y - camera.position.y) * camera.zoom) + self.image.set_alpha(self.frames * self.opacity / self.total_frames) + else: + self.kill() + + +class ParticleEmitter(pygame.sprite.Sprite): + """Emits particles at a particular interval for a particular time""" + + def __init__(self, position, velocity, accel_rate, color, growth_rate, + time_to_live, opacity, emit_rate, velocity_error=0.0): + pygame.sprite.Sprite.__init__(self, game.sprites) + self.position = position + self.velocity = velocity + self.velocity_error = velocity_error + self.accel_rate = accel_rate + self.color = color + self.growth_rate = growth_rate + self.time_to_live = time_to_live + self.opacity = opacity + self.emit_rate = float(emit_rate) / game.fps + self.rect = pygame.Rect(0, 0, 0, 0) + + def update(self): + emit_prob = self.emit_rate + while random.random() < emit_prob: + velocity = self.velocity.copy() + if self.velocity_error: + velocity.x += velocity.x * random.uniform( + -self.velocity_error, self.velocity_error) + velocity.y += velocity.y * random.uniform( + -self.velocity_error, self.velocity_error) + if not isinstance(self.color, tuple): + color = self.color() + else: + color = self.color + Particle(self.position, velocity, self.accel_rate, + color, self.growth_rate, self.time_to_live, self.opacity) + emit_prob -= 1 + + def draw(self, surface): + return self.rect + + +class GrayGenerator: + """Generates random gray colors""" + + def __init__(self, min_bright, max_bright): + self.min_bright = min_bright + self.max_bright = max_bright + + def __call__(self): + bright = random.randint(self.min_bright, self.max_bright) + return (bright, bright, bright) + + +class SmokeTrail(ParticleEmitter): + + def __init__(self, body): + """Smoke trail emitted from body""" + ParticleEmitter.__init__(self, + body.position, + body.velocity, + velocity_error=0.07, + accel_rate=-0.6, + color=(60, 60, 60), + growth_rate=12, + time_to_live=2.0, + opacity=150, + emit_rate=35) + Property changes on: particle.py ___________________________________________________________________ Name: svn:keywords + Id Name: svn:eol-style + native Modified: vessel.py =================================================================== --- vessel.py 2007-04-17 05:38:40 UTC (rev 176) +++ vessel.py 2007-04-17 05:41:54 UTC (rev 177) @@ -16,6 +16,7 @@ from pygame.locals import * from media import RotatedImage from vector import Vector2D, fullcircle, halfcircle, rightangle, diagonal +import particle max_weapons = 5 _empty_rect = pygame.rect.Rect(0, 0, 0, 0) @@ -97,6 +98,7 @@ max_speed = 0 max_energy = 0 energy_storage_mass = 0.02 # Mass per energy unit stored + damage_smoke = None control = Control() @@ -397,6 +399,8 @@ # Apply residual damage to hull self.hull_damage += value if self.hull_damage > self.disable_factor * self.hull_mass: + if self.damage_smoke is not None: + self.damage_smoke.kill() self.explode() else: self.system_damage += value @@ -404,6 +408,8 @@ system = netsync.random.choice(self._sys) system.disable() self.system_damage -= self.system_damage_threshold + if self.damage_smoke is None and self.vessel_type != 'missile': + self.damage_smoke = particle.SmokeTrail(self) def use_energy(self, energy, partial=False): """Consume vessel energy. Return the amount of energy that is available. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-04-17 06:51:35
|
Revision: 178 http://eos-game.svn.sourceforge.net/eos-game/?rev=178&view=rev Author: cduncan Date: 2007-04-16 23:51:33 -0700 (Mon, 16 Apr 2007) Log Message: ----------- Implement map files, create a sol map with the inner planets. Not sure if the outer planets will be on the same map. Modified Paths: -------------- ai.py game.py staticbody.py Added Paths: ----------- content/ content/maps/ content/maps/sol content/naree/ content/naree/description.txt content/rone/ content/rone/description.txt data/earth-west.png data/mars.png data/mercury.png data/venus.png map.py Modified: ai.py =================================================================== --- ai.py 2007-04-17 05:41:54 UTC (rev 177) +++ ai.py 2007-04-17 06:51:33 UTC (rev 178) @@ -268,7 +268,7 @@ self.target.add(self.mothership) else: # No mothership, just head toward a planet - self.target.add(game.planets) + self.target.add(game.map.planets) self.steerfunc = self.pursue elif (self.vessel.health < 0.66 and game.time - self.vessel.damage_time < 2000 and not self.vessel.is_friendly(self.target.sprite)): Added: content/maps/sol =================================================================== --- content/maps/sol (rev 0) +++ content/maps/sol 2007-04-17 06:51:33 UTC (rev 178) @@ -0,0 +1,28 @@ +[general] +name: Sol +description: Home system of the Human race + +[planet:Earth] +image: earth-east.png,earth-west.png +x: 0 +y: 0 + +[planet:Moon] +image: moon.png +x: 0 +y: -1000 + +[planet:Mars] +image: mars.png +x: 5000 +y: 1500 + +[planet:Venus] +image: venus.png +x: -4000 +y: -1000 + +[planet:Mercury] +image: mercury.png +x: -6000 +y: -1500 Property changes on: content/maps/sol ___________________________________________________________________ Name: svn:eol-style + native Added: content/naree/description.txt =================================================================== --- content/naree/description.txt (rev 0) +++ content/naree/description.txt 2007-04-17 06:51:33 UTC (rev 178) @@ -0,0 +1,3 @@ +Over the millennia, the Naree have seen many civilizations come and go. Accustomed to their superior technology making them invulnerable to outside aggression, they were ill prepared for the arrival of the Rone. The Naree, having long since given up warfare, lacked both the political and industrial infrastructure to respond to the Rone incursions. In fact, there is still little or no recognition of the Rone threat at the highest level of the Naree government, which in its centuries of passivity cannot comprehend engaging in mortal combat. + +Unable to tolerate the denial of the Naree high counsel, Matma, a counsel advisor, has decided the time is nigh to meet the Rone threat before it is too late, and drive them from Naree space forever. Matma and a small faction of scientists, engineers, and peacekeepers have revived long dormant Naree military technologies and have begun to build a force to counter the Rone. Without the backing of the stubbornly pacifist Naree government, and their spiritual leader Dalini, Matma must make do with scant resources and personnel. It is only the superior power and efficiency of Naree technology that prevents their utter defeat at the hands of the Rone. Property changes on: content/naree/description.txt ___________________________________________________________________ Name: mime-type + text/plain Name: svn:keywords + Id Name: svn:eol-style + native Added: content/rone/description.txt =================================================================== --- content/rone/description.txt (rev 0) +++ content/rone/description.txt 2007-04-17 06:51:33 UTC (rev 178) @@ -0,0 +1,3 @@ +The enigmatic Rone arrived only recently to this sector of space. Lacking a home system, they exploit the resources of whatever systems they come across. Any beings having the misfortune of being in their path are viewed as prey to be exploited at best. The nomadic migration of the Rone can only be described as a conflagration that spreads through space consuming everything in its path. When the Rone first encountered the Naree, they were certain they would present little resistance. This proved true at first, but a growing faction of Naree are showing stiff resistance to the Rone advance. This development has only stiffened the resolve of the Rone who can think of nothing but the extermination of the Naree. + +The Rone lack the elegant technologies of the Naree, but they compensate for this with a single-mindedness and ferocity which makes them a seemingly irresistible force. Technological advancement is only a small means to a greater end, and the Rone are only interested in such matters when the circumstances warrant it. Property changes on: content/rone/description.txt ___________________________________________________________________ Name: mime-type + text/plain Name: svn:keywords + Id Name: svn:eol-style + native Added: data/earth-west.png =================================================================== (Binary files differ) Property changes on: data/earth-west.png ___________________________________________________________________ Name: svn:mime-type + image/png Added: data/mars.png =================================================================== (Binary files differ) Property changes on: data/mars.png ___________________________________________________________________ Name: svn:mime-type + image/png Added: data/mercury.png =================================================================== (Binary files differ) Property changes on: data/mercury.png ___________________________________________________________________ Name: svn:mime-type + image/png Added: data/venus.png =================================================================== (Binary files differ) Property changes on: data/venus.png ___________________________________________________________________ Name: svn:mime-type + image/png Modified: game.py =================================================================== --- game.py 2007-04-17 05:41:54 UTC (rev 177) +++ game.py 2007-04-17 06:51:33 UTC (rev 178) @@ -25,7 +25,7 @@ # Sprite groups sprites = None # All sprites -planets = None +map = None # Other globally accessible things clock = None @@ -48,7 +48,8 @@ from staticbody import Planet from vessel import Vessel, KeyboardControl from media import load_image - global universe, collision_space, screen, screen_rect, background, planets + from map import Map + global universe, collision_space, screen, screen_rect, background, map global sprites, fps, avg_fps, clock, time, players, local_player, camera, ai global frame_lag, soundtrack, target frame_lag = expected_lag @@ -72,7 +73,7 @@ else: mode = modes[0] print "Could not find ideal display mode, using", mode - screen = pygame.display.set_mode(mode, FULLSCREEN, 16) + screen = pygame.display.set_mode(mode, FULLSCREEN | HWSURFACE | DOUBLEBUF, 16) else: screen = pygame.display.set_mode((1024, 768)) pygame.display.set_icon(load_image('eos-icon.png')) @@ -91,10 +92,8 @@ # Create game elements clock = pygame.time.Clock() sprites = RenderedGroup() - planets = Group() StarField(screen.get_rect()) - Planet('earth-east.png', 0, 0) - Planet('moon.png', 0, -1000) + map = Map('sol') # Establish networking players, local_player = netsync.init(run_server, game_id, host, port, player_count) @@ -163,8 +162,8 @@ sprites.clear(screen, background) sprites.update() dirty = sprites.draw(screen) - pygame.display.update(dirty) - #pygame.display.flip() + #pygame.display.update(dirty) + pygame.display.flip() universe.quickStep(1.0 / fps) # target FPS time += clock.tick(fps) avg_fps = clock.get_fps() or fps @@ -173,7 +172,7 @@ if time > next_wave: target = netsync.random.choice(players) if not target.alive(): - target = netsync.random.choice(planets.sprites()) + target = netsync.random.choice(map.planets) position = vector.Vector2D.unit(netsync.random.random() * vector.fullcircle) * 1000 x = target.position.x + position.x y = target.position.y + position.y Added: map.py =================================================================== --- map.py (rev 0) +++ map.py 2007-04-17 06:51:33 UTC (rev 178) @@ -0,0 +1,109 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# System maps +# $Id$ + +import sys +import ConfigParser +import game +import staticbody + +class Map: + + name = '' + description = '' + + def __init__(self, config_file, planet_factory=staticbody.Planet): + """Load a map from file, either a file-like object or a file path + string + + >>> from StringIO import StringIO + >>> game.init() + >>> class TestPlanet: + ... def __init__(self, name, **kw): + ... self.name = name + ... self.params = kw + >>> config = StringIO(''' + ... [general] + ... name: test map + ... description: This is only a test + ... + ... [planet:Floom] + ... x: 0 + ... y: 1 + ... type: watery + ... + ... [planet:Flam] + ... x: 1000 + ... y: 2000 + ... type: custardy + ... ''') + >>> m = Map(config, planet_factory=TestPlanet) + >>> m.name + 'test map' + >>> m.description + 'This is only a test' + >>> len(m.planets) + 2 + >>> m.planets[0].name + 'Floom' + >>> m.planets[0].params['x'] + '0' + >>> m.planets[0].params['y'] + '1' + >>> m.planets[0].params['type'] + 'watery' + >>> m.planets[1].name + 'Flam' + >>> m.planets[1].params['x'] + '1000' + >>> m.planets[1].params['y'] + '2000' + >>> m.planets[1].params['type'] + 'custardy' + """ + if isinstance(config_file, str): + config_file = open('content/maps/%s' % config_file, 'rb') + file_name = getattr(config_file, 'name', repr(config_file)) + parser = ConfigParser.SafeConfigParser() + parser.readfp(config_file) + self.planets = [] + for section in parser.sections(): + if section == 'general': + for option, value in parser.items(section): + if option == 'name': + self.name = value + elif option == 'description': + self.description = value + else: + raise MapConfigError, ( + 'Invalid option %s in section [general] of map config file %s' + % (option, file_name)) + elif section.startswith('planet:'): + nil, planet_name = section.split(':') + planet_params = dict(parser.items(section)) + try: + planet = planet_factory(planet_name, **planet_params) + except Exception, err: + raise MapConfigError, ( + 'Exception creating planet from section [%s] of map config file %s: %s' + % (section, file_name, err)), sys.exc_info()[-1] + self.planets.append(planet) + else: + raise MapConfigError, ( + 'Invalid section [%s] of map config file %s' + % (section, file_name)) + + +class MapConfigError(Exception): + """Map configuration file error""" + + +if __name__ == '__main__': + """Run tests if executed directly""" + import sys, doctest + failed, count = doctest.testmod() + print 'Ran', count, 'test cases with', failed, 'failures' + sys.exit(failed) Property changes on: map.py ___________________________________________________________________ Name: svn:keywords + Id Name: svn:eol-style + native Modified: staticbody.py =================================================================== --- staticbody.py 2007-04-17 05:41:54 UTC (rev 177) +++ staticbody.py 2007-04-17 06:51:33 UTC (rev 178) @@ -7,26 +7,23 @@ import pygame from pygame.constants import * -import game import media from body import RoundBody from vector import Vector2D from media import RotatedImage +from netsync import random class Planet(RoundBody): mass = 1000000 max_visible_dist = float('inf') - def __init__(self, image, x=0, y=0): - RoundBody.__init__(self, (x, y)) - game.planets.add(self) + def __init__(self, name, image, x=0, y=0): + RoundBody.__init__(self, (int(x), int(y))) self.body.disable() # immobile - self.image = media.load_image(image) + self.name = name + self.image = media.load_image(random.choice(image.split(','))) self.offscreen_img = RotatedImage('pointy-blue.gif') - clear = self.image.get_colorkey() - if clear: - self.image.set_colorkey(clear, RLEACCEL) self.rect = self.image.get_rect(centerx=self.position.x, centery=self.position.y) self.radius = self.rect.width / 2 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-04-19 21:11:50
|
Revision: 180 http://eos-game.svn.sourceforge.net/eos-game/?rev=180&view=rev Author: cduncan Date: 2007-04-19 14:11:47 -0700 (Thu, 19 Apr 2007) Log Message: ----------- Eliminate use of avg_fps for consistency so that dynamics don't change if the game slows. Modified Paths: -------------- ai.py stars.py vessel.py Modified: ai.py =================================================================== --- ai.py 2007-04-19 07:45:09 UTC (rev 179) +++ ai.py 2007-04-19 21:11:47 UTC (rev 180) @@ -52,7 +52,7 @@ 0.0 """ return ((target_position - (self.vessel.position + self.vessel.velocity * predict_ahead) - ) * game.avg_fps).clamp(self.vessel.max_speed) + ) * game.fps).clamp(self.vessel.max_speed) def seek(self): """Return desired velocity towards a fixed target @@ -73,7 +73,7 @@ """ position = self.vessel.position target_position = self.target.sprite.position - velocity = (target_position - position) * game.avg_fps + velocity = (target_position - position) * game.fps velocity.clamp_ip(self.vessel.max_speed) return velocity @@ -162,7 +162,7 @@ position + velocity * predict_ahead) if approach.length > target.radius * 6: # Far from target, catch up as fast as we can - return approach.radians, (approach * game.avg_fps).clamp(self.vessel.max_speed) + return approach.radians, (approach * game.fps).clamp(self.vessel.max_speed) else: # close to target, keep a distance based on target size if self.vessel.is_friendly(target): Modified: stars.py =================================================================== --- stars.py 2007-04-19 07:45:09 UTC (rev 179) +++ stars.py 2007-04-19 21:11:47 UTC (rev 180) @@ -79,6 +79,6 @@ star_rect[0] = randint(leftbound, rightbound) def update(self): - vx = - game.camera.zoom * game.camera.velocity.x / game.avg_fps - vy = - game.camera.zoom * game.camera.velocity.y / game.avg_fps + vx = - game.camera.zoom * game.camera.velocity.x / game.fps + vy = - game.camera.zoom * game.camera.velocity.y / game.fps self.move((vx, vy)) Modified: vessel.py =================================================================== --- vessel.py 2007-04-19 07:45:09 UTC (rev 179) +++ vessel.py 2007-04-19 21:11:47 UTC (rev 180) @@ -630,7 +630,6 @@ """Update shield status and regenerate >>> game.init() - >>> game.avg_fps = 30 >>> s = Shield(Vessel(), 10.0, 30.0) >>> s.level == s.max_level True @@ -648,13 +647,13 @@ if self.level < self.max_level and self.regeneration and self.enabled: # Regenerate shields new_level = min( - self.level + self.regeneration / game.avg_fps, self.max_level) + self.level + self.regeneration / game.fps, self.max_level) energy = self.vessel.use_energy(new_level - self.level, partial=True) self.level += energy if self.regeneration < self.max_regeneration: # Recover regeneration rate if disrupted self.regeneration = min( - self.regeneration + (self.max_regeneration / (game.avg_fps * self.regen_recovery)), + self.regeneration + (self.max_regeneration / (game.fps * self.regen_recovery)), self.max_regeneration) def draw(self, surface): @@ -798,7 +797,7 @@ 0.0 """ if self.heat > 0: - self.heat = max(self.heat - self.dissipation / game.avg_fps, 0.0) + self.heat = max(self.heat - self.dissipation / game.fps, 0.0) if self.regeneration and self.dissipation < self.max_dissipate: self.dissipation = min( self.dissipation + self.regeneration, self.max_dissipate) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-04-21 06:31:44
|
Revision: 181 http://eos-game.svn.sourceforge.net/eos-game/?rev=181&view=rev Author: cduncan Date: 2007-04-20 23:31:39 -0700 (Fri, 20 Apr 2007) Log Message: ----------- - Replace homegrown Vector2D class with python complex numbers for performance, this results in a nice reduction in code as well with only a little loss of abstraction. - Replace postion, velocity and heading properties on bodies with static attributes and explicit getters/setters. These values are so frequently accessed that the time spent in the implicit property getters was prohibitive. Now the values are fetched just once per frame and cached. These changes make a measurable difference in performance and framerate especially as the number of ships grows. Two additional possible optimization avenues are: - Only updating the ai controls a few times per second rather than every frame - Using Numeric to store and manipulate the starfield --This line, and those below, will be ignored-- M ai.py M body.py M vector.py M particle.py M media.py M stars.py M projectile.py M vessel.py M beam.py M staticbody.py M game.py M bay.py Modified Paths: -------------- ai.py bay.py beam.py body.py game.py media.py particle.py projectile.py stars.py staticbody.py vector.py vessel.py Modified: ai.py =================================================================== --- ai.py 2007-04-19 21:11:47 UTC (rev 180) +++ ai.py 2007-04-21 06:31:39 UTC (rev 181) @@ -13,7 +13,8 @@ import game import body import netsync -from vector import Vector2D, diagonal, halfcircle, rightangle, fullcircle +import vector +from vector import diagonal, halfcircle, rightangle, fullcircle from vessel import Control, Vessel, DirectionalThrusters class AIControl(Control): @@ -44,15 +45,16 @@ >>> game.init() >>> ai = AIControl(Vessel(), None) >>> ai.vessel.max_speed = 10 - >>> ai.vessel.position = Vector2D(100, 0) - >>> vel = ai.seek_position(Vector2D(-100, 0)) + >>> ai.vessel.position = (100, 0) + >>> vel = ai.seek_position((-100, 0)) >>> vel.x -10.0 >>> vel.y 0.0 """ - return ((target_position - (self.vessel.position + self.vessel.velocity * predict_ahead) - ) * game.fps).clamp(self.vessel.max_speed) + position = self.vessel.position + self.vessel.velocity * predict_ahead + velocity = (target_position - position) * game.fps + return vector.clamp(velocity, self.vessel.max_speed) def seek(self): """Return desired velocity towards a fixed target @@ -74,8 +76,7 @@ position = self.vessel.position target_position = self.target.sprite.position velocity = (target_position - position) * game.fps - velocity.clamp_ip(self.vessel.max_speed) - return velocity + return vector.clamp(velocity, self.vessel.max_speed) def flee(self): """Return desired velocity away from our target @@ -95,7 +96,7 @@ True """ velocity = self.seek() - return (velocity.radians + halfcircle) % fullcircle, -velocity + return (vector.radians(velocity) + halfcircle) % fullcircle, -velocity def predict_intercept(self, target, predict_ahead=2): """Return predicted position of target at the time @@ -119,14 +120,13 @@ 0 """ if target.velocity: - predict_ahead *= (self.vessel.velocity.distance(target.velocity) / - (self.vessel.position.distance(target.position) + 1)) + predict_ahead *= (vector.distance(self.vessel.velocity, target.velocity) / + (vector.distance(self.vessel.position, target.position) + 1)) position = self.vessel.position + self.vessel.velocity * predict_ahead predicted_velocity = (target.velocity + - Vector2D.unit(target.heading) * target.velocity.length) / 2 - T = (position.distance(target.position + predicted_velocity * predict_ahead) - / self.vessel.max_speed) - pos = target.position + (target.velocity * T) + vector.unit(target.heading) * vector.length(target.velocity)) / 2 + T = vector.distance(position, + target.position + predicted_velocity * predict_ahead) / self.vessel.max_speed return target.position + (target.velocity * T) else: return target.position @@ -153,25 +153,27 @@ velocity = self.vessel.velocity position = self.vessel.position target = self.target.sprite - if not target.velocity and position.distance(target.position) < target.radius * 1.5: + if not target.velocity and vector.distance( + position, target.position) < target.radius * 1.5: # close in to stationary target - return (target.position - position).radians, target.velocity - predict_ahead = (position + velocity * predict_ahead).distance( + return vector.radians(target.position - position), target.velocity + predict_ahead = vector.distance(position + velocity * predict_ahead, target.position + target.velocity * predict_ahead) / self.vessel.max_speed approach = (target.position + target.velocity * predict_ahead) - ( position + velocity * predict_ahead) - if approach.length > target.radius * 6: + if vector.length(approach) > target.radius * 6: # Far from target, catch up as fast as we can - return approach.radians, (approach * game.fps).clamp(self.vessel.max_speed) + return vector.radians(approach), vector.clamp( + approach * game.fps, self.vessel.max_speed) else: # close to target, keep a distance based on target size if self.vessel.is_friendly(target): # Match heading with friendly target heading = target.heading else: - heading = approach.radians - return heading, -(approach * - (self.vessel.radius * 6 - approach.length)).clamp(self.vessel.max_speed) + heading = vector.radians(approach) + return heading, vector.clamp(-(approach * + (self.vessel.radius * 6 - vector.length(approach))), self.vessel.max_speed) def evade(self): """Return desired velocity away from where we predict @@ -195,18 +197,20 @@ True """ heading, seek_velocity = self.pursue() - velocity = Vector2D.unit(seek_velocity.radians + rightangle * 1.5) * self.vessel.max_speed + velocity = vector.unit( + vector.radians(seek_velocity) + rightangle * 1.5) * self.vessel.max_speed return heading, velocity def avoid_vessels(self): """Return a vector away from other nearby vessels to avoid stacking up """ - center = Vector2D() + center = vector.vector2() too_close = [] mass = 0 # Compute the center vector amongst close vessels and avoid it for vessel in list(self.close_vessels): - proximity = max(self.vessel.position.distance(vessel.position) - vessel.radius, 0) + proximity = max(vector.distance( + self.vessel.position, vessel.position) - vessel.radius, 0) if proximity < self.proximity_radius: center += vessel.position too_close.append(vessel) @@ -216,7 +220,7 @@ self.close_vessels.remove(vessel) if too_close: center /= len(too_close) - return (self.vessel.position - center).normal * ( + return vector.normal(self.vessel.position - center) * ( self.vessel.max_speed * (mass * 2.0 / self.vessel.mass)) else: return center @@ -237,7 +241,8 @@ else: self.turn = 0 if self.vessel.velocity != desired_velocity: - thrust_dir = (desired_velocity - self.vessel.velocity).radians - self.vessel.heading + thrust_dir = vector.radians( + desired_velocity - self.vessel.velocity) - self.vessel.heading self.thrust = thrust_dir < rightangle or thrust_dir > fullcircle - rightangle self.fw_maneuver = not self.thrust and ( thrust_dir < rightangle or thrust_dir > fullcircle - rightangle) @@ -274,7 +279,7 @@ and not self.vessel.is_friendly(self.target.sprite)): self.steerfunc = self.evade elif (self.vessel.health > 0.75 - or self.target.sprite.position.distance(self.vessel.position) > 350): + or vector.distance(self.target.sprite.position, self.vessel.position) > 350): self.steerfunc = self.pursue # steering desired_heading, desired_velocity = self.steerfunc() @@ -414,7 +419,7 @@ self._closest_type = None self.closest_vessel.empty() self.last_sweep = game.frame_no - distance = self.vessel.position.distance(other.position) + distance = vector.distance(self.vessel.position, other.position) self._detected.append((distance, other)) if (isinstance(other, Vessel) and distance < self._closest_dist or self._closest_type == 'missile' and other.vessel_type != 'missle'): Modified: bay.py =================================================================== --- bay.py 2007-04-19 21:11:47 UTC (rev 180) +++ bay.py 2007-04-21 06:31:39 UTC (rev 181) @@ -11,7 +11,8 @@ import ai import body import media -from vector import Vector2D, halfcircle, diagonal +import vector +from vector import halfcircle, diagonal class FighterBay(vessel.Weapon): @@ -43,11 +44,9 @@ sensor=self.sensor, category=self.vessel.category) self.fighters.append(fighter) - fighter.position.x = self.vessel.position.x - fighter.position.y = self.vessel.position.y - fighter.velocity.x = self.vessel.velocity.x - fighter.velocity.y = self.vessel.velocity.y - fighter.push(Vector2D.unit(self.vessel.heading + halfcircle) * self.launch_force) + fighter.set_position(self.vessel.position) + fighter.set_velocity(self.vessel.velocity) + fighter.push(vector.unit(self.vessel.heading + halfcircle) * self.launch_force) fighter.update() self.last_launch = game.time media.play_sound('launch.wav', position=self.vessel.position) @@ -58,7 +57,7 @@ if (not target or not target.category & body.everything & ~self.vessel.category): return False return (len(self.fighters) < self.capacity - and self.vessel.position.distance(target.position) < self.launch_distance) + and vector.distance(self.vessel.position, target.position) < self.launch_distance) class BombBay(vessel.Weapon): @@ -82,7 +81,7 @@ and game.time > self.last_launch + self.reload_time and self.vessel.use_energy(self.energy_use)): bomb = self.bomb_factory(self.vessel, self, - Vector2D.unit(self.vessel.heading) * self.launch_velocity, + vector.unit(self.vessel.heading) * self.launch_velocity, self.max_ttl) self.last_launch = game.time @@ -92,5 +91,5 @@ if (not target or not target.alive() or target.explosion is not None or not target.category & body.everything & ~self.vessel.category): return False - return (abs(self.vessel.bearing(target)) < diagonal - and self.vessel.position.distance(target.position) <= self.range) + return (abs(self.vessel.bearing(target)) < diagonal / 2 + and vector.distance(self.vessel.position, target.position) <= self.range) Modified: beam.py =================================================================== --- beam.py 2007-04-19 21:11:47 UTC (rev 180) +++ beam.py 2007-04-21 06:31:39 UTC (rev 181) @@ -11,7 +11,7 @@ import body import random import vessel -from vector import Vector2D +import vector _empty_rect = pygame.rect.Rect(0, 0, 0, 0) @@ -40,7 +40,8 @@ self.ray.setCategoryBits(body.nothing) self.ray.setCollideBits(body.everything & ~self.gunmount.category) self.ray.parent = self - self.ray.set(self.gunmount.position, Vector2D.unit(self.gunmount.heading)) + self.ray.set(vector.to_tuple3(self.gunmount.position), + vector.to_tuple3(vector.unit(self.gunmount.heading))) self.firing = self.firing and self.gunmount.use_energy( self.beam_damage / self.efficiency / game.fps) @@ -48,8 +49,9 @@ if self.firing and self.enabled: camera = game.camera (sx, sy, ignored), (dx, dy, ignored) = self.ray.get() - sx = camera.rect.centerx + (sx - camera.position.x) * camera.zoom - sy = camera.rect.centery + (sy - camera.position.y) * camera.zoom + camera_pos_x, camera_pos_y = vector.to_tuple(game.camera.position) + sx = camera.rect.centerx + (sx - camera_pos_x) * camera.zoom + sy = camera.rect.centery + (sy - camera_pos_y) * camera.zoom dx *= self.length * camera.zoom dy *= self.length * camera.zoom self.targeted = False Modified: body.py =================================================================== --- body.py 2007-04-19 21:11:47 UTC (rev 180) +++ body.py 2007-04-21 06:31:39 UTC (rev 181) @@ -10,7 +10,7 @@ import game import pygame import media -from vector import Vector2D, VelocityVector, PositionVector +import vector # Collision category bit values, used for collision detection. # Each body assigned a category and optionally a collide @@ -47,7 +47,7 @@ def __init__(self, position=None, velocity=None, category=nothing, collides_with=nothing): """Create the body in the given position vector which - can be either a Vector2D or a tuple. An optional + can be either a vector or a tuple. An optional initial velocity may be specified if omitted the object is stationary. @@ -64,25 +64,40 @@ mass.adjust(self.mass) self.body.setMass(mass) if position is not None: - self.body.setPosition((position[0], position[1], 0)) + self.set_position(position) + else: + self.position = vector.vector2() if velocity is not None: - self.body.setLinearVel((velocity[0], velocity[1], 0)) + self.set_velocity(velocity) + else: + self.velocity = vector.vector2() if category != nothing or collides_with != nothing: self.setup_collision(category, collides_with) - # mapping the ODE universe - self.position = PositionVector(self.body) - self.velocity = VelocityVector(self.body) # on screen size and positioning if self.rect is None: - self.rect = pygame.Rect((0,0,0,0)) - self.rect.centerx = self.position.x - self.rect.centery = self.position.y - self.rect.width = self.radius*2 - self.rect.height = self.radius*2 + self.rect = pygame.Rect((0, 0, self.radius*2, self.radius*2)) self.enabled = True self.on_screen = True self.explosion = None + def get_position(self): + return vector.vector2(*self.body.getPosition()[:2]) + + def set_position(self, position): + if not isinstance(position, tuple): + position = vector.to_tuple(position) + self.body.setPosition((position[0], position[1], 0)) + self.position = vector.vector2(*position) + + def get_velocity(self): + return vector.vector2(*self.body.getLinearVel()[:2]) + + def set_velocity(self, velocity): + if not isinstance(velocity, tuple): + velocity = vector.to_tuple(velocity) + self.body.setLinearVel((velocity[0], velocity[1], 0)) + self.velocity = vector.vector2(*velocity) + def setup_collision(self, category, collides_with): """Configure the collision geom to determine what objects we collide with (collides_with) and what objects collide with us (category) @@ -106,24 +121,25 @@ @property def force(self): - return Vector2D(*self.body.getForce()[:2]) + return vector.vector2(*self.body.getForce()[:2]) @property def torque(self): return self.body.getTorque()[2] - def _heading_fget(self): + def get_heading(self): mat = self.body.getRotation() # Derive z-axis rotation. Assumes no rotation in other axis assert mat[6:] == (0, 0, 1), "Object rotated off axis!" return math.atan2(-mat[1], mat[0]) - def _heading_fset(self, h): + + def set_heading(self, h): sinh = math.sin(h) cosh = math.cos(h) # create rotation matrix in the Z-axis self.body.setRotation( (cosh, -sinh, 0, sinh, cosh, 0, 0, 0, 1)) - heading = property(fget=_heading_fget, fset=_heading_fset) + self.heading = h def _turn_rate_fget(self): ignored, ignored, turn_rate = self.body.getAngularVel() @@ -134,14 +150,15 @@ def update(self): """Calculate onscreen position and size""" - self.position.sync() - self.velocity.sync() + # Position, velocity and heading are cached because they are frequently accessed + self.position = self.get_position() + self.velocity = self.get_velocity() + self.heading = self.get_heading() # place self relative to the camera at proper zoom level camera = game.camera - self.rect.centerx = camera.rect.centerx + ( - (self.position.x - camera.position.x) * camera.zoom) - self.rect.centery = camera.rect.centery + ( - (self.position.y - camera.position.y) * camera.zoom) + screen_pos = vector.vector2(*camera.rect.center) + ( + self.position - camera.position) * camera.zoom + self.rect.center = vector.to_tuple(screen_pos) self.on_screen = self.rect.colliderect(game.screen_rect) if self.explosion is not None: try: @@ -158,40 +175,34 @@ elif self.offscreen_img is not None: camera_rect = game.camera.rect camera_pos = game.camera.position - distance = self.position.distance(camera_pos) + camera_x, camera_y = vector.to_tuple(camera_pos) + distance = vector.distance(self.position, camera_pos) if distance < self.max_visible_dist and self.position != camera_pos: opacity = 170 - (distance - camera_rect.centerx) / self.max_visible_dist * 120 - angle = (self.position - camera_pos).radians + angle = vector.radians(self.position - camera_pos) image = self.offscreen_img.rotated(angle) rect = image.get_rect(center=self.rect.center) + x, y = vector.to_tuple(self.position) if self.rect.top < game.screen_rect.top: rect.top = game.screen_rect.top rect.left = camera_rect.centerx + ( - (self.position.x - camera_pos.x) * - (rect.centery - camera_rect.centery) / - (self.position.y - camera_pos.y)) + (x - camera_x) * (rect.centery - camera_rect.centery) / (y - camera_y)) elif self.rect.bottom > game.screen_rect.bottom: rect.bottom = game.screen_rect.bottom rect.left = camera_rect.centerx + ( - (self.position.x - camera_pos.x) * - (rect.centery - camera_rect.centery) / - (self.position.y - camera_pos.y)) + (x - camera_x) * (rect.centery - camera_rect.centery) / (y - camera_y)) if rect.left < game.screen_rect.left: rect.left = game.screen_rect.left if (rect.top < self.rect.top < game.screen_rect.top or rect.bottom > game.screen_rect.bottom): rect.top = camera_rect.centery + ( - (self.position.y - camera_pos.y) * - (rect.centerx - camera_rect.centerx) / - (self.position.x - camera_pos.x)) + (y - camera_y) * (rect.centerx - camera_rect.centerx) / (x - camera_x)) elif rect.right > game.screen_rect.right: rect.right = game.screen_rect.right if (rect.top < self.rect.top < game.screen_rect.top or rect.bottom > game.screen_rect.bottom): rect.top = camera_rect.centery + ( - (self.position.y - camera_pos.y) * - (rect.centerx - camera_rect.centerx) / - (self.position.x - camera_pos.x)) + (y - camera_y) * (rect.centerx - camera_rect.centerx) / (x - camera_x)) image.set_alpha(opacity) surface.blit(image, rect) return rect @@ -223,7 +234,8 @@ def push(self, force): """Apply the force vector to the body for this frame""" - self.body.addForce((force[0], force[1], 0)) + x, y = vector.to_tuple(force) + self.body.addForce((x, y, 0)) def is_friendly(self, other): """Return true if the other body is considered friendly""" Modified: game.py =================================================================== --- game.py 2007-04-19 21:11:47 UTC (rev 180) +++ game.py 2007-04-21 06:31:39 UTC (rev 181) @@ -173,15 +173,13 @@ target = netsync.random.choice(players) if not target.alive(): target = netsync.random.choice(map.planets) - position = vector.Vector2D.unit(netsync.random.random() * vector.fullcircle) * 1000 - x = target.position.x + position.x - y = target.position.y + position.y + position = vector.unit(netsync.random.random() * vector.fullcircle) * 1000 warship = netsync.random.random() * frame_no > 800 if target is local_player: media.play_soundtrack(large=warship) if warship and len(enemies) < max_fleet_size: ai = AIVessel.load('vessels/naree/lotus') - ai.position.x, ai.position.y = x, y + ai.set_position(target.position + position) enemies.add(ai) barrier = 300 friendly = warship @@ -192,8 +190,7 @@ else: ship = 'vessels/rone/draken' friend = AIVessel.load(ship, mothership=target, category=body.friend) - friend.position.x = target.position.x - position.x - friend.position.y = target.position.y - position.y + friend.set_position(target.position - position) friends.add(friend) barrier *= 2 friendly = False @@ -202,7 +199,7 @@ if warship and netsync.random.random() * frame_no < barrier: break ai = AIVessel.load('vessels/naree/cress') - ai.position.x, ai.position.y = x, y + ai.set_position(target.position + position) enemies.add(ai) warship = True barrier *= 2 Modified: media.py =================================================================== --- media.py 2007-04-19 21:11:47 UTC (rev 180) +++ media.py 2007-04-21 06:31:39 UTC (rev 181) @@ -12,6 +12,7 @@ import pygame from pygame.constants import * import game +import vector from vector import fullcircle _empty_rect = pygame.Rect(0, 0, 0, 0) @@ -93,7 +94,7 @@ # Don't play same sound too close together return if position is not None: - distance = game.camera.position.distance(position) + distance = vector.distance(game.camera.position, position) if distance > 0: volume = volume * (700 - distance) / 700 if volume < 0.1: Modified: particle.py =================================================================== --- particle.py 2007-04-19 21:11:47 UTC (rev 180) +++ particle.py 2007-04-21 06:31:39 UTC (rev 181) @@ -10,6 +10,7 @@ from pygame.locals import * import game import body +import vector class Particle(pygame.sprite.Sprite): @@ -20,10 +21,8 @@ def __init__(self, position, velocity, accel_rate, color, growth_rate, time_to_live, opacity=255): pygame.sprite.Sprite.__init__(self, game.sprites) - self.pos_x = float(position[0]) - self.pos_y = float(position[1]) - self.vel_x = float(velocity[0]) / game.fps - self.vel_y = float(velocity[1]) / game.fps + self.pos_x, self.pos_y = vector.to_tuple(position) + self.vel_x, self.vel_y = vector.to_tuple(velocity / game.fps) self.accel_rate = float(accel_rate) / game.fps + 1 self.growth_rate = float(growth_rate) / game.fps self.color = color @@ -58,23 +57,26 @@ if self.frames > 0: self.frames -= 1 camera = game.camera + camera_x, camera_y = vector.to_tuple(camera.position) self.rect.centerx = camera.rect.centerx + ( - (self.pos_x - camera.position.x) * camera.zoom) + (self.pos_x - camera_x) * camera.zoom) self.rect.centery = camera.rect.centery + ( - (self.pos_y - camera.position.y) * camera.zoom) - self.image.set_alpha(self.frames * self.opacity / self.total_frames) + (self.pos_y - camera_y) * camera.zoom) else: self.kill() + + def draw(self, surface): + self.image.set_alpha(self.frames * self.opacity / self.total_frames) + return surface.blit(self.image, self.rect) class ParticleEmitter(pygame.sprite.Sprite): - """Emits particles at a particular interval for a particular time""" + """Emits particles from a body at a particular interval for a particular time""" - def __init__(self, position, velocity, accel_rate, color, growth_rate, + def __init__(self, body, accel_rate, color, growth_rate, time_to_live, opacity, emit_rate, velocity_error=0.0): pygame.sprite.Sprite.__init__(self, game.sprites) - self.position = position - self.velocity = velocity + self.body = body self.velocity_error = velocity_error self.accel_rate = accel_rate self.color = color @@ -87,17 +89,15 @@ def update(self): emit_prob = self.emit_rate while random.random() < emit_prob: - velocity = self.velocity.copy() + velocity = self.body.velocity if self.velocity_error: - velocity.x += velocity.x * random.uniform( - -self.velocity_error, self.velocity_error) - velocity.y += velocity.y * random.uniform( - -self.velocity_error, self.velocity_error) + velocity += (vector.unit(random.random() * vector.fullcircle) + * random.random() * self.velocity_error * vector.length(velocity)) if not isinstance(self.color, tuple): color = self.color() else: color = self.color - Particle(self.position, velocity, self.accel_rate, + Particle(self.body.position, velocity, self.accel_rate, color, self.growth_rate, self.time_to_live, self.opacity) emit_prob -= 1 @@ -122,8 +122,7 @@ def __init__(self, body): """Smoke trail emitted from body""" ParticleEmitter.__init__(self, - body.position, - body.velocity, + body, velocity_error=0.07, accel_rate=-0.6, color=(60, 60, 60), Modified: projectile.py =================================================================== --- projectile.py 2007-04-19 21:11:47 UTC (rev 180) +++ projectile.py 2007-04-21 06:31:39 UTC (rev 181) @@ -11,7 +11,8 @@ import game import random from body import RoundBody, everything -from vector import Vector2D, fullcircle, halfcircle, rightangle, diagonal +import vector +from vector import fullcircle, halfcircle, rightangle, diagonal import vessel import media import ai @@ -64,7 +65,7 @@ if (target.category is None or not target.category & everything & ~self.gunmount.category): return False - target_dist = target.position.distance(self.gunmount.position) + target_dist = vector.distance(target.position, self.gunmount.position) if self.firing: if (self.charge >= self.max_charge / 1.5 and self.gunmount.bearing(target) < (diagonal / 4.0) @@ -90,7 +91,7 @@ self.radius = max(charge, 1) RoundBody.__init__( self, gunmount.position, - Vector2D.unit(gunmount.heading) * self.speed + gunmount.velocity, + vector.unit(gunmount.heading) * self.speed + gunmount.velocity, collides_with=everything & ~gunmount.category ) self.charge = charge @@ -123,9 +124,9 @@ surface.fill((self.colormax, bg, self.colormax-bg), (self.rect.center, (partsize, partsize))) for i in self.step: - direction = Vector2D.unit(self.rot) - part = (self.rect.centerx + direction.x * radius, - self.rect.centery + direction.y * radius, + direction_x, direction_y = vector.to_tuple(vector.unit(self.rot)) + part = (self.rect.centerx + direction_x * radius, + self.rect.centery + direction_y * radius, partsize, partsize) rects.append(part) bg = random.randint(0, self.colormax) @@ -185,10 +186,10 @@ def update_system(self): if (self.enabled and self.firing and game.time - self.last_fire > self.cool_down and self.gunmount.use_energy(self.damage_value / self.efficiency)): - shot_position = self.gunmount.position.copy() + shot_position = self.gunmount.position if self.mount_positions is not None: angle, length = self.mount_positions[self.next_position] - shot_position += Vector2D.unit(angle + self.gunmount.heading) * length + shot_position += vector.unit(angle + self.gunmount.heading) * length self.next_position = (self.next_position + 1) % self.mount_points self.shot_factory( shooter=self.gunmount, @@ -196,7 +197,7 @@ image=self.shot_img.rotated(self.gunmount.heading), radius=self.radius, damage=self.damage_value, - velocity=Vector2D.unit(self.gunmount.heading) * self.velocity, + velocity=vector.unit(self.gunmount.heading) * self.velocity, range=self.range, fade_range=self.shot_fade_range, damage_loss=self.damage_loss) @@ -213,9 +214,10 @@ if (target.category is None or not target.category & everything & ~self.gunmount.category): return False - if target.position.distance(self.gunmount.position) > self.range: + if vector.distance(target.position, self.gunmount.position) > self.range: return False - heading_diff = self.gunmount.heading - (target.position - self.gunmount.position).radians + heading_diff = self.gunmount.heading - vector.radians( + target.position - self.gunmount.position) if heading_diff > halfcircle: heading_diff -= fullcircle elif heading_diff < -halfcircle: @@ -256,7 +258,7 @@ self.fade_range = range * 0.9 if damage_loss: # calculate damage loss per frame - flight_time = range / self.velocity.length + flight_time = range / vector.length(self.velocity) self.damage_loss = damage_loss / flight_time / game.fps else: self.damage_loss = 0 @@ -267,7 +269,7 @@ def update(self): RoundBody.update(self) self.damage_value -= self.damage_loss - distance = self.start_position.distance(self.position) + distance = vector.distance(self.start_position, self.position) if distance > self.range: self.kill() elif distance > self.fade_range: @@ -306,8 +308,7 @@ if self.alive() and self.explosion is None: if self.damage_value > 0: self.explode('hit.wav') - self.velocity.x = other.velocity.x - self.velocity.y = other.velocity.y + self.set_velocity(other.velocity) other.damage(self.damage_value) else: self.kill() @@ -350,9 +351,9 @@ and game.time > self.min_time): proximity = self.shard_range / 2 self.detonating = (not self.target or - self.position.distance(self.target.position) < proximity - or self.shooter.position.distance(self.target.position) < - self.shooter.position.distance(self.position) + proximity) + vector.distance(self.position, self.target.position) < proximity + or vector.distance(self.shooter.position, self.target.position) < + vector.distance(self.shooter.position, self.position) + proximity) if game.time > self.max_time or self.detonating or ( not self.bomb_bay.firing and game.time > self.min_time): self.detonate() @@ -372,7 +373,7 @@ Shot(self, image=shard_img.rotated(shard_angle), damage=self.shard_damage, - velocity=Vector2D.unit(shard_angle) * random.gauss( + velocity=vector.unit(shard_angle) * random.gauss( self.shard_velocity, self.shard_velocity / 3), range=self.shard_range, damage_loss=-30.0) Modified: stars.py =================================================================== --- stars.py 2007-04-19 21:11:47 UTC (rev 180) +++ stars.py 2007-04-21 06:31:39 UTC (rev 181) @@ -8,6 +8,7 @@ from random import randint import pygame import game +import vector class StarField(pygame.sprite.Sprite): @@ -79,6 +80,5 @@ star_rect[0] = randint(leftbound, rightbound) def update(self): - vx = - game.camera.zoom * game.camera.velocity.x / game.fps - vy = - game.camera.zoom * game.camera.velocity.y / game.fps - self.move((vx, vy)) + star_velocity = game.camera.zoom * -game.camera.velocity / game.fps + self.move(vector.to_tuple(star_velocity)) Modified: staticbody.py =================================================================== --- staticbody.py 2007-04-19 21:11:47 UTC (rev 180) +++ staticbody.py 2007-04-21 06:31:39 UTC (rev 181) @@ -9,9 +9,9 @@ from pygame.constants import * import media from body import RoundBody -from vector import Vector2D from media import RotatedImage from netsync import random +import vector class Planet(RoundBody): @@ -24,6 +24,6 @@ self.name = name self.image = media.load_image(random.choice(image.split(','))) self.offscreen_img = RotatedImage('pointy-blue.gif') - self.rect = self.image.get_rect(centerx=self.position.x, centery=self.position.y) + self.rect = self.image.get_rect() self.radius = self.rect.width / 2 Modified: vector.py =================================================================== --- vector.py 2007-04-19 21:11:47 UTC (rev 180) +++ vector.py 2007-04-21 06:31:39 UTC (rev 181) @@ -2,366 +2,48 @@ ## Copyright (c) 2007 Casey Duncan and contributors ## See LICENSE.txt for licensing details -# Two dimensional vectors +# 2D vectors using python complex numbers # $Id$ import math +import cmath -# number of cached unit vectors -unit_vectors = 360 -max_radian = 2 * math.pi -unit_vector_res = max_radian / unit_vectors +vector2 = complex -class Vector2D(object): - """Two dimensional vector +length = abs - >>> Vector2D(3, 4).length - 5.0 - >>> Vector2D(100, 0).normal.x - 1.0 - >>> Vector2D(100, 0).normal.y - 0.0 - >>> Vector2D(0, -42).normal.x - 0.0 - >>> Vector2D(0, -42).normal.y - -1.0 - """ +def to_tuple(vector): + return (vector.real, vector.imag) - def __init__(self, x=0, y=0): - """Create a 2 dimensional vector +def to_tuple3(vector): + return (vector.real, vector.imag, 0) - >>> v = Vector2D(2, 3) - >>> v.x, v.y - (2, 3) - """ - self.__x = x - self.__y = y - self._clearcache() +def radians(vector): + return math.atan2(vector.imag, vector.real) - # magic Y property that invalidates this vector's - # cache whenever it's modified in place - def x_fget(self): - return self.__x - def x_fset(self, value): - self._clearcache() - self.__x = value - x = property(fget=x_fget, fset=x_fset) +def unit(radians): + return cmath.exp(radians * 1j) - # magic Y property that invalidates this vector's - # cache whenever it's modified in place - def y_fget(self): - return self.__y - def y_fset(self, value): - self._clearcache() - self.__y = value - y = property(fget=y_fget, fset=y_fset) +def normal(vector): + L = length(vector) + if L == 0: + return vector2() + else: + return vector / L - def __getitem__(self, index): - """Emulate sequence (x, y, 0). The third dimension is supplied - for easy compatibility with ode, which is 3D - """ - return (self.x, self.y, 0)[index] +def clamp(vector, max_length): + L = length(vector) + if L > max_length: + return vector * (max_length / L) + else: + return vector - def __add__(self, other): - """Add two vectors or a vector and a tuple. Return a new vector +def distance(vector1, vector2): + return length(vector1 - vector2) - >>> v1 = Vector2D(2, 3) - >>> v2 = v1 + Vector2D(5, 6) - >>> v1.x, v1.y - (2, 3) - >>> v2.x, v2.y - (7, 9) - >>> v3 = v1 + (1, -1) - >>> v1.x, v1.y - (2, 3) - >>> v3.x, v3.y - (3, 2) - """ - if isinstance(other, Vector2D): - return Vector2D(self.x + other.x, self.y + other.y) - else: - return Vector2D(self.x + other[0], self.y + other[1]) - - def __iadd__(self, other): - """Add two vectors or a vector and a tuple in place - - >>> v = Vector2D(3, 4) - >>> v.length - 5.0 - >>> v += Vector2D(5, 6) - >>> v.x, v.y - (8, 10) - >>> v.length**2 - 164.0 - >>> v += (1, -1) - >>> v.x, v.y - (9, 9) - >>> v.length**2 - 162.0 - """ - if isinstance(other, tuple): - self.x += other[0] - self.y += other[1] - else: - self.x += other.x - self.y += other.y - return self - - def __sub__(self, other): - """Subtract two vectors or a tuple from a vector. Return a new vector - - >>> v1 = Vector2D(8, 3) - >>> v2 = v1 - Vector2D(5, 6) - >>> v1.x, v1.y - (8, 3) - >>> v2.x, v2.y - (3, -3) - >>> v3 = v1 - (1, -1) - >>> v1.x, v1.y - (8, 3) - >>> v3.x, v3.y - (7, 4) - """ - if isinstance(other, Vector2D): - return Vector2D(self.x - other.x, self.y - other.y) - else: - return Vector2D(self.x - other[0], self.y - other[1]) - - def __isub__(self, other): - """Add two vectors or a vector and a tuple in place - - >>> v = Vector2D(8, 10) - >>> v.length**2 - 164.0 - >>> v -= Vector2D(5, 6) - >>> v.x, v.y - (3, 4) - >>> v.length - 5.0 - >>> v -= (3, 1) - >>> v.x, v.y - (0, 3) - >>> v.length - 3.0 - """ - if isinstance(other, tuple): - self.x -= other[0] - self.y -= other[1] - else: - self.x -= other.x - self.y -= other.y - return self - - def __div__(self, k): - return Vector2D(self.x / k, self.y / k) - - def __idiv__(self, k): - self.x /= k - self.y /= k - return self - - def __mul__(self, k): - return Vector2D(self.x * k, self.y * k) - - def __imul__(self, k): - self.x *= k - self.y *= k - return self - - def __neg__(self): - return Vector2D(-self.x, -self.y) - - def __eq__(self, other): - """Compare two vectors for equality - - >>> Vector2D(4, 2) == Vector2D(4, 2) - True - >>> Vector2D(4, 2) == Vector2D(2, 4) - False - """ - return self.x == other.x and self.y == other.y - - def clamp(self, k): - if self.length > k: - return self * (k / self.length) - return self - - def clamp_ip(self, k): - """Clamp vector to length in place""" - if self.length > k: - self *= (k / self.length) - - @property - def radians(self): - if self._radians is None: - self._radians = math.atan2(self.y, self.x) % fullcircle - return self._radians - - def distance(self, other): - return (self - other).length - - @property - def length(self): - # a^2 + b^2 = c^2 - if self._length is None: - self._length = math.sqrt(self.x**2 + self.y**2) - return self._length - - def dotprod(self, other): - """Compute the dot product of two vectors returning the - scalar value - """ - theta = self.radians - other.radians - return self.length * other.length * math.cos(theta) - - @property - def normal(self): - if self._normal is None: - if self.length == 0: - self._normal = Vector2D() - else: - self._normal = self / self.length - return self._normal - - def copy(self): - """Return a new vector equal to self - - >>> v = Vector2D(5, 6) - >>> v2 = v.copy() - >>> v.x == v2.x and v.y == v2.y and v.length == v2.length - True - """ - cp = Vector2D(self.x, self.y) - cp._radians = self._radians - cp._length = self._length - cp._normal = self._normal - return cp - - @classmethod - def unit(cls, radians): - """Return the unit vector for a given heading in radians - - >>> Vector2D.unit(0).radians - 0.0 - >>> Vector2D.unit(math.pi).radians == math.pi - True - >>> Vector2D.unit(math.pi*2).radians - 0.0 - """ - return _heading_vec[int(radians / unit_vector_res) % unit_vectors] - - def _clearcache(self): - """Invalidate cached properties""" - self._radians = None - self._length = None - self._normal = None - - def __nonempty__(self): - return self.x != 0 or self.y != 0 - - def __repr__(self): - return "<%s(%s, %s) at %x>" % ( - self.__class__.__name__, self.x, self.y, id(self)) - -class SyncedVector(Vector2D): - """Vector synchronized to external """ - - def __init__(self, getter, setter): - """Create vector where the coordinate values are retrieved via the - getter (which returns a tuple) and set via the setter which accepts a - sequence with coordinate values - - >>> class VecSource: - ... vec = (0, 0, 0) - ... def get(self): - ... return tuple(self.vec) - ... def set(self,v): - ... self.vec = tuple(v) - ... - >>> src = VecSource() - >>> sv = SyncedVector(src.get, src.set) - >>> sv.x, sv.y - (0, 0) - >>> sv.length - 0.0 - >>> sv.x = 3 - >>> sv.y = 4 - >>> sv.x, sv.y - (3, 4) - >>> sv[0], sv[1] - (3, 4) - >>> sv.length - 5.0 - >>> src.vec - (3, 4, 0) - >>> sv.x *= 2 - >>> sv.x, sv.y - (6, 4) - >>> sv.length == Vector2D(6, 4).length - True - >>> src.vec - (6, 4, 0) - """ - self.getter = getter - self.setter = setter - self.sync() - - def sync(self): - """Synchronize our coordinate values, retrieving them using the getter - """ - self._clearcache() - self._vec = list(self.getter()) - - def x_fget(self): - return self._vec[0] - def x_fset(self, value): - self._clearcache() - self._vec[0] = value - self.setter(self._vec) - x = property(fget=x_fget, fset=x_fset) - - def y_fget(self): - return self._vec[1] - def y_fset(self, value): - self._clearcache() - self._vec[1] = value - self.setter(self._vec) - y = property(fget=y_fget, fset=y_fset) - - def __getitem__(self, index): - return self._vec[index] - -class PositionVector(SyncedVector): - """Vector bound to ode body position""" - - def __init__(self, body): - """Create vector bound to body, an ode Body object""" - SyncedVector.__init__(self, body.getPosition, body.setPosition) - -class VelocityVector(SyncedVector): - """Vector bound to ode body velocity""" - - def __init__(self, body): - """Create vector bound to body, an ode Body object""" - SyncedVector.__init__(self, body.getLinearVel, body.setLinearVel) - # angle aliases fullcircle = math.pi * 2 halfcircle = math.pi rightangle = math.pi / 2 diagonal = math.pi / 4 -# Cached unit vector map -_heading_vec = {} -for d in range(unit_vectors): - r = d * unit_vector_res - uv = Vector2D(math.cos(r), math.sin(r)) - _heading_vec[d] = uv - - -if __name__ == '__main__': - """Run tests if executed directly""" - import sys, doctest - failed, count = doctest.testmod() - print 'Ran', count, 'test cases with', failed, 'failures' - sys.exit(failed) Modified: vessel.py =================================================================== --- vessel.py 2007-04-19 21:11:47 UTC (rev 180) +++ vessel.py 2007-04-21 06:31:39 UTC (rev 181) @@ -15,7 +15,8 @@ import sys from pygame.locals import * from media import RotatedImage -from vector import Vector2D, fullcircle, halfcircle, rightangle, diagonal +import vector +from vector import fullcircle, halfcircle, rightangle, diagonal import particle max_weapons = 5 @@ -130,7 +131,7 @@ self._sys = [] self._damage_sys = [] self.weapons = [] - self.heading = netsync.random.random() * fullcircle + self.set_heading(netsync.random.random() * fullcircle) self.damage_time = 0 @classmethod @@ -238,15 +239,15 @@ weapon.firing = ctrl_state for system in self._sys: system.update_system() - if self.velocity.length > self.max_speed: + if vector.length(self.velocity) > self.max_speed: # If we are overspeed, bleed off a little - overspeed = self.velocity.length - self.max_speed + overspeed = vector.length(self.velocity) - self.max_speed if overspeed < self.max_speed / 5: # not much overspeed, just clamp it - self.velocity.clamp_ip(self.max_speed) + self.set_velocity(vector.clamp(self.velocity, self.max_speed)) else: # very overspeed, clamp down quick - self.velocity.clamp_ip(self.max_speed + overspeed / 2) + self.set_velocity(vector.clamp(self.velocity, self.max_speed + overspeed / 2)) if self.explosion is None: self.image = self.vessel_img.rotated(self.heading) self.rect = self.image.get_rect(center=self.rect.center) @@ -514,9 +515,7 @@ >>> v1.bearing(v2) == -rightangle True """ - dx = other.position.x - self.position.x - dy = other.position.y - self.position.y - angle = math.atan2(dy, dx) - self.heading + angle = vector.radians(other.position - self.position) - self.heading if angle > halfcircle: angle -= fullcircle elif angle < -halfcircle: @@ -962,13 +961,13 @@ """ if self.enabled: if self.vessel.control.fw_maneuver: - self.vessel.push(Vector2D.unit(self.vessel.heading) * self.thrust) + self.vessel.push(vector.unit(self.vessel.heading) * self.thrust) if self.vessel.control.bw_maneuver: - self.vessel.push(Vector2D.unit(self.vessel.heading + halfcircle) * self.thrust) + self.vessel.push(vector.unit(self.vessel.heading + halfcircle) * self.thrust) if self.vessel.control.left_maneuver: - self.vessel.push(Vector2D.unit(self.vessel.heading - rightangle) * self.thrust) + self.vessel.push(vector.unit(self.vessel.heading - rightangle) * self.thrust) if self.vessel.control.right_maneuver: - self.vessel.push(Vector2D.unit(self.vessel.heading + rightangle) * self.thrust) + self.vessel.push(vector.unit(self.vessel.heading + rightangle) * self.thrust) class Engine(System): @@ -1030,7 +1029,7 @@ # overspeed, bleed off a little # this avoids a sudden decceleration when we let off boost self.vessel.max_speed = max(self.vessel.max_speed * 0.98, self.max_speed) - self.vessel.push(Vector2D.unit(self.vessel.heading) * self.thrust) + self.vessel.push(vector.unit(self.vessel.heading) * self.thrust) elif self.vessel.max_speed > self.max_speed: # overspeed, bleed off a little # this avoids a sudden decceleration when we let off boost This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-04-25 05:21:45
|
Revision: 187 http://eos-game.svn.sourceforge.net/eos-game/?rev=187&view=rev Author: cduncan Date: 2007-04-24 22:21:43 -0700 (Tue, 24 Apr 2007) Log Message: ----------- ooo-la-la! - Decouple ode step time from framerate, so physics speed is unaffected by graphics speed. This breaks multiplayer, I know, but the results are worth it and I think in the end this is the way to do muliplayer properly as well so that graphics speed is totally separated from network transit and physics. There is still more work to be done to get rid of frame-rate references throughout, but this is a major improvement as it is. - With the physics decoupled, remove the framerate throttle for fullscreen. This lets the graphics go at whatever rate SDL can flip the screen. The results are phenomenal, running bascially at the refresh rate of the LCD screen 60fps+! Also general game performance is greatly enhanced, I think because display.flip() is blocking on os x, and so timing it wrong can waste tons of cpu cycles. - Windowed mode gets periodic sleeps to keep the frame rate at 30fps. The LCD is capable of 60fps, but at 32bpp the rate jumps between 30fps and 60fps when lots of sprites are blitting causing the game to feel uneven. This also makes it friendlier to background apps which you might care more about in windowed mode. Modified Paths: -------------- eos.py game.py Modified: eos.py =================================================================== --- eos.py 2007-04-24 06:49:17 UTC (rev 186) +++ eos.py 2007-04-25 05:21:43 UTC (rev 187) @@ -41,6 +41,7 @@ opts, args = parser.parse_args() if args: raise optparse.OptParseError('Unrecognized args: %s' % args) + game.debug = opts.debug game.init(opts.server_game is not None, opts.server_game or opts.client_game, opts.host, opts.port, opts.player_count, opts.fullscreen, opts.frame_lag) start = pygame.time.get_ticks() Modified: game.py =================================================================== --- game.py 2007-04-24 06:49:17 UTC (rev 186) +++ game.py 2007-04-25 05:21:43 UTC (rev 187) @@ -6,6 +6,7 @@ # $Id$ import math +from time import sleep import media import netsync import ode @@ -30,13 +31,15 @@ # Other globally accessible things clock = None time = 0 # milliseconds since game began -frame_no = 0 # current frame number +frame_no = 5000 # current frame number frame_lag = 0 # expected number of lag frames players = None local_player = None camera = None soundtrack = None target = None +windowed = False +debug = False def init(run_server=False, game_id=None, host='127.0.0.1', port='11211', player_count=1, fullscreen=False, expected_lag=0): @@ -51,8 +54,9 @@ from map import Map global universe, collision_space, screen, screen_rect, background, map global sprites, fps, avg_fps, clock, time, players, local_player, camera, ai - global frame_lag, soundtrack, target + global frame_lag, soundtrack, target, windowed frame_lag = expected_lag + windowed = not fullscreen # Initialize pygame and setup main screen pygame.mixer.pre_init(11025) @@ -150,12 +154,14 @@ from ai import AIVessel import body import media - global universe, screen, background + import body + global universe, screen, background, debug global sprites, fps, avg_fps, clock, time, frame_no - global ai_interval + global ai_interval, windowed friends = pygame.sprite.Group() enemies = pygame.sprite.Group() next_wave = 0 + this_frame_time = last_frame_time = 1000 / fps while handle_events(): netsync.send_keystate(frame_no, pygame.key.get_pressed()) # send early handle_collisions() @@ -164,9 +170,16 @@ dirty = sprites.draw(screen) #pygame.display.update(dirty) pygame.display.flip() - universe.quickStep(1.0 / fps) # target FPS - time += clock.tick(fps) - avg_fps = clock.get_fps() or fps + universe.quickStep(this_frame_time / 1000.0) + last_time = time + time += clock.tick() + if windowed and time - last_time < 33: + sleep(0.02) + last_frame_time = this_frame_time + this_frame_time = min(time - last_time, last_frame_time * 3) + fps = avg_fps = clock.get_fps() or fps + if debug and frame_no % 30 == 0: + print body.body_count, len(sprites), avg_fps if frame_no >= frame_lag: netsync.step(frame_no - frame_lag) # retrieve late, after clock.tick throttle if time > next_wave: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-04-25 15:57:41
|
Revision: 190 http://eos-game.svn.sourceforge.net/eos-game/?rev=190&view=rev Author: cduncan Date: 2007-04-25 08:57:39 -0700 (Wed, 25 Apr 2007) Log Message: ----------- Use rotozoom for image rotation to get rid of the jaggies, the results are quite good. Since this changes the original image bitsize and flags there is an option to use the original rotate function when preserving those things is important. Modified Paths: -------------- media.py vessel.py Modified: media.py =================================================================== --- media.py 2007-04-25 07:09:20 UTC (rev 189) +++ media.py 2007-04-25 15:57:39 UTC (rev 190) @@ -39,7 +39,12 @@ steps = 300 # Rotational step count - def __init__(self, img_name): + def __init__(self, img_name, enhanced=True): + """Load img_name and store rotated versions for future use. If enhanced is True + Use a filtered rotation that results in 32-bit color images with far fewer + jaggies. Using enhanced will not preserve the image flags and will not allow + you to use set_alpha on the images + """ global _RotatedImage_cache self.step_angle = fullcircle / self.steps if (img_name not in _RotatedImage_cache @@ -48,8 +53,15 @@ self._rotated = {} angle = 0 for r in range(self.steps): - self._rotated[r] = optimize_image(pygame.transform.rotate( - start_img, 270 - math.degrees(angle))) + if enhanced: + # Use rotozoom for higher quality + self._rotated[r] = optimize_image(pygame.transform.rotozoom( + start_img, 270 - math.degrees(angle), 1.0)) + else: + # Use normal rotate to preserve image bit size and flags + self._rotated[r] = optimize_image(pygame.transform.rotate( + start_img, 270 - math.degrees(angle))) + angle += self.step_angle _RotatedImage_cache[img_name] = self._rotated else: Modified: vessel.py =================================================================== --- vessel.py 2007-04-25 07:09:20 UTC (rev 189) +++ vessel.py 2007-04-25 15:57:39 UTC (rev 190) @@ -611,7 +611,7 @@ regen_recovery = 5.0 # Time regeneration recovers after disruption # Cosmetic - opacity = 60 # 0-255 + opacity = 80 # 0-255 flicker = 12 # variance in opacity fadeout = 200 # millis shields fade when turned off timeout = 1500 # millis shields stay active @@ -621,7 +621,7 @@ self.vessel = vessel self.max_level = self.level = float(max_level) self.regeneration = self.max_regeneration = float(regeneration) - self.rot_images = RotatedImage('%s-shield.png' % vessel.vessel_class) + self.rot_images = RotatedImage('%s-shield.png' % vessel.vessel_class, enhanced=False) self.time = 0 self.enabled = True @@ -745,7 +745,7 @@ def __init__(self, vessel, durability, dissipation, regeneration=0): pygame.sprite.Sprite.__init__(self, game.sprites) - self.armor_img = RotatedImage('%s-armor.png' % vessel.vessel_class) + self.armor_img = RotatedImage('%s-armor.png' % vessel.vessel_class, enhanced=False) self.vessel = vessel self.durability = float(durability) self.dissipation = self.max_dissipate = float(dissipation) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-04-28 20:36:13
|
Revision: 194 http://eos-game.svn.sourceforge.net/eos-game/?rev=194&view=rev Author: oberon7 Date: 2007-04-28 13:36:10 -0700 (Sat, 28 Apr 2007) Log Message: ----------- Added rudimentary soundtrack configuration. Replaced previous audio files with rone ambient ogg. All races currently use the one piece of ambient music. Modified Paths: -------------- game.py media.py Added Paths: ----------- music/ music/naree/ music/rone/ music/rone/ambient.ogg music/rone/soundtrack Removed Paths: ------------- sounds/soundtrack01.ogg sounds/soundtrack02.ogg sounds/soundtrack03.ogg sounds/soundtrack04.ogg sounds/soundtrack05.ogg sounds/soundtrack06.ogg sounds/soundtrack07.ogg sounds/suspense01.ogg sounds/suspense02.ogg sounds/suspense03.ogg sounds/suspense04.ogg sounds/suspense05.ogg sounds/suspense06.ogg Modified: game.py =================================================================== --- game.py 2007-04-27 08:19:11 UTC (rev 193) +++ game.py 2007-04-28 20:36:10 UTC (rev 194) @@ -64,7 +64,7 @@ # Kickstart the soundtrack pygame.mixer.set_reserved(1) soundtrack = pygame.mixer.Channel(0) - media.soundtrack_loader.start() + media.Soundtrack('rone').start() # FIXME # Get the available display modes and use the best one if fullscreen: @@ -184,8 +184,6 @@ target = netsync.random.choice(map.planets) position = vector.unit(netsync.random.random() * vector.fullcircle) * 1000 warship = netsync.random.random() * frame_no > 800 - if target is local_player: - media.play_soundtrack(large=warship) if warship and len(enemies) < max_fleet_size: ai = AIVessel.load('vessels/naree/lotus') ai.set_position(target.position + position) Modified: media.py =================================================================== --- media.py 2007-04-27 08:19:11 UTC (rev 193) +++ media.py 2007-04-28 20:36:10 UTC (rev 194) @@ -5,10 +5,12 @@ # Graphics and sound utilities # $Id$ +import ConfigParser import math import glob import random import threading +import time import pygame from pygame.constants import * import game @@ -116,43 +118,84 @@ sound.play() # avoids the reserved soundtrack channel _sound_last_played[name] = game.frame_no -class SoundtrackLoader(threading.Thread): - """Load soundtracks in a background thread""" +class SoundtrackAudio(): + """Audio track for soundtrack""" - def __init__(self): + def __init__(self, sound, min_loops, max_loops): + self.sound = sound + self.min_loops = min_loops + self.max_loops = max_loops + +class Soundtrack(threading.Thread): + """Background thread to play the soundtrack""" + + def __init__(self, race): threading.Thread.__init__(self) - self.soundtracks = [] - self.suspense = [] - - def load(self, file_pattern, sound_list): - for filename in glob.glob(file_pattern): + self.setDaemon(True) + self.race = race + self.tracks = [] + self.last_track = None + + def load(self, config_file): + """Create a soundtrack from a config file. config_file is either a + readable file-like object or a file name. + + The config file is in ConfigParser format and contains a [*.ogg] + section for each individual audio file which makes up the soundtrack. + The values under each section describe the properties that should + be used for that audio file (e.g. volume, how many loops, etc). See + below for an example. + + >>> config = ''' + ... [music/rone/ambient.ogg] + ... volume: 0.4 + ... min_loops: 1 + ... max_loops: 3 + ... ''' + >>> pygame.mixer.init() + >>> import StringIO + >>> s = Soundtrack(None) + >>> s.load(StringIO.StringIO(config)) + >>> len(s.tracks) + 1 + >>> track = s.tracks[0] + >>> track.sound is not None + True + >>> round(track.sound.get_volume(), 1) == 0.4 + True + >>> track.min_loops + 1 + >>> track.max_loops + 3 + """ + if isinstance(config_file, str): + config_file = open(config_file, 'rt') + parser = ConfigParser.SafeConfigParser() + parser.readfp(config_file) + for filename in parser.sections(): + volume = parser.getfloat(filename, 'volume') + min_loops = parser.getint(filename, 'min_loops') + max_loops = parser.getint(filename, 'max_loops') sound = pygame.mixer.Sound(filename) - sound_list.append(sound) + sound.set_volume(volume) + self.tracks.append(SoundtrackAudio(sound, min_loops, max_loops)) def run(self): - self.load('sounds/suspense*.ogg', self.suspense) - self.load('sounds/soundtrack*.ogg', self.soundtracks) + self.load('music/%s/soundtrack' % self.race) + while True: + while game.soundtrack.get_busy(): + time.sleep(1) + while True: + track = random.choice(self.tracks) + if track is not self.last_track or len(self.tracks) <= 1: + break # avoid consecutive repeats + loops = random.randint(track.min_loops, track.max_loops) + game.soundtrack.play(track.sound, loops) + self.last_track = track -soundtrack_loader = SoundtrackLoader() -last_soundtrack = None - -def play_soundtrack(large=False, volume=0.6): - global soundtrack_loader, last_soundtrack - while 1: - if large: - if soundtrack_loader.soundtracks: - sound = random.choice(soundtrack_loader.soundtracks) - else: - return - else: - if soundtrack_loader.soundtracks: - sound = random.choice(soundtrack_loader.suspense) - else: - return - if sound is not last_soundtrack: - # Avoid playing the same one twice in a row - last_soundtrack = sound - break - sound.set_volume(volume) - game.soundtrack.fadeout(2000) - game.soundtrack.queue(sound) +if __name__ == '__main__': + """Run tests if executed directly""" + import sys, doctest + failed, count = doctest.testmod() + print 'Ran', count, 'test cases with', failed, 'failures' + sys.exit(failed) Added: music/rone/ambient.ogg =================================================================== (Binary files differ) Property changes on: music/rone/ambient.ogg ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: music/rone/soundtrack =================================================================== --- music/rone/soundtrack (rev 0) +++ music/rone/soundtrack 2007-04-28 20:36:10 UTC (rev 194) @@ -0,0 +1,4 @@ +[music/rone/ambient.ogg] +volume: 1.0 +min_loops: -1 +max_loops: -1 Deleted: sounds/soundtrack01.ogg =================================================================== (Binary files differ) Deleted: sounds/soundtrack02.ogg =================================================================== (Binary files differ) Deleted: sounds/soundtrack03.ogg =================================================================== (Binary files differ) Deleted: sounds/soundtrack04.ogg =================================================================== (Binary files differ) Deleted: sounds/soundtrack05.ogg =================================================================== (Binary files differ) Deleted: sounds/soundtrack06.ogg =================================================================== (Binary files differ) Deleted: sounds/soundtrack07.ogg =================================================================== (Binary files differ) Deleted: sounds/suspense01.ogg =================================================================== (Binary files differ) Deleted: sounds/suspense02.ogg =================================================================== (Binary files differ) Deleted: sounds/suspense03.ogg =================================================================== (Binary files differ) Deleted: sounds/suspense04.ogg =================================================================== (Binary files differ) Deleted: sounds/suspense05.ogg =================================================================== (Binary files differ) Deleted: sounds/suspense06.ogg =================================================================== (Binary files differ) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-05-04 07:33:10
|
Revision: 212 http://eos-game.svn.sourceforge.net/eos-game/?rev=212&view=rev Author: cduncan Date: 2007-05-04 00:33:07 -0700 (Fri, 04 May 2007) Log Message: ----------- - Implement basic stand-alone camera with inertia effect. Camera pans to new targets rather than jumping immediately. - Abstract out image scaling and fix color key transparency used by the shields, armor and large explosion. The latter needs to be re-rendered in RGBA to look right, though. - Zoom shield and armor overlays, they're better now, but still not perfect. - Add a cache for scaled images to save time rotozooming. In fact it looks like this is only needed for planets as rotozooming small sprites in extremely fast, but I'm checking this in to test on slower machines first. The cache hit rate is pretty low though due to the sheer number of images needed given both rotation and scaling. Modified Paths: -------------- body.py game.py media.py stars.py staticbody.py vessel.py Added Paths: ----------- camera.py Modified: body.py =================================================================== --- body.py 2007-05-04 07:11:19 UTC (rev 211) +++ body.py 2007-05-04 07:33:07 UTC (rev 212) @@ -184,10 +184,8 @@ self.apparent_size = apparent_size elif self.apparent_size is None or abs( apparent_size - self.apparent_size) > self.apparent_size / 10: - self.image = image = pygame.transform.rotozoom(self.src_image, 0, apparent_size) - transparent = image.get_colorkey() - if transparent: - image.set_colorkey(transparent, RLEACCEL) + self.image = media.scale_image(self.src_image, apparent_size, + colorkey=self.explosion is not None) self.apparent_size = apparent_size self.rect = self.image.get_rect(center=self.rect.center) Added: camera.py =================================================================== --- camera.py (rev 0) +++ camera.py 2007-05-04 07:33:07 UTC (rev 212) @@ -0,0 +1,50 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Camera +# $Id$ + +import pygame +import game +import vessel +import vector + +class Camera: + + centering = 0.98 # Lower values == more centering force + adhesion = 9.0 # Higher values == closer camera tracking under acceleration + + def __init__(self, target): + self.target = target + self.rect =pygame.Rect(game.screen_rect.width / 2, game.screen_rect.height / 2, 0, 0) + self.offset = vector.vector2() + self.last_velocity = target.velocity + self.zoom = 1.0 + + @property + def position(self): + # Use a property to track target position precisely, avoiding lag and jitters + return self.target.position + self.offset + + def follow(self, target): + self.last_velocity = target.velocity + self.offset = self.target.position - target.position + self.target = target + + def update(self): + if not self.target.alive(): + self.acquire_target() + self.offset *= self.centering + accel = self.target.velocity - self.last_velocity + self.offset += -vector.normal(accel) * (vector.length(accel) / self.adhesion)**2 + self.last_velocity = self.target.velocity + + def acquire_target(self): + # Select a new target + for sprite in game.sprites.sprites(): + if (sprite.alive() and isinstance(sprite, vessel.Vessel) + and sprite.vessel_type != 'missile'): + self.follow(sprite) + break + Property changes on: camera.py ___________________________________________________________________ Name: svn:keywords + Id Name: svn:eol-style + native Modified: game.py =================================================================== --- game.py 2007-05-04 07:11:19 UTC (rev 211) +++ game.py 2007-05-04 07:33:07 UTC (rev 212) @@ -51,6 +51,7 @@ from vessel import Vessel, KeyboardControl from media import load_image from map import Map + from camera import Camera global universe, collision_space, screen, screen_rect, background, map global sprites, fps, avg_fps, clock, time, players, local_player, camera, ai global frame_lag, target, windowed, new_sprites @@ -62,7 +63,7 @@ pygame.init() # Get the available display modes and use the best one - if fullscreen: + if fullscreen and not debug: modes = pygame.display.list_modes() # Choose the first mode with a height between 700 and 800 px for mode in modes: @@ -97,9 +98,7 @@ # Establish networking players, local_player = netsync.init( run_server, game_id, host, port, player_count, race, ship) - camera = local_player - camera.rect.center = screen.get_rect().center - camera.zoom = 1.0 + camera = Camera(local_player) target = selection.Target(local_player) # Kickstart the soundtrack @@ -152,7 +151,7 @@ import media global universe, screen, background, debug global sprites, new_sprites, fps, avg_fps, clock, time, frame_no - global ai_interval, windowed + global ai_interval, windowed, camera friends = pygame.sprite.Group() enemies = pygame.sprite.Group() next_wave = 0 @@ -167,6 +166,7 @@ new_sprites.empty() sprites.clear(screen, background) sprites.update() + camera.update() dirty = sprites.draw(screen) #pygame.display.update(dirty) pygame.display.flip() @@ -179,7 +179,7 @@ this_frame_time = min(time - last_time, last_frame_time * 3) fps = clock.get_fps() or fps if debug and frame_no % 30 == 0: - print body.body_count, len(sprites), fps + print body.body_count, len(sprites), len(media._scaled_image_cache), fps if frame_no >= frame_lag: netsync.step(frame_no - frame_lag) # retrieve late, after clock.tick throttle if time > next_wave: Modified: media.py =================================================================== --- media.py 2007-05-04 07:11:19 UTC (rev 211) +++ media.py 2007-05-04 07:33:07 UTC (rev 212) @@ -34,6 +34,77 @@ else: return image.convert() +class Cache: + """Simple, fast, bounded cache that gives approximate MRU behavior""" + + def __init__(self, max_size, load_factor=0.85): + self.max_size = max_size + self.max_recent_size = int(max_size * load_factor) + self._recent = {} # Recently accessed bucket + self._aged = {} # Less recently accessed bucket + self.accesses = 0 + self.misses = 0 + self.adds = 0 + self.flips = 0 + self.purged = 0 + + def __getitem__(self, key): + self.accesses += 1 + try: + try: + return self._recent[key] + except KeyError: + # Promote aged element to "recent" + value = self._aged.pop(key) + self._recent[key] = value + return value + except KeyError: + self.misses += 1 + raise + + def __len__(self): + return len(self._recent) + len(self._aged) + + def __contains__(self, key): + return key in self._recent or key in self._aged + + def __setitem__(self, key, value): + assert value is not None + self.adds += 1 + if key in self._aged: + del self._aged[key] + if len(self._recent) + 1 > self.max_recent_size: + # Flip the cache discarding aged entries + self.flips += 1 + print self.flips, 'cache flips in', self.adds, ' adds. ', + print self.misses, 'misses in', self.accesses, 'accesses (', + print (self.accesses - self.misses) * 100 / self.accesses, '% hit rate) ', + print 'with', self.purged, 'purged' + self._aged = self._recent + self._recent = {} + self._recent[key] = value + while self._aged and len(self) > self.max_size: + # Over max size, purge aged entries + self.purged += 1 + self._aged.popitem() + +_scaled_image_cache = Cache(2000) + +def scale_image(image, scale, colorkey=False): + scaled_width = int(image.get_rect().width * scale) + try: + return _scaled_image_cache[image, scaled_width] + except KeyError: + if scale != 1.0: + zoomed_image = pygame.transform.rotozoom(image, 0, scale) + if colorkey: + zoomed_image = zoomed_image.convert() + zoomed_image.set_colorkey(zoomed_image.get_at((0, 0))) + else: + zoomed_image = image + _scaled_image_cache[image, scaled_width] = zoomed_image + return zoomed_image + _RotatedImage_cache = {} class RotatedImage: Modified: stars.py =================================================================== --- stars.py 2007-05-04 07:11:19 UTC (rev 211) +++ stars.py 2007-05-04 07:33:07 UTC (rev 212) @@ -118,5 +118,5 @@ self.nebula_pos[1] += vec[1] * 0.15 def update(self): - star_velocity = game.camera.zoom * -game.camera.velocity / game.fps + star_velocity = game.camera.zoom * -game.camera.target.velocity / game.fps self.move(vector.to_tuple(star_velocity)) Modified: staticbody.py =================================================================== --- staticbody.py 2007-05-04 07:11:19 UTC (rev 211) +++ staticbody.py 2007-05-04 07:33:07 UTC (rev 212) @@ -42,8 +42,7 @@ apparent_size, screen_pos = vector.to_screen(self.position, 1.0) if apparent_size != self.apparent_size and self.on_screen: self.apparent_size = apparent_size - self.image = pygame.transform.rotozoom( - self.src_image, 0, apparent_size * self.max_size_factor) + self.image = media.scale_image(self.src_image, apparent_size) self.rect=self.image.get_rect(center=vector.to_tuple(screen_pos)) else: self.rect.center = vector.to_tuple(screen_pos) Modified: vessel.py =================================================================== --- vessel.py 2007-05-04 07:11:19 UTC (rev 211) +++ vessel.py 2007-05-04 07:33:07 UTC (rev 212) @@ -15,6 +15,7 @@ import random import sys from pygame.locals import * +import media from media import RotatedImage import vector from vector import fullcircle, halfcircle, rightangle, diagonal @@ -514,15 +515,6 @@ if hasattr(system, 'kill'): system.kill() body.RoundBody.kill(self) - if game.camera is self: - for sprite in game.sprites.sprites(): - if (sprite.alive() and isinstance(sprite, Vessel) - and sprite.vessel_type != 'missile'): - game.camera = sprite - game.camera.zoom = 1 - sprite.off_screen = False - sprite.rect.center = game.screen_rect.center - break def bearing(self, other): """Return the clockwise angle between the vessel heading and @@ -690,6 +682,8 @@ if self.time >= game.time and self.level: image = self.rot_images.rotated( math.radians(game.time / 30)) + if self.vessel.apparent_size is not None: + self.image = media.scale_image(image, self.vessel.apparent_size, colorkey=True) flicker = int(self.flicker + self.flicker * (self.max_level / self.level)) flicker = random.randint(0, flicker) opacity = self.opacity * (self.level / self.max_level / 2) + self.opacity @@ -770,13 +764,16 @@ def draw(self, surface): if self.heat > 0 and self.vessel.explosion is None: image = self.armor_img.rotated(self.vessel.heading) + if self.vessel.apparent_size is not None: + self.image = media.scale_image(image, self.vessel.apparent_size, colorkey=True) rect = image.get_rect(center=self.vessel.rect.center) if self.durability: level = min(self.heat / self.durability, 1.0) else: level = 1.0 - opacity = (math.cos(math.pi * game.time / 1000) + 1.0) * (level * 40) + ( - level * 175) + period = math.pi * game.time / 1000 + opacity = 1 - math.cos(period)**2 / (math.sin(period)**2 + 1) + opacity *= (level * 40) + (level * 175) image.set_alpha(opacity) surface.blit(image, rect) return rect This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-05-05 07:28:19
|
Revision: 215 http://eos-game.svn.sourceforge.net/eos-game/?rev=215&view=rev Author: cduncan Date: 2007-05-05 00:28:17 -0700 (Sat, 05 May 2007) Log Message: ----------- Add simple frontend to choose your ship if not specified on the cmd line Modified Paths: -------------- game.py vessel.py Modified: game.py =================================================================== --- game.py 2007-05-04 22:07:15 UTC (rev 214) +++ game.py 2007-05-05 07:28:17 UTC (rev 215) @@ -82,9 +82,11 @@ print "Could not find ideal display mode, using", mode print mode screen = pygame.display.set_mode(mode, FULLSCREEN | HWSURFACE | DOUBLEBUF, 32) + pygame.mouse.set_visible(False) else: screen = pygame.display.set_mode(window_size) pygame.display.set_icon(load_image('eos-icon.png')) + screen_rect = screen.get_rect() pygame.display.set_caption('Eos') background = pygame.Surface(screen.get_size()) @@ -104,6 +106,63 @@ StarField(screen.get_rect()) map = Map('sol') + if ship is None and race is None: + stars = StarField(screen.get_rect()) + stars.draw(screen) + pygame.display.flip() + # Select ship + ships = [ + Vessel.load('vessels/rone/draken'), + Vessel.load('vessels/rone/drach'), + Vessel.load('vessels/naree/lotus'), + Vessel.load('vessels/naree/cress'), + ] + import math + font = pygame.font.Font('fonts/forgottenfuturist/Forgotbi.ttf', 24) + title_image = font.render('Select Ship', True, (255, 255, 255)) + arrow_image = media.RotatedImage('pointy-green.gif') + left_arrow = arrow_image.rotated(math.pi) + right_arrow = arrow_image.rotated(0) + choice = 0 + event = handle_events() + rot = -math.pi / 2 + while ship is None: + screen.blit(background, (0, 0)) + stars.move((1, 1)) + stars.draw(screen) + screen.blit(title_image, title_image.get_rect( + centerx=screen_rect.centerx, centery=screen_rect.centery - 75)) + chosen_ship = ships[choice] + ship_img = chosen_ship.vessel_img.rotated(rot) + screen.blit(ship_img, ship_img.get_rect(center=screen_rect.center)) + ship_name = font.render( + ('%s %s' % (chosen_ship.vessel_class, chosen_ship.vessel_type)).title(), + True, (255, 255, 255)) + screen.blit(ship_name, ship_name.get_rect( + centerx=screen_rect.centerx, centery=screen_rect.centery + 75)) + screen.blit(left_arrow, left_arrow.get_rect( + centerx=screen_rect.centerx - 75, centery=screen_rect.centery)) + screen.blit(right_arrow, right_arrow.get_rect( + centerx=screen_rect.centerx + 75, centery=screen_rect.centery)) + pygame.display.flip() + sleep(0.022) + rot += math.pi / 100 + time += 22 + for event in pygame.event.get(): + if event.type == QUIT or ( + event.type == KEYDOWN and event.key == K_ESCAPE): + raise SystemExit + elif event.type == KEYDOWN and event.key == K_RIGHT: + choice = (choice + 1) % len(ships) + elif event.type == KEYDOWN and event.key == K_LEFT: + choice = (choice - 1) % len(ships) + elif event.type == KEYDOWN and event.key == K_RETURN: + ship = ships[choice].vessel_class + stars.kill() + for ship_choice in ships: + ship_choice.kill() + timw = 0 + # Establish networking players, local_player = netsync.init( run_server, game_id, host, port, player_count, race, ship) @@ -113,9 +172,6 @@ # Kickstart the soundtrack media.Soundtrack(local_player.race).start() - if fullscreen: - pygame.mouse.set_visible(False) - def handle_events(): for event in pygame.event.get(): if event.type == QUIT or ( Modified: vessel.py =================================================================== --- vessel.py 2007-05-04 22:07:15 UTC (rev 214) +++ vessel.py 2007-05-05 07:28:17 UTC (rev 215) @@ -547,7 +547,7 @@ def __str__(self): return '<%s@%x "%s" at (%d, %d)>' % ( - self.__class__.__name__, id(self), self.vessel_class, self.position.x, self.position.y) + self.__class__.__name__, id(self), self.vessel_class, vector.to_tuple(self.position)) class System: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-05-05 17:29:31
|
Revision: 216 http://eos-game.svn.sourceforge.net/eos-game/?rev=216&view=rev Author: cduncan Date: 2007-05-05 10:29:29 -0700 (Sat, 05 May 2007) Log Message: ----------- Use lower resolution and pixel depth on powerpc macs for performance Modified Paths: -------------- eos.py game.py Modified: eos.py =================================================================== --- eos.py 2007-05-05 07:28:17 UTC (rev 215) +++ eos.py 2007-05-05 17:29:29 UTC (rev 216) @@ -45,7 +45,7 @@ dest='debug', default=False, action='store_true', help='Drop into the debugger on error') parser.add_option('-g', '--resolution', - dest='resolution', default='high', choices=('low', 'med', 'high', 'max'), + dest='resolution', choices=('low', 'med', 'high', 'max'), help='Graphics quality: low, med, high or max. Default: high') opts, args = parser.parse_args() if args: raise optparse.OptParseError('Unrecognized args: %s' % args) Modified: game.py =================================================================== --- game.py 2007-05-05 07:28:17 UTC (rev 215) +++ game.py 2007-05-05 17:29:29 UTC (rev 216) @@ -7,6 +7,7 @@ import math from time import sleep +import platform import media import netsync import ode @@ -42,7 +43,7 @@ def init(run_server=False, game_id=None, host='127.0.0.1', port='11211', player_count=1, fullscreen=False, expected_lag=0, race=None, ship=None, - resolution='high'): + resolution=None): import body import selection from sprite import RenderedGroup @@ -64,6 +65,15 @@ pygame.init() # Get the available display modes and use the best one + if platform.system() == 'Darwin' and platform.processor() == 'powerpc': + # Use lower quailty setting by default on powerpc macs for performance + pixel_depth = 16 + if resolution is None: + resolution = 'med' + else: + pixel_depth = 32 + if resolution is None: + resolution = 'high' screen_sizes = { 'max': ((0, 100000), (1280, 1024)), 'high': ((650, 800), (1024, 768)), @@ -81,7 +91,7 @@ mode = modes[0] print "Could not find ideal display mode, using", mode print mode - screen = pygame.display.set_mode(mode, FULLSCREEN | HWSURFACE | DOUBLEBUF, 32) + screen = pygame.display.set_mode(mode, FULLSCREEN | HWSURFACE | DOUBLEBUF, pixel_depth) pygame.mouse.set_visible(False) else: screen = pygame.display.set_mode(window_size) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-05-13 00:41:38
|
Revision: 226 http://eos-game.svn.sourceforge.net/eos-game/?rev=226&view=rev Author: cduncan Date: 2007-05-12 17:41:35 -0700 (Sat, 12 May 2007) Log Message: ----------- Use collision radius to determine ai standoff distance Modified Paths: -------------- ai.py staticbody.py Modified: ai.py =================================================================== --- ai.py 2007-05-13 00:20:30 UTC (rev 225) +++ ai.py 2007-05-13 00:41:35 UTC (rev 226) @@ -158,14 +158,14 @@ position = self.vessel.position target = self.target.sprite if not target.velocity and vector.distance( - position, target.position) < target.radius * 1.5: + position, target.position) < target.collision_radius * 1.5: # close in to stationary target return vector.radians(target.position - position), target.velocity predict_ahead = vector.distance(position + velocity * predict_ahead, target.position + target.velocity * predict_ahead) / self.vessel.max_speed approach = (target.position + target.velocity * min(predict_ahead, 5.0)) - ( position + velocity * 0.75) - if vector.length(approach) > target.radius * 6: + if vector.length(approach) > target.collision_radius * 6: # Far from target, catch up as fast as we can return vector.radians(approach), vector.clamp( approach * game.fps, self.vessel.max_speed) @@ -178,7 +178,8 @@ # Face enemy target head-on heading = vector.radians(target.position - position) return heading, vector.clamp(-(approach * - (self.vessel.radius * 6 - vector.length(approach))), self.vessel.max_speed) + (self.vessel.collision_radius * 6 - vector.length(approach))), + self.vessel.max_speed) def evade(self): """Return desired velocity away from where we predict Modified: staticbody.py =================================================================== --- staticbody.py 2007-05-13 00:20:30 UTC (rev 225) +++ staticbody.py 2007-05-13 00:41:35 UTC (rev 226) @@ -28,7 +28,7 @@ self.image = self.src_image = media.load_image(random.choice(image.split(','))) self.offscreen_img = RotatedImage('pointy-blue.gif') self.rect = self.image.get_rect() - self.radius = self.rect.width / 2 + self.collision_radius = self.radius = self.rect.width / 2 self.apparent_size = None self.max_radius = self.radius * self.max_size_factor This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-09-15 16:16:13
|
Revision: 255 http://eos-game.svn.sourceforge.net/eos-game/?rev=255&view=rev Author: cduncan Date: 2007-09-15 09:16:10 -0700 (Sat, 15 Sep 2007) Log Message: ----------- Speed up blitting by making use of RLEACCEL whenever possible Modified Paths: -------------- media.py panel.py stars.py Modified: media.py =================================================================== --- media.py 2007-09-13 07:43:33 UTC (rev 254) +++ media.py 2007-09-15 16:16:10 UTC (rev 255) @@ -30,7 +30,9 @@ if image.get_bitsize() == 32: # Image has alpha channel, note this prevents # applying alpha on blit - return image.convert_alpha() + image = image.convert_alpha() + image.set_alpha(0, pygame.RLEACCEL) + return image else: return image.convert() @@ -91,15 +93,18 @@ _scaled_image_cache = Cache(6000) def scale_image(image, scale, colorkey=False): - scaled_width = int(image.get_rect().width * scale) + orig_width = image.get_rect().width + scaled_width = int(orig_width * scale) try: return _scaled_image_cache[image, scaled_width] except KeyError: - if scale != 1.0: + if scaled_width != orig_width: zoomed_image = pygame.transform.rotozoom(image, 0, scale) if colorkey: zoomed_image = zoomed_image.convert() - zoomed_image.set_colorkey(zoomed_image.get_at((0, 0))) + zoomed_image.set_colorkey(zoomed_image.get_at((0, 0)), pygame.RLEACCEL) + else: + zoomed_image.set_alpha(0, pygame.RLEACCEL) else: zoomed_image = image _scaled_image_cache[image, scaled_width] = zoomed_image Modified: panel.py =================================================================== --- panel.py 2007-09-13 07:43:33 UTC (rev 254) +++ panel.py 2007-09-15 16:16:10 UTC (rev 255) @@ -190,6 +190,7 @@ image.blit(planet_img, planet_rect) self.map_points = pygame.Surface(self.rect.size, pygame.SRCALPHA, 32) self.map_mask = pygame.Surface(self.rect.size, pygame.SRCALPHA, 32) + image.set_alpha(0, pygame.RLEACCEL) return image def update_panel(self): @@ -266,6 +267,7 @@ True, (255, 255, 255)) info_rect = info_img.get_rect(left=4, top=info_rect.bottom) image.blit(info_img, info_rect) + image.set_alpha(0, pygame.RLEACCEL) def draw(self, surface): surface.blit(self.image, self.rect) Modified: stars.py =================================================================== --- stars.py 2007-09-13 07:43:33 UTC (rev 254) +++ stars.py 2007-09-15 16:16:10 UTC (rev 255) @@ -51,6 +51,7 @@ self.nebula_image = pygame.transform.rotozoom( self.nebula_image, 30.0, float(game.screen_rect.width) / float(camera.Camera.virtual_width)) + self.nebula_image.set_alpha(255, pygame.RLEACCEL) rect = self.nebula_image.get_rect(center=game.screen_rect.center) self.nebula_pos = [float(rect.left), float(rect.top)] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-09-25 21:39:54
|
Revision: 264 http://eos-game.svn.sourceforge.net/eos-game/?rev=264&view=rev Author: oberon7 Date: 2007-09-25 14:39:01 -0700 (Tue, 25 Sep 2007) Log Message: ----------- Work-in-progress multiplayer. Remote players are now modeled as RemoteAI vessels, instead of special KeyboardControl vessels. Modified Paths: -------------- ai.py net.py vessel.py Modified: ai.py =================================================================== --- ai.py 2007-09-24 16:54:03 UTC (rev 263) +++ ai.py 2007-09-25 21:39:01 UTC (rev 264) @@ -336,6 +336,55 @@ self.close_vessels.add(other) +class RemoteAI(BasicAI): + """Remote player AI control""" + + def __init__(self, *args, **kw): + BasicAI.__init__(self, *args, **kw) + #self.vessel.max_speed *= 1.5 + #self.vessel.turn_rate *= 2 + self.future_position = None + self.future_velocity = None + self.future_heading = None + self.state_history = [] + + def update(self): + if self.future_position: + offset = self.future_position - self.vessel.position + distance = vector.length(offset) + distant_speed = min(distance, self.vessel.max_speed) + distant_velocity = vector.normal(offset) + distant_heading = vector.normal(distant_velocity) + proximal_speed = vector.length(self.future_velocity) + proximal_velocity = vector.normal(self.future_velocity) + proximal_heading = vector.unit(self.future_heading) + speed = ((distant_speed * distance * distance) + (proximal_velocity * 1000000)) / ((distance * distance) + 1000000) + velocity = (distant_velocity * distance * distance) + (proximal_velocity * 1000000) + heading = (distant_heading * distance * distance) + (proximal_heading * 1000000) + self.steer(vector.radians(heading), vector.normal(velocity) * speed) + + def set_state(self, state): + self.state_history.insert(0, state) + if len(self.state_history) > 2: + del self.state_history[2:] + self.future_position = (self.state_history[0]['position'] + + ((self.state_history[0]['position'] + - self.state_history[1]['position']) * 2)) + self.future_velocity = (self.state_history[0]['velocity'] + + ((self.state_history[0]['velocity'] + - self.state_history[1]['velocity']) * 2)) + self.future_heading = vector.radians( + vector.unit(self.state_history[0]['heading']) + + ((vector.unit(self.state_history[0]['heading']) + - vector.unit(self.state_history[1]['heading'])) * 2)) + else: + #self.vessel.directional_thrusters.thrust *= 1.5 + #self.vessel.maneuvering_thrusters.thrust *= 1.1 + self.vessel.set_position(state['position']) + self.vessel.set_velocity(state['velocity']) + self.vessel.set_heading(state['heading']) + + class GnatAI(BasicAI): """Gnat AI control""" @@ -409,7 +458,11 @@ ai_class = globals()[ai] self.control = ai_class(self, target, mothership, sensor) + def set_state(self, state): + Vessel.set_state(self, state) + self.control.set_state(state) + class Sensor: """Detects bodies in the vicinity of a host ship""" Modified: net.py =================================================================== --- net.py 2007-09-24 16:54:03 UTC (rev 263) +++ net.py 2007-09-25 21:39:01 UTC (rev 264) @@ -5,6 +5,7 @@ # Multiplayer networking # $Id$ +import ai import game import marshal import message @@ -14,7 +15,6 @@ import struct import threading import time -import vessel class SocketCommunicator: @@ -22,8 +22,9 @@ control_size = struct.calcsize(control_fmt) buffer_size = 1024 - def __init__(self): + def __init__(self, latency=None): self.data = '' + self.latency = latency def send(self, sock, *args): payload = marshal.dumps(args) @@ -31,6 +32,8 @@ sock.sendall(payload) def receive(self, sock): + if self.latency: + time.sleep(self.latency) # control while len(self.data) < self.control_size: select.select([sock], [], []) @@ -116,6 +119,6 @@ if player_id != game.local_player.net_id: player = game.players.get(player_id, None) if not player: - player = vessel.KeyboardControl.new_remote_player(state['race'], state['vessel_class']) + player = ai.AIVessel.load(config_file=state['config_file'], ai='RemoteAI') game.players[player_id] = player player.set_state(state) Modified: vessel.py =================================================================== --- vessel.py 2007-09-24 16:54:03 UTC (rev 263) +++ vessel.py 2007-09-25 21:39:01 UTC (rev 264) @@ -79,13 +79,7 @@ player.control = KeyboardControl() return player - @classmethod - def new_remote_player(cls, *args, **kw): - player = cls.new_player(*args, **kw) - player.setup_collision(body.foe, body.nothing) - return player - class VesselConfigError(Exception): """Vessel configuration file error""" @@ -152,7 +146,7 @@ def __init__(self, vessel_name=None, vessel_class='', vessel_type='', description='', image_name=None, hull_mass=1, hull_length=1, crew=0, max_speed=0, max_energy=0, - standoff_distance=0, race=None): + standoff_distance=0, race=None, config_file=None): if image_name is None and vessel_class: image_name = vessel_class + '.png' if image_name is not None: @@ -161,6 +155,7 @@ self.collision_radius = min(img_rect.size) / 3 else: self.collision_radius = 0 + self.config_file = config_file self.hull_length = float(hull_length) self.radius = self.hull_length self.standoff_distance = float(standoff_distance) @@ -184,33 +179,26 @@ self._repair_time = None self.damage_time = 0 self.race = race + self.state_history = [] def get_state(self): state = {} - state['race'] = self.race - state['vessel_class'] = self.vessel_class + state['config_file'] = self.config_file + state['position'] = self.get_position() + state['velocity'] = self.get_velocity() + state['heading'] = self.get_heading() state['hull_damage'] = self.hull_damage state['damage_time'] = self.damage_time state['energy'] = self.energy state['crew'] = self.crew - state['position'] = self.get_position() - state['velocity'] = self.get_velocity() - state['heading'] = self.get_heading() - state['turn_rate'] = self.turn_rate state['control.weapons'] = self.control.weapons return state def set_state(self, state): - # smooth state changes over by using a moving average - self.hull_damage = (self.hull_damage + state['hull_damage']) / 2 - self.damage_time = (self.damage_time + state['damage_time']) / 2 - self.energy = (self.energy + state['energy']) / 2 - self.crew = (self.crew + state['crew']) / 2 - self.set_position((self.get_position() + state['position']) / 2) - self.set_velocity((self.get_velocity() + state['velocity']) / 2) - self.set_heading(vector.radians( - vector.unit(self.get_heading()) + vector.unit(state['heading']))) - self.turn_rate = (self.turn_rate + state['turn_rate']) / 2 + self.hull_damage = state['hull_damage'] + self.damage_time = state['damage_time'] + self.energy = state['energy'] + self.crew = state['crew'] self.control.weapons = state['control.weapons'] @classmethod @@ -260,7 +248,7 @@ file_name, config = get_config(config_file) params = dict(config.items('general')) params.update(kw) - vessel = cls(**params) + vessel = cls(config_file=config_file, **params) for section in config.sections(): if section == 'general': continue This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-09-26 02:32:54
|
Revision: 266 http://eos-game.svn.sourceforge.net/eos-game/?rev=266&view=rev Author: oberon7 Date: 2007-09-25 19:32:51 -0700 (Tue, 25 Sep 2007) Log Message: ----------- Pseudo-functional multiplayer. Modified Paths: -------------- eos.py game.py net.py Modified: eos.py =================================================================== --- eos.py 2007-09-26 00:43:40 UTC (rev 265) +++ eos.py 2007-09-26 02:32:51 UTC (rev 266) @@ -23,6 +23,9 @@ parser.add_option('-p', '--port', dest='port', type='int', default=252525, help='server port [default %default]') + parser.add_option('-l', '--latency', + dest='latency', type='float', default=0.0, + help='artificial latency [default %default secs]') parser.add_option('-r', '--race', dest='race', default=None, help='player race') @@ -46,7 +49,7 @@ game.debug = opts.debug game.init(opts.is_server, opts.is_client, opts.host, opts.port, - opts.fullscreen, opts.race, opts.ship, opts.resolution) + opts.fullscreen, opts.race, opts.ship, opts.resolution, opts.latency) start = pygame.time.get_ticks() try: if opts.do_profile: Modified: game.py =================================================================== --- game.py 2007-09-26 00:43:40 UTC (rev 265) +++ game.py 2007-09-26 02:32:51 UTC (rev 266) @@ -10,6 +10,7 @@ from time import sleep import platform import media +import net import ode import pygame from pygame.locals import * @@ -37,15 +38,16 @@ clock = None time = 0 # milliseconds since game began frame_no = 0 # current frame number -players = None +server = None +players = {} local_player = None camera = None target = None windowed = False debug = False -def init(is_server=False, is_client=False, host='127.0.0.1', port='11211', - fullscreen=False, race=None, ship=None, resolution=None): +def init(is_server=False, is_client=False, host='127.0.0.1', port=252525, + fullscreen=False, race=None, ship=None, resolution=None, latency=None): import body import selection from sprite import RenderedGroup @@ -57,7 +59,7 @@ from map import Map from camera import Camera global universe, collision_space, screen, screen_rect, background, map - global sprites, fps, avg_fps, clock, time, players, local_player, camera, ai + global sprites, fps, avg_fps, clock, time, local_player, camera, ai global target, windowed, new_sprites, messenger, send_msg, ui_sprites windowed = not fullscreen @@ -176,12 +178,11 @@ ship_choice.kill() time = 0 - # Establish player(s) - players = [vessel.KeyboardControl.new_player(race, ship)] - local_player = players[0] + # Establish player + local_player = vessel.KeyboardControl.new_player(race, ship) camera = Camera(local_player) target = selection.Target(local_player) - + import panel global base_panel base_panel = panel.BasePanel() @@ -193,6 +194,18 @@ # Kickstart the soundtrack media.Soundtrack(local_player.race).start() + # Establish multiplayer + if is_server: + net.Server.start(host, port) + if is_server or is_client: + global server + server = net.Client(host, port) + if latency: + server.latency = latency + server.start() + else: + players['solo'] = local_player + def exit(): sys.exit() @@ -256,6 +269,7 @@ #pygame.display.update(dirty) pygame.display.flip() universe.quickStep(this_frame_time / 1000.0) + if server: server.step() last_time = time time += clock.tick() if windowed and time - last_time < 33: @@ -265,65 +279,67 @@ fps = clock.get_fps() or fps if debug and frame_no % 30 == 0: print body.body_count, len(sprites), len(media._scaled_image_cache), fps - if time > next_wave: - target = random.choice(players) - target_race = target.race - wave_race = random.choice([r for r in ['naree', 'rone', 'sc'] - if r != target_race]) - if not target.alive(): - target = random.choice(map.planets) - position = vector.unit(random.random() * vector.fullcircle) * 1000 - warship = random.random() * frame_no > 800 - if warship and len(enemies) < max_fleet_size: - if wave_race == 'rone': - ai = AIVessel.load('vessels/rone/draken') - elif wave_race == 'naree': - ai = AIVessel.load('vessels/naree/lotus') - elif wave_race == 'sc': - ai = AIVessel.load('vessels/sc/pegasus') - ai.set_position(target.position + position) - enemies.add(ai) - else: - warship = False - barrier = 300 - friendly = warship - while ((friendly or random.random() * frame_no > barrier) - and len(friends) < max_fleet_size): - if random.random() * frame_no < barrier * 6: - if target_race == 'rone': - ship = 'vessels/rone/drach' - elif target_race == 'naree': - ship = 'vessels/naree/cress' - elif target_race == 'sc': - ship = 'vessels/sc/striker' + if not server: + # FIXME: multiplayer doesn't work with non-players yet + if time > next_wave: + target = random.choice(players.values()) + target_race = target.race + wave_race = random.choice([r for r in ['naree', 'rone', 'sc'] + if r != target_race]) + if not target.alive(): + target = random.choice(map.planets) + position = vector.unit(random.random() * vector.fullcircle) * 1000 + warship = random.random() * frame_no > 800 + if warship and len(enemies) < max_fleet_size: + if wave_race == 'rone': + ai = AIVessel.load('vessels/rone/draken') + elif wave_race == 'naree': + ai = AIVessel.load('vessels/naree/lotus') + elif wave_race == 'sc': + ai = AIVessel.load('vessels/sc/pegasus') + ai.set_position(target.position + position) + enemies.add(ai) else: - if target_race == 'rone': - ship = 'vessels/rone/draken' - elif target_race == 'naree': - ship = 'vessels/naree/lotus' - elif target_race == 'sc': - ship = 'vessels/sc/pegasus' - friend = AIVessel.load(ship, mothership=target, category=body.friend) - friend.set_position(target.position - position) - friends.add(friend) - barrier *= 2 - friendly = False - barrier = 300 + max(50 * len(enemies) - 50 * len(friends), -250) - while len(enemies) < max_fleet_size: - if warship and random.random() * frame_no < barrier: - break - if wave_race == 'rone': - ai = AIVessel.load('vessels/rone/drach') - elif wave_race == 'naree': - ai = AIVessel.load('vessels/naree/cress') - elif wave_race == 'sc': - ai = AIVessel.load('vessels/sc/striker') - ai.set_position(target.position + position) - enemies.add(ai) - warship = True - barrier *= 2 - next_wave = time + ai_interval * 1000 - ai_interval = max(ai_interval * .9, 5) - elif not enemies: - next_wave = min(time + 5000, next_wave) + warship = False + barrier = 300 + friendly = warship + while ((friendly or random.random() * frame_no > barrier) + and len(friends) < max_fleet_size): + if random.random() * frame_no < barrier * 6: + if target_race == 'rone': + ship = 'vessels/rone/drach' + elif target_race == 'naree': + ship = 'vessels/naree/cress' + elif target_race == 'sc': + ship = 'vessels/sc/striker' + else: + if target_race == 'rone': + ship = 'vessels/rone/draken' + elif target_race == 'naree': + ship = 'vessels/naree/lotus' + elif target_race == 'sc': + ship = 'vessels/sc/pegasus' + friend = AIVessel.load(ship, mothership=target, category=body.friend) + friend.set_position(target.position - position) + friends.add(friend) + barrier *= 2 + friendly = False + barrier = 300 + max(50 * len(enemies) - 50 * len(friends), -250) + while len(enemies) < max_fleet_size: + if warship and random.random() * frame_no < barrier: + break + if wave_race == 'rone': + ai = AIVessel.load('vessels/rone/drach') + elif wave_race == 'naree': + ai = AIVessel.load('vessels/naree/cress') + elif wave_race == 'sc': + ai = AIVessel.load('vessels/sc/striker') + ai.set_position(target.position + position) + enemies.add(ai) + warship = True + barrier *= 2 + next_wave = time + ai_interval * 1000 + ai_interval = max(ai_interval * .9, 5) + elif not enemies: + next_wave = min(time + 5000, next_wave) frame_no += 1 Modified: net.py =================================================================== --- net.py 2007-09-26 00:43:40 UTC (rev 265) +++ net.py 2007-09-26 02:32:51 UTC (rev 266) @@ -50,17 +50,17 @@ class Server(SocketServer.BaseRequestHandler, SocketCommunicator): - throttle = 0.01 + throttle = 0.1 player_id_counter = 0 player_states = {} def __init__(self, *args, **kwargs): + self.last_call = 0 SocketCommunicator.__init__(self) SocketServer.BaseRequestHandler.__init__(self, *args, **kwargs) def handle(self): while True: - time.sleep(Server.throttle) payload = self.receive(self.request) if payload[0] == 'player.connect': @@ -73,7 +73,12 @@ self.player_states[payload[1]] = time.time(), payload[2] elif payload[0] == 'player.get_states': + now = time.time() + time_delta = now - self.last_call + if time_delta < Server.throttle: + time.sleep(Server.throttle - time_delta) self.send(self.request, self.player_states) + self.last_call = time.time() @classmethod def start(cls, host, port): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-09-27 18:14:34
|
Revision: 268 http://eos-game.svn.sourceforge.net/eos-game/?rev=268&view=rev Author: oberon7 Date: 2007-09-27 11:14:30 -0700 (Thu, 27 Sep 2007) Log Message: ----------- Multiplayer networking reworked to imitate the Quake3 model. All network communication is over UDP and expected to be unreliable. Modified Paths: -------------- game.py net.py vessel.py Modified: game.py =================================================================== --- game.py 2007-09-26 05:29:51 UTC (rev 267) +++ game.py 2007-09-27 18:14:30 UTC (rev 268) @@ -39,6 +39,7 @@ time = 0 # milliseconds since game began frame_no = 0 # current frame number server = None +client = None players = {} local_player = None camera = None @@ -180,6 +181,7 @@ # Establish player local_player = vessel.KeyboardControl.new_player(race, ship) + players[local_player.net_id] = local_player camera = Camera(local_player) target = selection.Target(local_player) @@ -196,15 +198,11 @@ # Establish multiplayer if is_server: - net.Server.start(host, port) - if is_server or is_client: global server - server = net.Client(host, port) - if latency: - server.latency = latency - server.start() - else: - players['solo'] = local_player + server = net.Server(host, port) + elif is_client: + global client + client = net.Client(host, port) def exit(): sys.exit() @@ -253,7 +251,10 @@ event_handler = event.Handler(hide_mouse=not windowed) while 1: event_handler.handle_events() - local_player.control.set_keystate(pygame.key.get_pressed()) + keystate = pygame.key.get_pressed() + local_player.control.set_keystate(keystate) + if client: + client.step(keystate) handle_collisions() if new_sprites: # New sprites were added last frame, add them to the general @@ -269,7 +270,8 @@ #pygame.display.update(dirty) pygame.display.flip() universe.quickStep(this_frame_time / 1000.0) - if server: server.step() + if server: + server.step() last_time = time time += clock.tick() if windowed and time - last_time < 33: @@ -279,7 +281,7 @@ fps = clock.get_fps() or fps if debug and frame_no % 30 == 0: print body.body_count, len(sprites), len(media._scaled_image_cache), fps - if not server: + if not server and not client: # FIXME: multiplayer doesn't work with non-players yet if time > next_wave: target = random.choice(players.values()) Modified: net.py =================================================================== --- net.py 2007-09-26 05:29:51 UTC (rev 267) +++ net.py 2007-09-27 18:14:30 UTC (rev 268) @@ -5,132 +5,123 @@ # Multiplayer networking # $Id$ -import ai import game import marshal -import message -import random +import pygame import select import socket import SocketServer import struct import threading -import time +import vessel class SocketCommunicator: control_fmt = '!l' control_size = struct.calcsize(control_fmt) - buffer_size = 1024 + buffer_size = 4096 - def __init__(self, latency=None): - self.data = '' - self.latency = latency + def __init__(self): + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.setblocking(False) + self.received = [] + thread = threading.Thread(target=self._start) + thread.setDaemon(True) + thread.start() - def send(self, sock, *args): - payload = marshal.dumps(args) - sock.sendall(struct.pack(self.control_fmt, len(payload))) - sock.sendall(payload) + def send(self, address, *args): + try: + payload = marshal.dumps(args) + data = struct.pack(self.control_fmt, len(payload)) + payload + self.socket.sendto(data, address) + except socket.error: + pass - def receive(self, sock): - if self.latency: - if random.randint(0, 10) == 0: - time.sleep(self.latency * random.random() * 30) - else: - time.sleep(self.latency * random.random() * 2) - # control - while len(self.data) < self.control_size: - select.select([sock], [], []) - self.data += sock.recv(self.buffer_size) - payload_size, = struct.unpack(self.control_fmt, self.data[:self.control_size]) - self.data = self.data[self.control_size:] - # payload - while len(self.data) < payload_size: - select.select([sock], [], []) - self.data += sock.recv(self.buffer_size) - payload = marshal.loads(self.data[:payload_size]) - self.data = self.data[payload_size:] - return payload + def _start(self): + try: + while True: + try: + select.select([self.socket], [], []) + data, address = self.socket.recvfrom(self.buffer_size) + payload_size, = struct.unpack(self.control_fmt, data[:self.control_size]) + payload = marshal.loads(data[self.control_size:]) + self.received.append((address, payload)) + except (socket.error, struct.error, EOFError, ValueError, TypeError): + pass + finally: + self.socket.close() -class Server(SocketServer.BaseRequestHandler, SocketCommunicator): +class Server(SocketCommunicator): - throttle = 0.1 - player_id_counter = 0 - player_states = {} - - def __init__(self, *args, **kwargs): - self.last_call = 0 + def __init__(self, host, port): SocketCommunicator.__init__(self) - SocketServer.BaseRequestHandler.__init__(self, *args, **kwargs) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.bind((host, port)) + self.server_generation = 0 + self.client_generation = {} + self.addresses = set() - def handle(self): - while True: - payload = self.receive(self.request) + def get_game_state(self): + game_state = {} + for player in game.players.values(): + game_state[player.net_id] = { + 'ship' : player.vessel_class, + 'position' : player.get_position(), + 'velocity' : player.get_velocity(), + 'heading' : player.get_heading(), + } + return game_state - if payload[0] == 'player.connect': - Server.player_id_counter += 1 - self.send(self.request, Server.player_id_counter) - message.send_status(None, message.all_players, - 'Player %d Connected' % Server.player_id_counter) + def set_client_state(self, client_state): + net_id, ship, position, velocity, heading, keystate = client_state + if net_id not in game.players: + game.players[net_id] = vessel.KeyboardControl.new_player(net_id=net_id, ship=ship) + game.players[net_id].set_position(position) + game.players[net_id].set_velocity(velocity) + game.players[net_id].set_heading(heading) + game.players[net_id].control.set_keystate(keystate) - elif payload[0] == 'player.set_state': - self.player_states[payload[1]] = time.time(), payload[2] + def step(self): + for address in self.addresses: + self.send(address, self.server_generation, self.get_game_state()) + while self.received: + address, (server_generation, client_generation, client_state) = self.received.pop() + self.addresses.add(address) + if client_generation > self.client_generation.get(client_state[0]): + self.set_client_state(client_state) + self.client_generation[client_state[0]] = client_generation + self.server_generation += 1 - elif payload[0] == 'player.get_states': - now = time.time() - time_delta = now - self.last_call - if time_delta < Server.throttle: - time.sleep(Server.throttle - time_delta) - self.send(self.request, self.player_states) - self.last_call = time.time() +class Client(SocketCommunicator): - @classmethod - def start(cls, host, port): - server = SocketServer.ThreadingTCPServer((host, port), cls) - thread = threading.Thread(name='Eos Server', target=server.serve_forever) - thread.setDaemon(True) - thread.start() - message.send_status(None, message.all_players, 'Server started') - -class Client(threading.Thread, SocketCommunicator): - def __init__(self, host, port): - threading.Thread.__init__(self) SocketCommunicator.__init__(self) - self.setName('Eos Client') - self.setDaemon(True) + self.server = (host, port) + self.client_generation = 0 + self.server_generation = None - self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.server.connect((host, port)) - self.send(self.server, 'player.connect') - game.local_player.net_id = self.receive(self.server) - game.players[game.local_player.net_id] = game.local_player - message.send_status(None, message.all_players, - 'Connected as Player %d' % game.local_player.net_id) + def get_client_state(self, keystate): + player = game.local_player + net_id = player.net_id + ship = player.vessel_class + position = player.get_position() + velocity = player.get_velocity() + heading = player.get_heading() + return net_id, ship, position, velocity, heading, keystate - self.player_states = {} - self.last_update = {} + def set_game_state(self, game_state): + for net_id, player_state in game_state.items(): + if net_id not in game.players: + game.players[net_id] = vessel.KeyboardControl.new_player(net_id=net_id, ship=player_state['ship']) + game.players[net_id].set_position(player_state['position']) + game.players[net_id].set_velocity(player_state['velocity']) + game.players[net_id].set_heading(player_state['heading']) - def send_state(self): - self.send(self.server, 'player.set_state', - game.local_player.net_id, game.local_player.get_state()) - - def get_states(self): - self.send(self.server, 'player.get_states') - self.player_states.update(self.receive(self.server)[0]) - - def run(self): - while True: - self.send_state() - self.get_states() - - def step(self): - while self.player_states: - player_id, (timestamp, state) = self.player_states.popitem() - if player_id != game.local_player.net_id and timestamp > self.last_update.get(player_id, 0): - player = game.players.get(player_id, None) - if not player: - player = ai.AIVessel.load(config_file=state['config_file'], ai='RemoteAI') - game.players[player_id] = player - player.set_state(timestamp, state) - self.last_update[player_id] = timestamp + def step(self, keystate): + self.send(self.server, self.server_generation, self.client_generation, self.get_client_state(keystate)) + while self.received: + address, (server_generation, game_state) = self.received.pop() + if server_generation > self.server_generation: + self.set_game_state(game_state) + self.server_generation = server_generation + self.client_generation += 1 Modified: vessel.py =================================================================== --- vessel.py 2007-09-26 05:29:51 UTC (rev 267) +++ vessel.py 2007-09-27 18:14:30 UTC (rev 268) @@ -63,18 +63,21 @@ self.target = game.target.selected @classmethod - def new_player(cls, race=None, ship=None): + def new_player(cls, race=None, ship=None, net_id=None): if ship is not None: shipglob = 'vessels/*/%s' % ship elif race is not None: shipglob = 'vessels/%s/*' % race else: shipglob = 'vessels/*/*' + if net_id is None: + net_id = random.randint(0, sys.maxint) ships = glob.glob(shipglob) assert len(ships) > 0, 'Invalid race/ship: %r/%r' % (race, ship) ship = random.choice(ships) race = ship.split('/')[1] player = Vessel.load(ship, race=race) + player.net_id = net_id player.setup_collision(body.friend, body.nothing) player.control = KeyboardControl() return player @@ -181,26 +184,6 @@ self.race = race self.state_history = [] - def get_state(self): - state = {} - state['config_file'] = self.config_file - state['position'] = self.get_position() - state['velocity'] = self.get_velocity() - state['heading'] = self.get_heading() - state['hull_damage'] = self.hull_damage - state['damage_time'] = self.damage_time - state['energy'] = self.energy - state['crew'] = self.crew - state['control.weapons'] = self.control.weapons - return state - - def set_state(self, timestamp, state): - self.hull_damage = state['hull_damage'] - self.damage_time = state['damage_time'] - self.energy = state['energy'] - self.crew = state['crew'] - self.control.weapons = state['control.weapons'] - @classmethod def load(cls, config_file, **kw): """Create a vessel from a config file. config_file is either a readable This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ob...@us...> - 2007-09-27 22:11:24
|
Revision: 269 http://eos-game.svn.sourceforge.net/eos-game/?rev=269&view=rev Author: oberon7 Date: 2007-09-27 15:11:22 -0700 (Thu, 27 Sep 2007) Log Message: ----------- Artificial latency and packet loss. Modified Paths: -------------- eos.py game.py net.py Modified: eos.py =================================================================== --- eos.py 2007-09-27 18:14:30 UTC (rev 268) +++ eos.py 2007-09-27 22:11:22 UTC (rev 269) @@ -23,9 +23,6 @@ parser.add_option('-p', '--port', dest='port', type='int', default=252525, help='server port [default %default]') - parser.add_option('-l', '--latency', - dest='latency', type='float', default=0.0, - help='artificial latency [default %default secs]') parser.add_option('-r', '--race', dest='race', default=None, help='player race') @@ -41,6 +38,12 @@ parser.add_option('-d', '--debug', dest='debug', default=False, action='store_true', help='Drop into the debugger on error') + parser.add_option('--fake-latency', + dest='latency', type='float', default=0.0, + help='artificial latency [default %default secs]') + parser.add_option('--fake-packet-loss', + dest='packet_loss', type='float', default=0.0, + help='artificial packet loss [default %default percent]') parser.add_option('-g', '--resolution', dest='resolution', choices=('low', 'med', 'high', 'max'), help='Graphics quality: low, med, high or max. Default: high') @@ -49,7 +52,8 @@ game.debug = opts.debug game.init(opts.is_server, opts.is_client, opts.host, opts.port, - opts.fullscreen, opts.race, opts.ship, opts.resolution, opts.latency) + opts.fullscreen, opts.race, opts.ship, opts.resolution, + opts.latency, opts.packet_loss) start = pygame.time.get_ticks() try: if opts.do_profile: Modified: game.py =================================================================== --- game.py 2007-09-27 18:14:30 UTC (rev 268) +++ game.py 2007-09-27 22:11:22 UTC (rev 269) @@ -48,7 +48,8 @@ debug = False def init(is_server=False, is_client=False, host='127.0.0.1', port=252525, - fullscreen=False, race=None, ship=None, resolution=None, latency=None): + fullscreen=False, race=None, ship=None, resolution=None, latency=None, + packet_loss=None): import body import selection from sprite import RenderedGroup @@ -199,10 +200,10 @@ # Establish multiplayer if is_server: global server - server = net.Server(host, port) + server = net.Server(host, port, latency, packet_loss) elif is_client: global client - client = net.Client(host, port) + client = net.Client(host, port, latency, packet_loss) def exit(): sys.exit() Modified: net.py =================================================================== --- net.py 2007-09-27 18:14:30 UTC (rev 268) +++ net.py 2007-09-27 22:11:22 UTC (rev 269) @@ -8,11 +8,13 @@ import game import marshal import pygame +import random import select import socket import SocketServer import struct import threading +import time import vessel class SocketCommunicator: @@ -21,10 +23,12 @@ control_size = struct.calcsize(control_fmt) buffer_size = 4096 - def __init__(self): + def __init__(self, latency, packet_loss): self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.setblocking(False) self.received = [] + self.latency = latency + self.packet_loss = packet_loss thread = threading.Thread(target=self._start) thread.setDaemon(True) thread.start() @@ -43,9 +47,16 @@ try: select.select([self.socket], [], []) data, address = self.socket.recvfrom(self.buffer_size) + if random.random() < self.packet_loss: + continue # drop a packet payload_size, = struct.unpack(self.control_fmt, data[:self.control_size]) payload = marshal.loads(data[self.control_size:]) - self.received.append((address, payload)) + if self.latency: + # artificial latency + until = time.time() + random.expovariate(1.0 / self.latency) + else: + until = None + self.received.append((until, address, payload)) except (socket.error, struct.error, EOFError, ValueError, TypeError): pass finally: @@ -53,8 +64,8 @@ class Server(SocketCommunicator): - def __init__(self, host, port): - SocketCommunicator.__init__(self) + def __init__(self, host, port, latency, packet_loss): + SocketCommunicator.__init__(self, latency, packet_loss) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((host, port)) self.server_generation = 0 @@ -84,18 +95,24 @@ def step(self): for address in self.addresses: self.send(address, self.server_generation, self.get_game_state()) + delayed = [] while self.received: - address, (server_generation, client_generation, client_state) = self.received.pop() - self.addresses.add(address) - if client_generation > self.client_generation.get(client_state[0]): - self.set_client_state(client_state) - self.client_generation[client_state[0]] = client_generation + next = self.received.pop() + until, address, (server_generation, client_generation, client_state) = next + if until < time.time(): + self.addresses.add(address) + if client_generation > self.client_generation.get(client_state[0]): + self.set_client_state(client_state) + self.client_generation[client_state[0]] = client_generation + else: + delayed.append(next) + self.received.extend(delayed) self.server_generation += 1 class Client(SocketCommunicator): - def __init__(self, host, port): - SocketCommunicator.__init__(self) + def __init__(self, host, port, latency, packet_loss): + SocketCommunicator.__init__(self, latency, packet_loss) self.server = (host, port) self.client_generation = 0 self.server_generation = None @@ -119,9 +136,15 @@ def step(self, keystate): self.send(self.server, self.server_generation, self.client_generation, self.get_client_state(keystate)) + delayed = [] while self.received: - address, (server_generation, game_state) = self.received.pop() - if server_generation > self.server_generation: - self.set_game_state(game_state) - self.server_generation = server_generation + next = self.received.pop() + until, address, (server_generation, game_state) = next + if until < time.time(): + if server_generation > self.server_generation: + self.set_game_state(game_state) + self.server_generation = server_generation + else: + delayed.append(next) + self.received.extend(delayed) self.client_generation += 1 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-10-07 00:06:03
|
Revision: 280 http://eos-game.svn.sourceforge.net/eos-game/?rev=280&view=rev Author: cduncan Date: 2007-10-06 17:05:55 -0700 (Sat, 06 Oct 2007) Log Message: ----------- - Implement sprite layers for closer control of update/draw order. - Sprites added to a RenderedGroup are not incorporated into the group until the next update call, so that sprites added during update are not prematurely drawn. - to_back() and to_front() group methods to control draw order inside the group (used by the ui layer currently) Modified Paths: -------------- ai.py beam.py body.py camera.py game.py message.py panel.py particle.py projectile.py selection.py sprite.py stars.py staticbody.py station.py vessel.py Modified: ai.py =================================================================== --- ai.py 2007-10-04 08:15:42 UTC (rev 279) +++ ai.py 2007-10-07 00:05:55 UTC (rev 280) @@ -8,12 +8,13 @@ import sys import time import math -from pygame import sprite +from pygame.sprite import Group, GroupSingle from pygame.locals import * import ode import game import body import vector +import sprite from vector import diagonal, halfcircle, rightangle, fullcircle from vessel import Control, Vessel, DirectionalThrusters @@ -33,17 +34,17 @@ # override vessel collision handler with our own self.vessel.collide = self.collide # cache some vessel stats - self.target = sprite.GroupSingle() + self.target = GroupSingle() if target: if hasattr(target, 'sprite'): target = target.sprite if target.alive(): self.target.add(target) - self.mothership = sprite.GroupSingle() + self.mothership = GroupSingle() if mothership: self.mothership.add(mothership) self.steerfunc = self.standoff - self.close_vessels = sprite.Group() + self.close_vessels = Group() self.proximity_radius = self.vessel.collision_radius * 3 self.sensor = sensor self.target_time = 0 @@ -477,6 +478,8 @@ class AIVessel(Vessel): """Vessel under ai control""" + layer = sprite.layers.ai_vessels + def __init__(self, target=None, category=body.foe, mothership=None, sensor=None, ai='BasicAI', **kw): Vessel.__init__(self, **kw) @@ -525,7 +528,7 @@ self.geom.setCollideBits(detect_bits) self.last_sweep = None self._detected = [] - self.closest_vessel = sprite.GroupSingle() + self.closest_vessel = GroupSingle() self._closest_dist = sys.maxint self._closest_type = None @@ -632,7 +635,7 @@ """Track the target vessel, sampling its location every sample_time seconds, keeping at most max_samples. """ - self.target = sprite.GroupSingle(target) + self.target = GroupSingle(target) self.samples = [] self.next_sample = 0 self.max_samples = max_samples Modified: beam.py =================================================================== --- beam.py 2007-10-04 08:15:42 UTC (rev 279) +++ beam.py 2007-10-07 00:05:55 UTC (rev 280) @@ -13,6 +13,7 @@ import vessel import vector import media +import sprite _empty_rect = pygame.rect.Rect(0, 0, 0, 0) @@ -24,7 +25,7 @@ def __init__(self, gunmount, range, damage, efficiency, arc_degrees=0, width=1, color='255,255,255', sound=None, name=None): - pygame.sprite.Sprite.__init__(self, game.new_sprites) + pygame.sprite.Sprite.__init__(self, sprite.layers.effects) self.gunmount = gunmount self.range = float(range) self.length = self.range Modified: body.py =================================================================== --- body.py 2007-10-04 08:15:42 UTC (rev 279) +++ body.py 2007-10-07 00:05:55 UTC (rev 280) @@ -46,6 +46,7 @@ offscreen_img = None # Rotated image to point to offscreen sprite xplode_animation = None + layer = () # Sprite group(s) to add body to rect = None image = None src_image = None @@ -61,7 +62,7 @@ """ global body_count body_count += 1 - pygame.sprite.Sprite.__init__(self, game.new_sprites) + pygame.sprite.Sprite.__init__(self, self.layer) if self.xplode_animation is None: # Cache explosion animation in the class RoundBody.xplode_animation = media.Animation('splode/*.jpg', alpha=180, fade=8) @@ -230,16 +231,14 @@ """Hide a body so that is is no longer considered in ode, updated or drawn""" if self.geom is not None: self.geom.disable() # don't participate in collisions anymore - if self in game.sprites: - game.sprites.remove(self) - if self in game.new_sprites: - game.new_sprites.remove(self) + if self in self.layer: + self.layer.remove(self) def show(self): """Show a body previously hidden""" if self.geom is not None: - self.geom.enable() # don't participate in collisions anymore - game.new_sprites.add(self) + self.geom.enable() # Participate in collisions now + self.layer.add(self) def disable(self): """Physically disable a body""" Modified: camera.py =================================================================== --- camera.py 2007-10-04 08:15:42 UTC (rev 279) +++ camera.py 2007-10-07 00:05:55 UTC (rev 280) @@ -54,7 +54,7 @@ def acquire_target(self): # Select a new target - for sprite in game.sprites.sprites(): + for sprite in sprite.layers.ai_vessels.sprites(): if (sprite.alive() and isinstance(sprite, vessel.Vessel) and sprite.vessel_type not in ('missile', 'station')): self.follow(sprite) Modified: game.py =================================================================== --- game.py 2007-10-04 08:15:42 UTC (rev 279) +++ game.py 2007-10-07 00:05:55 UTC (rev 280) @@ -19,6 +19,7 @@ import vessel import message import particle +import sprite fps = 40 # Framerate, averaged over time ai_interval = 70 # Release AI every N seconds @@ -29,12 +30,6 @@ screen_rect = None # screen rectangle background = None # background surface -# Sprite groups -sprites = None # All sprites -new_sprites = None # sprites added this frame to be drawn next frame -ui_sprites = None # User interface sprites -map = None - # Other globally accessible things clock = None time = 0 # milliseconds since game began @@ -62,8 +57,8 @@ from map import Map from camera import Camera global universe, collision_space, screen, screen_rect, background, map - global sprites, fps, avg_fps, clock, time, local_player, camera, ai - global target, windowed, new_sprites, messenger, send_msg, ui_sprites + global fps, avg_fps, clock, time, local_player, camera, ai + global target, windowed, messenger, send_msg windowed = not fullscreen # Initialize pygame and setup main screen @@ -115,10 +110,7 @@ collision_space = ode.HashSpace() # Create game elements - clock = pygame.time.Clock() - sprites = RenderedGroup() - new_sprites = RenderedGroup() - ui_sprites = RenderedGroup() + clock = pygame.time.Clock() StarField(screen.get_rect()) map = Map('sol') @@ -249,8 +241,8 @@ import event import panel global universe, screen, background, debug - global sprites, new_sprites, fps, avg_fps, clock, time, frame_no - global ai_interval, windowed, camera, map, ui_sprites + global fps, avg_fps, clock, time, frame_no + global ai_interval, windowed, camera, map friends = pygame.sprite.Group() enemies = pygame.sprite.Group() next_wave = 0 @@ -263,17 +255,10 @@ if client: client.step(keystate) handle_collisions() - if new_sprites: - # New sprites were added last frame, add them to the general - # sprites group and draw them from here on out - sprites.add(new_sprites) - new_sprites.empty() screen.fill((0, 0, 0)) - ui_sprites.update() - sprites.update() + sprite.layers.update() camera.update() - dirty = sprites.draw(screen) - ui_sprites.draw(screen) + sprite.layers.draw(screen) pygame.display.flip() universe.quickStep(this_frame_time / 1000.0) if server: @@ -286,7 +271,9 @@ this_frame_time = min(time - last_time, last_frame_time * 3) fps = clock.get_fps() or fps if debug and frame_no % 30 == 0: - print body.body_count, len(sprites), len(media._scaled_image_cache), fps + print body.body_count, len(sprite.layers), len(media._scaled_image_cache), fps + + ## Temporary wave-based game play ## if not server and not client: # FIXME: multiplayer doesn't work with non-players yet if time > next_wave: Modified: message.py =================================================================== --- message.py 2007-10-04 08:15:42 UTC (rev 279) +++ message.py 2007-10-07 00:05:55 UTC (rev 280) @@ -7,6 +7,7 @@ import pygame import game +import sprite # Message priorities panic = 0 @@ -135,7 +136,7 @@ message_ttl = 5000 def __init__(self, messenger, player): - pygame.sprite.Sprite.__init__(self, game.ui_sprites) + pygame.sprite.Sprite.__init__(self, sprite.layers.ui) self.messenger = messenger self.player = player self.message = None Modified: panel.py =================================================================== --- panel.py 2007-10-04 08:15:42 UTC (rev 279) +++ panel.py 2007-10-07 00:05:55 UTC (rev 280) @@ -15,6 +15,7 @@ import event import vessel import widget +import sprite class Panel(pygame.sprite.Sprite, event.Handler): """User interface panel. Panels contain controls. Enabled panels receive @@ -28,10 +29,10 @@ def __init__(self, enabled=True, *controls): """Create a panel and add it to the panel handler""" - pygame.sprite.Sprite.__init__(self) + pygame.sprite.Sprite.__init__(self, sprite.layers.ui) self.image = self.create_image() self.enabled = enabled - self.controls = sprite.RenderedGroup(controls) + self.controls = pygame.sprite.Group(controls) # Map of hotkey => control for binding key presses to controls self.control_keymap = dict( (control.hotkey, control) for control in controls @@ -39,6 +40,7 @@ self.clicked_control = None self.slide_end_frame = None handler.add_panel(self) + sprite.layers.ui.to_back(self) def create_image(self): """Return a panel image that is a background for the panel controls. @@ -243,7 +245,6 @@ return False - class MiniMap(BottomPanel): """Tactical mini-map display""" @@ -262,7 +263,6 @@ BottomPanel.__init__(self, width, height, align_right=True) self.tab = PanelTab(self, align_right=True) self.controls.add(self.tab) - game.ui_sprites.add(self) def create_image(self): image = pygame.Surface(self.rect.size, pygame.SRCALPHA, 32) @@ -308,7 +308,6 @@ self.map_mask.fill((0, 0, 0, 120), point_rect.inflate(4, 4)) def draw(self, surface): - self.tab.draw(surface) surface.blit(self.image, self.rect) surface.blit(self.map_mask, self.rect) surface.blit(self.map_points, self.rect) @@ -340,10 +339,9 @@ self.font_name, int(self.planet_font_size * game.camera.base_scale)) self.info_font = pygame.font.Font( self.font_name, int(self.info_font_size * game.camera.base_scale)) - self.buttons = sprite.RenderedGroup() + self.buttons = pygame.sprite.Group() self.set_planet(planet) self.hotkey = self.base_keys.pop(0) - game.ui_sprites.add(self) def create_image(self): image = pygame.Surface(self.rect.size, pygame.SRCALPHA, 32) @@ -438,7 +436,6 @@ self.controls.add(self.tab) def draw(self, surface): - self.tab.draw(surface) surface.blit(self.image, self.rect) if self.info_image is not None: self.info_rect.centery = self.rect.centery @@ -467,6 +464,7 @@ else: rect = self.image.get_rect(right=game.screen_rect.right) widget.Button.__init__(self, rect, self.panel.show_or_hide) + sprite.layers.ui.add(self) def render(self): """Create image for panel tab""" Modified: particle.py =================================================================== --- particle.py 2007-10-04 08:15:42 UTC (rev 279) +++ particle.py 2007-10-07 00:05:55 UTC (rev 280) @@ -11,6 +11,7 @@ import game import body import vector +import sprite from vector import halfcircle max_particles = 500 # Max particles on-screen @@ -23,7 +24,7 @@ def __init__(self, position, velocity, accel_rate, color, growth_rate, time_to_live, opacity=255): - pygame.sprite.Sprite.__init__(self, game.new_sprites) + pygame.sprite.Sprite.__init__(self, sprite.layers.effects) self.position = position self.velocity = velocity / game.fps self.accel_rate = float(accel_rate) / game.fps + 1 @@ -92,7 +93,7 @@ def __init__(self, body, accel_rate, color, growth_rate, time_to_live, opacity, emit_rate, velocity_error=0.0, initial_size=0): - pygame.sprite.Sprite.__init__(self, game.new_sprites) + pygame.sprite.Sprite.__init__(self, sprite.layers.effects) self.body = body self.velocity_error = velocity_error self.accel_rate = accel_rate Modified: projectile.py =================================================================== --- projectile.py 2007-10-04 08:15:42 UTC (rev 279) +++ projectile.py 2007-10-07 00:05:55 UTC (rev 280) @@ -19,6 +19,7 @@ import media import ai import particle +import sprite class FullereneCannon(vessel.Weapon): @@ -91,6 +92,7 @@ particles = 4 rpm = 80 exploding = False + layer = sprite.layers.effects def __init__(self, charge, gunmount): self.mass = charge @@ -321,6 +323,8 @@ class Shot(RoundBody): """Generic projectile shot""" + layer = sprite.layers.effects + def __init__(self, shooter, image, damage, velocity, position=None, range=None, max_ttl=None, fade_range=None, damage_loss=0, radius=1, mass=0.1): """Generic projectile shot Modified: selection.py =================================================================== --- selection.py 2007-10-04 08:15:42 UTC (rev 279) +++ selection.py 2007-10-07 00:05:55 UTC (rev 280) @@ -10,6 +10,7 @@ import game import pygame import media +import sprite class Target(pygame.sprite.Sprite): @@ -20,7 +21,7 @@ sensor = None def __init__(self, source_vessel, range=2000): - pygame.sprite.Sprite.__init__(self, game.new_sprites) + pygame.sprite.Sprite.__init__(self, sprite.layers.ui) self.selected = pygame.sprite.GroupSingle() self._select_timeout = sys.maxint self._sensor_off_frame = None Modified: sprite.py =================================================================== --- sprite.py 2007-10-04 08:15:42 UTC (rev 279) +++ sprite.py 2007-10-07 00:05:55 UTC (rev 280) @@ -6,13 +6,39 @@ # $Id$ import pygame +from pygame.sprite import OrderedUpdates -class RenderedGroup(pygame.sprite.OrderedUpdates): +class RenderedGroup(OrderedUpdates): """A modified version of RenderedUpdates that can render sprites that do not have an image. If the sprite has no 'image' attribute then it's draw() method is called passing it the surface. The draw() method returns the - rect affected + rect affected. + + Sprites added to a RenderedGroup are not drawn until the next full frame, + thus avoiding bugs where sprites are added and drawn immediately without + being appropriately updated first. """ + def __init__(self, *sprites): + OrderedUpdates.__init__(self, *sprites) + # _activesprites is the list of sprites to draw this frame + # note this is *never* mutated in place, since it may + # need to be changed while being iterated. + self._activesprites = [] + self._have_new = bool(sprites) + + def add_internal(self, sprite): + OrderedUpdates.add_internal(self, sprite) + self._have_new = True + + def remove_internal(self, sprite): + OrderedUpdates.remove_internal(self, sprite) + if not self._have_new or sprite in self._activesprites: + self._activesprites = list(self._activesprites) + self._activesprites.remove(sprite) + + def sprites(self): + return self._activesprites + def draw(self, surface): spritedict = self.spritedict surface_blit = surface.blit @@ -36,3 +62,76 @@ spritedict[s] = newrect return dirty + def update(self, *args): + """update(*args) + call update for all member sprites + + calls the update method for all sprites in the group. + Passes all arguments on to the Sprite update function.""" + if self._have_new: + # Incorporate new sprites + #if self is layers.effects: + # print 'Incorporate!', set(self._spritelist) - set(self._activesprites) + self._activesprites = list(self._spritelist) + self._have_new = False + for s in self.sprites(): + s.update(*args) + + def to_front(self, sprite): + """Make the designated sprite the top-most sprite in the group + (last to render). sprite must already be in the group + """ + self._spritelist.remove(sprite) + self._spritelist.append(sprite) + if not self._have_new or sprite in self._activesprites: + self._activesprites = list(self._activesprites) + self._activesprites.remove(sprite) + self._activesprites.append(sprite) + + def to_back(self, sprite): + """Make the designated sprite the bottom-most sprite in the group + (first to render). sprite must already be in the group + """ + self._spritelist.remove(sprite) + self._spritelist.insert(0, sprite) + if not self._have_new or sprite in self._activesprites: + self._activesprites = list(self._activesprites) + self._activesprites.remove(sprite) + self._activesprites.insert(0, sprite) + + +class SpriteLayers: + """Sprite layer manager""" + + def __init__(self): + self.background = RenderedGroup() # Starfield, planets, etc. + self.ai_vessels = RenderedGroup() # AI controlled vessels + self.effects = RenderedGroup() # Weapons fire, explosions, goodies + self.human_vessels = RenderedGroup() # Human controlled vessels + self.ui = RenderedGroup() # User interface + + def update(self): + """Update all sprite layers""" + self.ui.update() + self.human_vessels.update() + self.ai_vessels.update() + self.effects.update() + self.background.update() + + def draw(self, surface): + """Draw all sprite layers""" + self.background.draw(surface) + self.ai_vessels.draw(surface) + self.effects.draw(surface) + self.human_vessels.draw(surface) + self.ui.draw(surface) + + def __len__(self): + return ( + len(self.background) + + len(self.ai_vessels) + + len(self.effects) + + len(self.human_vessels) + + len(self.ui)) + +layers = SpriteLayers() # Singleton Modified: stars.py =================================================================== --- stars.py 2007-10-04 08:15:42 UTC (rev 279) +++ stars.py 2007-10-07 00:05:55 UTC (rev 280) @@ -14,6 +14,7 @@ import media import subpixel import camera +import sprite class StarField(pygame.sprite.Sprite): @@ -26,7 +27,7 @@ parallax_factor = 0.95 def __init__(self, rect): - pygame.sprite.Sprite.__init__(self, game.sprites) + pygame.sprite.Sprite.__init__(self, sprite.layers.background) self.rect = rect colors = [ (200, 200, 255), Modified: staticbody.py =================================================================== --- staticbody.py 2007-10-04 08:15:42 UTC (rev 279) +++ staticbody.py 2007-10-07 00:05:55 UTC (rev 280) @@ -14,11 +14,13 @@ from media import RotatedImage import random import vector +import sprite class Planet(RoundBody): mass = 1000000 base = None # Established planetary base + layer = sprite.layers.background def __init__(self, name, image, x=0, y=0, resources=0): RoundBody.__init__(self, (int(x), int(y))) Modified: station.py =================================================================== --- station.py 2007-10-04 08:15:42 UTC (rev 279) +++ station.py 2007-10-07 00:05:55 UTC (rev 280) @@ -111,7 +111,7 @@ station_spacing = 0 # minimum gap between stations def __init__(self, image_name, planet): - pygame.sprite.Sprite.__init__(self, game.new_sprites) + pygame.sprite.Sprite.__init__(self) self.image_src = media.RotatedImage(image_name) self.planet = planet self.distance = planet.radius + Station.station_altitude @@ -163,7 +163,7 @@ _src_image = None def __init__(self, source, target): - pygame.sprite.Sprite.__init__(self, game.new_sprites) + pygame.sprite.Sprite.__init__(self) self.source = source self.target = target if self._src_image is None: Modified: vessel.py =================================================================== --- vessel.py 2007-10-04 08:15:42 UTC (rev 279) +++ vessel.py 2007-10-07 00:05:55 UTC (rev 280) @@ -20,6 +20,7 @@ from vector import fullcircle, halfcircle, rightangle, diagonal import particle import message +import sprite max_weapons = 5 _empty_rect = pygame.rect.Rect(0, 0, 0, 0) @@ -72,6 +73,8 @@ ship = random.choice(ships) race = ship.split('/')[1] player = Vessel.load(ship, race=race) + player.layer = sprite.layers.human_vessels + player.layer.add(player) player.net_id = net_id player.setup_collision(body.friend, body.nothing) player.control = KeyboardControl() @@ -669,7 +672,7 @@ timeout = 1500 # millis shields stay active def __init__(self, vessel, max_level, regeneration): - pygame.sprite.Sprite.__init__(self, game.new_sprites) + pygame.sprite.Sprite.__init__(self, vessel.groups()) self.vessel = vessel self.max_level = self.level = float(max_level) self.regeneration = self.max_regeneration = float(regeneration) @@ -863,7 +866,7 @@ mass_factor = 0.005 # Mass per durability unit per ship length unit def __init__(self, vessel, durability, dissipation=0, image=None, regeneration=0): - pygame.sprite.Sprite.__init__(self, game.new_sprites) + pygame.sprite.Sprite.__init__(self, vessel.groups()) if image is not None: self.armor_img = RotatedImage(image, enhanced=False) else: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |