From: <cd...@us...> - 2007-01-29 18:00:58
|
Revision: 66 http://eos-game.svn.sourceforge.net/eos-game/?rev=66&view=rev Author: cduncan Date: 2007-01-29 10:00:38 -0800 (Mon, 29 Jan 2007) Log Message: ----------- - Refactor ai into a Control class - Use vessel for player and ai ships. - Tweak config/system logic for existing ships after test flights - Systems now become attributes of the vessels they are installed on - Integrate weapons as systems - Add naree gnat, standalone for now, eventually they will be a lotus weapon Modified Paths: -------------- ai.py game.py ideas/races.txt projectile.py vessel.py vessels/naree/cress vessels/naree/gnat vessels/naree/lotus Added Paths: ----------- data/cress.png data/lotus.png Removed Paths: ------------- data/naree-fighter.png data/naree-medium.png player.py Modified: ai.py =================================================================== --- ai.py 2007-01-28 08:22:04 UTC (rev 65) +++ ai.py 2007-01-29 18:00:38 UTC (rev 66) @@ -11,92 +11,81 @@ from pygame.locals import * import game import body -from player import Player -from media import RotatedImage from vector import Vector2D, diagonal, halfcircle, rightangle, fullcircle +from vessel import Control, Vessel, DirectionalThrusters -class AI(Player): - - category = body.foe - collides_with = body.friend | body.foe - max_speed = 1000 - - mass = 1.5 - radius = 50 - - # Damage - max_shield = 300 - - # Movement - fw_thrust = 500 - maneuver_thrust = fw_thrust * 0.75 - speed_boost = 1.6 - max_speed = 1000 - max_turn_rate = math.radians(4) - turn_accel = math.radians(0.25) - - def __init__(self, target): - Player.__init__(self) - self.ship_img = RotatedImage('naree-fighter.png') +class AIControl(Control): + + def __init__(self, vessel, target): + self.vessel = vessel + # override vessel collision handler with our own + self.vessel.collide = self.collide + # cache some vessel stats self.target = target self.close_vessels = [] - self.steerfunc = random.choice([self.seek, self.flee, self.pursue, self.evade]) - + self.steerfunc = random.choice([self.seek, self.pursue]) + def seek_position(self, target_position): """Return desired velocity towards a fixed position >>> game.init() - >>> ai = AI(None) - >>> ai.max_speed = 10 - >>> ai.position = Vector2D(100, 0) + >>> ai = AIControl(Vessel(), None) + >>> ai.vessel.max_speed = 10 + >>> ai.vessel.position = Vector2D(100, 0) >>> vel = ai.seek_position(Vector2D(-100, 0)) >>> vel.x -10.0 >>> vel.y 0.0 """ - return (target_position - self.position).clamp(self.max_speed) + return (target_position - self.vessel.position).clamp(self.vessel.max_speed) def seek(self, standoff=200): """Return desired velocity towards our target >>> game.init() - >>> ai = AI(AI(None)) - >>> ai.max_speed = 33.0 - >>> ai.position = Vector2D(0, 20) - >>> ai.target.position = Vector2D(0, 40) - >>> vel = ai.seek() + >>> ai = AIControl(Vessel(), Vessel()) + >>> ai.vessel.max_speed = 33.0 + >>> ai.vessel.position = Vector2D(0, 0) + >>> ai.target.position = Vector2D(0, 400) + >>> head, vel = ai.seek() >>> vel.x 0.0 >>> vel.y 33.0 + >>> head == rightangle + True """ + velocity = self.vessel.velocity + position = self.vessel.position target_position = self.target.position target_velocity = self.target.velocity - approach = (target_position + target_velocity) - (self.position + self.velocity) + approach = (target_position + target_velocity) - (position + velocity) new_heading = approach.radians #(target_position - self.position).radians if not standoff or approach.length > standoff: - if self.velocity.length and approach.length / self.velocity.length < game.avg_fps: - return new_heading, approach.clamp(self.velocity.length) + if velocity.length and approach.length / velocity.length < game.avg_fps: + return new_heading, approach.clamp(velocity.length) else: - return new_heading, approach.clamp(self.max_speed) + return new_heading, approach.clamp(self.vessel.max_speed) else: # Close in, match speed with the target - return new_heading, self.target.velocity.copy() + return new_heading, target_velocity.copy() def flee(self): """Return desired velocity away from our target >>> game.init() - >>> ai = AI(AI(None)) - >>> ai.max_speed = 33.0 - >>> ai.position = Vector2D(0, 20) - >>> ai.target.position = Vector2D(0, 40) - >>> vel = ai.flee() + >>> ai = AIControl(Vessel(), Vessel()) + >>> ai.vessel.max_speed = 33.0 + >>> ai.vessel.position = Vector2D(0, 0) + >>> ai.target.position = Vector2D(0, 400) + >>> head, vel = ai.flee() >>> vel.x -0.0 >>> vel.y -33.0 + >>> head == rightangle + halfcircle + True """ heading, velocity = self.seek() return (heading + halfcircle) % fullcircle, -velocity @@ -107,20 +96,19 @@ when we should intercept them >>> game.init() - >>> ai = AI(None) - >>> ai.max_speed = 100 - >>> ai.position = Vector2D(0, 0) - >>> target = AI(None) - >>> target.position = Vector2D(150, 0) - >>> target.velocity = Vector2D(20, 0) - >>> pos = ai.predict_intercept(target) + >>> ai = AIControl(Vessel(), Vessel()) + >>> ai.vessel.max_speed = 100 + >>> ai.vessel.position = Vector2D(0, 0) + >>> ai.target.position = Vector2D(150, 0) + >>> ai.target.velocity = Vector2D(20, 0) + >>> pos = ai.predict_intercept(ai.target) >>> pos.x 180.0 >>> pos.y 0.0 """ if target.velocity: - T = self.position.distance(target.position) / self.max_speed + T = self.vessel.position.distance(target.position) / self.vessel.max_speed return target.position + (target.velocity * T) else: return target.position @@ -130,19 +118,20 @@ our target to be >>> game.init() - >>> ai = AI(None) - >>> ai.max_speed = 100 - >>> ai.position = Vector2D(0, 0) - >>> ai.target = AI(None) + >>> ai = AIControl(Vessel(), Vessel()) + >>> ai.vessel.max_speed = 100 + >>> ai.vessel.position = Vector2D(0, 0) >>> ai.target.position = Vector2D(150, 0) >>> ai.target.velocity = Vector2D(-200, 0) - >>> vel = ai.pursue() + >>> head, vel = ai.pursue() >>> vel.x -100.0 >>> vel.y 0.0 + >>> head + 0.0 """ - heading = (self.target.position - self.position).radians + heading = (self.target.position - self.vessel.position).radians return heading, self.seek_position(self.predict_intercept(self.target)) def evade(self): @@ -152,21 +141,22 @@ a pursuit or seek afterward. >>> game.init() - >>> ai = AI(None) - >>> ai.max_speed = 42 - >>> ai.position = Vector2D(0, 0) - >>> ai.target = AI(None) + >>> ai = AIControl(Vessel(), Vessel()) + >>> ai.vessel.max_speed = 42 + >>> ai.vessel.position = Vector2D(0, 0) >>> ai.target.position = Vector2D(0, 150) >>> ai.target.velocity = Vector2D(0, -200) - >>> vel = ai.evade() + >>> head, vel = ai.evade() >>> vel.x -0.0 >>> vel.y 42.0 + >>> head == rightangle + True """ heading, velocity = self.pursue() return heading, -velocity - + def avoid_vessels(self): """Return a vector away from other nearby vessels to avoid stacking up """ @@ -176,52 +166,80 @@ for vessel in self.close_vessels: center += vessel.position center /= len(self.close_vessels) - return (self.position - center).normal * self.velocity.length + return (self.vessel.position - center).normal * self.vessel.velocity.length else: return center def steer(self, desired_heading, desired_velocity): - heading_diff = desired_heading - self.heading + heading_diff = desired_heading - self.vessel.heading if heading_diff > halfcircle: heading_diff -= fullcircle elif heading_diff < -halfcircle: heading_diff += fullcircle - keymap = [0] * 1000 - if heading_diff > self.turn * abs(self.turn) / self.turn_accel: - keymap[K_RIGHT] = 1 - elif heading_diff < self.turn * abs(self.turn) / self.turn_accel: - keymap[K_LEFT] = 1 + turn_accel = self.vessel.directional_thrusters.accel + max_turn_rate = self.vessel.directional_thrusters.max_turn_rate + if heading_diff > self.vessel.turning * abs(self.vessel.turning) / turn_accel: + self.turn = 1 + elif heading_diff < self.vessel.turning * abs(self.vessel.turning) / turn_accel: + self.turn = -1 + else: + self.turn = 0 # Only apply thrust if our velocity needs adjustment and we are # not turning at the full rate. The latter prevents "orbiting" # targets and make the ship movement look more "natural" # overshoots tend to correct themselves better this way - if self.velocity != desired_velocity and abs(self.turn) < self.max_turn_rate: - thrust_dir = (desired_velocity - self.velocity).radians - self.heading - keymap[K_UP] = thrust_dir < diagonal or thrust_dir > fullcircle - diagonal - keymap[K_d] = halfcircle > thrust_dir + diagonal > rightangle - keymap[K_s] = halfcircle + rightangle > thrust_dir + diagonal > halfcircle - keymap[K_a] = fullcircle > thrust_dir + diagonal > halfcircle + rightangle - return keymap + if self.vessel.velocity != desired_velocity and abs(self.vessel.turning) < max_turn_rate: + thrust_dir = (desired_velocity - self.vessel.velocity).radians - self.vessel.heading + self.thrust = thrust_dir < diagonal or thrust_dir > fullcircle - diagonal + self.right_maneuver = halfcircle > thrust_dir + diagonal > rightangle + self.bw_maneuver = halfcircle + rightangle > thrust_dir + diagonal > halfcircle + self.left_maneuver = fullcircle > thrust_dir + diagonal > halfcircle + rightangle + else: + self.thrust = False + self.right_maneuver = False + self.left_maneuver = False + self.bw_maneuver = False def update(self): # steering desired_heading, desired_velocity = self.steerfunc() desired_velocity += self.avoid_vessels() - keystate = self.steer(desired_heading, desired_velocity) + self.steer(desired_heading, desired_velocity) self.close_vessels = [] # Reset for next frame - # shooting - keystate[K_SPACE] = 0 - - # emulate being a legitimate player - Player.update(self, keystate) + self.weapons[0] = False def collide(self, other, contacts): - if isinstance(other, Player): + """Detect contact with other vessels to avoid stacking + + >>> game.init() + >>> ai = AIControl(Vessel(), None) + >>> ai.close_vessels + [] + >>> v = Vessel() + >>> ai.collide(v, []) + >>> ai.close_vessels == [v] + True + >>> ai.collide(object(), []) + >>> ai.close_vessels == [v] + True + """ + if isinstance(other, Vessel): # Keep track of other vessels were are stacked on self.close_vessels.append(other) + +class AIVessel(Vessel): + """Vessel under ai control""" + + def __init__(self, target=None, **kw): + self.category = body.foe + self.collides_with = body.everything + Vessel.__init__(self, **kw) + self.control = AIControl(self, target) + + if __name__ == '__main__': """Run tests if executed directly""" import sys, doctest Copied: data/cress.png (from rev 59, data/naree-fighter.png) =================================================================== (Binary files differ) Copied: data/lotus.png (from rev 59, data/naree-medium.png) =================================================================== (Binary files differ) Deleted: data/naree-fighter.png =================================================================== (Binary files differ) Deleted: data/naree-medium.png =================================================================== (Binary files differ) Modified: game.py =================================================================== --- game.py 2007-01-28 08:22:04 UTC (rev 65) +++ game.py 2007-01-29 18:00:38 UTC (rev 66) @@ -31,10 +31,10 @@ def init(fullscreen=False): from sprite import RenderedGroup - from ai import AI - from player import Player from stars import StarField from staticbody import Planet + from vessel import Vessel, KeyboardControl + from ai import AIVessel global universe, collision_space, screen, background, sprites global fps, avg_fps, clock, time, player, camera, ai # Initialize pygame and setup main screen @@ -61,10 +61,13 @@ sprites = RenderedGroup() StarField(screen.get_rect()) Planet('earth-east.png', 0, 0) - camera = player = Player() + camera = player = Vessel.load(random.choice(['vessels/naree/lotus', 'vessels/naree/cress'])) + player.control = KeyboardControl() camera.rect.center = screen.get_rect().center camera.zoom = 1 - ai = [AI(player) for i in xrange(10)] + for i in range(10): + ship = random.choice(['vessels/naree/lotus', 'vessels/naree/cress', 'vessels/naree/gnat']) + AIVessel.load(ship, target=player) def handle_events(): for event in pygame.event.get(): Modified: ideas/races.txt =================================================================== --- ideas/races.txt 2007-01-28 08:22:04 UTC (rev 65) +++ ideas/races.txt 2007-01-29 18:00:38 UTC (rev 66) @@ -15,6 +15,7 @@ - Strongly individualistic, tend toward factions - Interested in justice when it serves the winner - Sexes treated relatively equally +- Can take over enemy ships using commandos (when shields are down) Main Characters --------------- @@ -40,6 +41,7 @@ Heavily invested in the notion of fairness and justice. - Lack distinct sexes, Naree reproductive process unknown, as is their lifespan +- Can take energy from enemy ships without shields Main Characters --------------- @@ -67,6 +69,7 @@ - At war with the Naree, obsessed with their destruction and pay attention to little else. - Military tech centers on large numbers of inexpensive units +- Has ships with cloaking tech Main Characters --------------- Deleted: player.py =================================================================== --- player.py 2007-01-28 08:22:04 UTC (rev 65) +++ player.py 2007-01-29 18:00:38 UTC (rev 66) @@ -1,215 +0,0 @@ -## Eos, Dawn of Light -- A Space Opera -## Copyright (c) 2007 Casey Duncan and contributors -## See LICENSE.txt for licensing details - -# Player state -# $Id$ -# $Date$ - -import random -import math -import pygame -from pygame.locals import * -import game -from projectile import FullereneCannon -from body import RoundBody, friend, nothing -from vector import Vector2D, fullcircle, rightangle -from media import RotatedImage, load_image -from vessel import Controls - -class KeyboardControls(Controls): - - def update(self): - keystate = pygame.key.get_pressed() - self.thrust = keystate[K_UP] - self.turn = keystate[K_RIGHT] - keystate[K_LEFT] - self.fw_maneuver = keystate[K_w] - self.right_maneuver = keystate[K_a] - self.left_maneuver = keystate[K_d] - self.bw_maneuver = keystate[K_s] or keystate[K_DOWN] - self.shield = keystate[R_SHIFT] - self.weapons[0] = keystate[K_SPACE] - -class Player(RoundBody): - - mass = 2 - radius = 50 - category = friend - collides_with = nothing - - # Damage - max_shield = 300 - - # Movement - fw_thrust = 500 - maneuver_thrust = fw_thrust * 0.75 - speed_boost = 1.6 - max_speed = 800 - max_turn_rate = math.radians(3) - turn_accel = math.radians(0.18) - - # Cosmetic - shield_opacity = 110 # 0-255 - shield_flicker = 12 - shield_fadeout = 200 - shield_timeout = 1500 # millis shields stay active - - def __init__(self): - RoundBody.__init__(self, (random.randint(-200,200),random.randint(-200,200))) - self.ship_img = RotatedImage('naree-medium.png') - self.shield_img = RotatedImage('shield.png') - self.shields = self.max_shield - self.shield_time = 0 - self.heading = random.random() * fullcircle - self.turn = 0.0 - self.thrust = Vector2D() - self.weapon = FullereneCannon(self) - self.update_ship_img() - - def draw(self, surface): - dirty = surface.blit(self.image, self.rect) - if self.shield_time >= game.time: - shield_img = self.shield_img.rotated( - math.radians(game.time / 30)) - flicker = random.randint(0, self.shield_flicker) - if self.shield_time - game.time > self.shield_fadeout: - opacity = self.shield_opacity - else: - opacity = self.shield_opacity * ( - self.shield_time - game.time) / self.shield_fadeout - shield_img.set_alpha(max(opacity - flicker, 10)) - shield_rect = shield_img.get_rect(center=self.rect.center) - dirty2 = surface.blit(shield_img, shield_rect) - dirty.union_ip(dirty2) - return dirty - - def update_ship_img(self): - self.image = self.ship_img.rotated(self.heading) - self.rect = self.image.get_rect(center=self.rect.center) - - def update_turn(self, keystate): - """Translate keystrokes into turning actions - - >>> game.init() - >>> p = Player() - >>> goleft = fake_keys(K_LEFT) - >>> goright = fake_keys(K_RIGHT) - - >>> last_heading = p.heading - >>> p.update_turn(goright) - >>> p.turn == p.turn_accel - True - >>> p.heading == (last_heading + p.turn) % fullcircle - True - - >>> for x in xrange(100): p.update_turn(goleft) - >>> p.turn == -p.max_turn_rate - True - """ - turn_dir = keystate[K_RIGHT] - keystate[K_LEFT] - if turn_dir: - self.turn += turn_dir * self.turn_accel - self.turn = min(max(self.turn, -self.max_turn_rate), self.max_turn_rate) - elif self.turn > 0: - self.turn = max(self.turn - self.turn_accel, 0) - elif self.turn: - self.turn = min(self.turn + self.turn_accel, 0) - self.heading += self.turn - self.heading %= fullcircle - self.update_ship_img() - - def update_thrust(self, keystate): - """Translate keystrokes into thrusting actions - - >>> game.init() - >>> p = Player() - >>> up = fake_keys(K_UP) - >>> down = fake_keys(K_DOWN) - >>> nothing = fake_keys() - - >>> p.update_thrust(nothing) - >>> p.thrust.length - 0.0 - - >>> p.update_thrust(up) - >>> p.max_speed == Player.max_speed - True - >>> p.thrust > 0 - True - """ - if keystate[K_UP]: - if keystate[K_w]: - self.max_speed = self.__class__.max_speed * self.speed_boost - else: - self.max_speed = self.__class__.max_speed - self.thrust = Vector2D.unit(self.heading) * self.fw_thrust - else: - self.thrust *= 0 - if keystate[K_w]: - self.thrust += Vector2D.unit(self.heading) * self.maneuver_thrust - if keystate[K_s] or keystate[K_DOWN]: - self.thrust -= Vector2D.unit(self.heading) * self.maneuver_thrust - if keystate[K_a]: - self.thrust += Vector2D.unit(self.heading - rightangle) * self.maneuver_thrust - if keystate[K_d]: - self.thrust += Vector2D.unit(self.heading + rightangle) * self.maneuver_thrust - if self.thrust: - self.push(self.thrust) - if self.velocity.length > self.max_speed: - # If we are overspeed, bleed off a little - # rather abruptly clamp it, otherwise we'd have - # an instantaneous deceleration when we let off boost. - self.velocity *= 0.98 - - def update_shoot(self, keystate): - """Translate keystrokes into shooting actions - - >>> game.init() - >>> p = Player() - >>> p.weapon.charge - 0 - >>> p.update_shoot(fake_keys(K_SPACE)) - >>> p.weapon.charge > 0 - True - >>> p.update_shoot(fake_keys()) # release spacebar - >>> p.weapon.charge - 0 - """ - self.weapon.update(keystate[K_SPACE]) - - def update(self, keystate=None): - RoundBody.update(self) - if keystate is None: - keystate = pygame.key.get_pressed() - - self.update_turn(keystate) - self.update_thrust(keystate) - self.update_shoot(keystate) - if keystate[K_LSHIFT]: - self.shield_time = game.time + self.shield_timeout - - def damage(self, value): - self.shields -= value - if self.shields > 0: - self.shield_flicker = int( - Player.shield_flicker * (self.max_shield / self.shields)) - self.shield_opacity = int( - Player.shield_opacity - - ((Player.shield_opacity / 5) * - (self.max_shield - self.shields) / self.max_shield)) - self.shield_time = game.time + self.shield_timeout - else: - self.kill() - -def fake_keys(*args): - k = [0]*1000 - for i in args: k[i] = 1 - return k - - -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: projectile.py =================================================================== --- projectile.py 2007-01-28 08:22:04 UTC (rev 65) +++ projectile.py 2007-01-29 18:00:38 UTC (rev 66) @@ -13,27 +13,39 @@ import game from body import RoundBody, everything from vector import Vector2D, fullcircle +import vessel -class FullereneCannon: +class FullereneCannon(vessel.System): - charge_rate = 2.5 # charge per second - max_charge = 4.5 - min_charge = 0.25 - max_hold = 4 # Max time full charge holds in seconds + name = 'fullerene pulse cannon' + attr_name = 'fullerene_cannon' + mass = 10 - def __init__(self, gunmount): + def __init__(self, gunmount, charge_rate, min_charge, max_charge, max_charge_time): + """Fullerene pulse cannon, a variable charge energy projectile + + gunmount -- body we are mounted to + charge_rate -- Charge per second + min_charge -- Minimum charge to fire, controls max fire rate + max_charge -- Maximum charge + max_charge_time -- Maximum seconds to hold before auto-fire + """ + self.charge_rate = float(charge_rate) + self.min_charge = float(min_charge) + self.max_charge = float(max_charge) + self.max_charge_time = float(max_charge_time) self.charge_start = None self.charge = 0 - self.charge_per_frame = self.charge_rate / game.avg_fps self.gunmount = gunmount - def update(self, charging): + def update_system(self): + charging = self.gunmount.control.weapons[0] if (charging and (self.charge_start is None - or game.time - self.charge_start < self.max_hold * 1000)): + 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_per_frame, self.max_charge) + self.charge = min(self.charge + self.charge_rate / game.avg_fps, self.max_charge) elif self.charge_start and self.charge >= self.min_charge: Fullerene(self.charge, self.gunmount) self.charge = 0 Modified: vessel.py =================================================================== --- vessel.py 2007-01-28 08:22:04 UTC (rev 65) +++ vessel.py 2007-01-29 18:00:38 UTC (rev 66) @@ -11,13 +11,15 @@ import ConfigParser import game import pygame +from pygame.locals import * from body import RoundBody from vector import Vector2D, fullcircle, halfcircle, rightangle from media import RotatedImage max_weapons = 5 +_empty_rect = pygame.rect.Rect(0, 0, 0, 0) -class Controls: +class Control: """Static vessel control state""" thrust = False @@ -36,6 +38,20 @@ pass +class KeyboardControl(Control): + + def update(self): + keystate = pygame.key.get_pressed() + self.thrust = keystate[K_UP] + self.turn = keystate[K_RIGHT] - keystate[K_LEFT] + self.fw_maneuver = keystate[K_w] + self.left_maneuver = keystate[K_a] + self.right_maneuver = keystate[K_d] + self.bw_maneuver = keystate[K_s] or keystate[K_DOWN] + self.shield = keystate[K_RSHIFT] + self.weapons[0] = keystate[K_SPACE] + + class VesselConfigError(Exception): """Vessel configuration file error""" @@ -67,29 +83,38 @@ system_damage_threshold = 10 # How much damage to disable a system max_speed = 0 - control = Controls() + control = Control() def __init__(self, vessel_name=None, vessel_class='', vessel_type='', description='', - image_name='ship.png', hull_mass=1, hull_length=1, crew=0, max_speed=0): + image_name=None, hull_mass=1, hull_length=1, crew=0, max_speed=0): + if image_name is None: + if vessel_class: + image_name = vessel_class + '.png' + else: + image_name = 'ship.png' + self.vessel_img = RotatedImage(image_name) + img_rect = self.vessel_img.get_rect() + self.radius = max(img_rect.width, img_rect.height) / 2 RoundBody.__init__(self) self.vessel_name = vessel_name self.vessel_class = vessel_class self.vessel_type = vessel_type self.description = description + self.hull_length = float(hull_length) self.hull_mass = float(hull_mass) - self.hull_length = float(hull_length) self.crew = int(crew) self.max_speed = float(max_speed) - self.vessel_img = RotatedImage(image_name) self._sys = [] self._damage_sys = [] self.heading = random.random() * fullcircle self.turning = 0 @classmethod - def load(cls, config_file): + def load(cls, config_file, **kw): """Create a vessel from a config file. config_file is either a readable - file-like object or a file name. + file-like object or a file name. Additional keyword arguments are passed + to the vessel constructor and override values of the same name in the + config file. The config file is in ConfigParser format and contains a [general] section that defines the overall parameters for the vessel. These are @@ -102,7 +127,7 @@ >>> config = ''' ... [general] - ... vessel_class: test + ... vessel_class: ship ... description: A test vessel ... hull_mass: 42 ... hull_length: 3.1415926 @@ -113,7 +138,7 @@ >>> import StringIO >>> v = Vessel.load(StringIO.StringIO(config)) >>> v.vessel_class - 'test' + 'ship' >>> v.description 'A test vessel' >>> v.hull_mass @@ -134,6 +159,7 @@ parser = ConfigParser.SafeConfigParser() parser.readfp(config_file) params = dict(parser.items('general')) + params.update(kw) vessel = cls(**params) for section in parser.sections(): if section == 'general': @@ -171,7 +197,13 @@ system.update_system() if self.velocity.length > self.max_speed: # If we are overspeed, bleed off a little - self.velocity *= 0.98 + overspeed = self.velocity.length - self.max_speed + if overspeed < self.max_speed / 5: + # not much overspeed, just clamp it + self.velocity.clamp_ip(self.max_speed) + else: + # very overspeed, clamp down quick + self.velocity.clamp_ip(self.max_speed + overspeed / 2) self.heading += self.turning self.image = self.vessel_img.rotated(self.heading) self.rect = self.image.get_rect(center=self.rect.center) @@ -180,7 +212,9 @@ """Add a vessel system >>> game.init() - >>> class TestSys: mass = 5 + >>> class TestSys: + ... mass = 5 + ... attr_name = 'test_sys' >>> v = Vessel() >>> v.mass = 1 >>> len(list(v)) @@ -191,13 +225,21 @@ 1 >>> v.mass 6.0 + >>> v.test_sys is s + True """ if self.allow_system(system): self._sys.append(system) self.calc_mass() if hasattr(system, 'damage'): self._damage_sys.append(system) - self._damage_sys.sort(key=lambda s: getattr(s, 'priority', 999)) + self._damage_sys.sort(key=lambda s: getattr(s, 'priority', sys.maxint)) + if getattr(system, 'attr_name', None) is not None: + if not hasattr(self, system.attr_name): + # Add system as a vessel attribute for convenient access + setattr(self, system.attr_name, system) + else: + assert False, 'Vessel already has attribute %s' % system.attr_name def allow_system(self, system): """Return true if the hull can accomodate the system""" @@ -249,6 +291,7 @@ self.mass = sum(s.mass for s in self._sys) + self.hull_mass massobj = self.body.getMass() massobj.adjust(self.mass) + self.body.setMass(massobj) def __iter__(self): return iter(self._sys) @@ -302,6 +345,7 @@ """Vessel system base class""" name = 'base system' + attr_name = None # Vessel attribute mass = 0 priority = sys.maxint enabled = True @@ -347,6 +391,7 @@ """ name = 'energy shield' + attr_name = 'shield' mass = 0 priority = 0 # Shields get hit first @@ -409,7 +454,7 @@ if self.time >= game.time and self.level: image = self.rot_images.rotated( math.radians(game.time / 30)) - flicker = self.flicker + self.flicker * (self.max_level / self.level) + flicker = int(self.flicker + self.flicker * (self.max_level / self.level)) flicker = random.randint(0, flicker) opacity = self.opacity * (self.level / self.max_level) if self.time - game.time <= self.fadeout: @@ -419,7 +464,7 @@ dirty = surface.blit(image, shield_rect) return dirty else: - return (0, 0, 0, 0) + return _empty_rect def damage(self, value): """Damage shields, return damage not absorbed @@ -451,7 +496,7 @@ """Simple directional thrust system""" name = 'directional thrusters' - + attr_name = 'directional_thrusters' mass = 0.5 mass_factor = 50 # Number of tons required to half turn acceleration @@ -473,7 +518,8 @@ @property def accel(self): """Per frame turn acceleration""" - return self.thrust / max(self.mass_factor / self.vessel.mass * 2, 1) / game.avg_fps + thrust_factor = self.thrust / self.vessel.mass * self.vessel.hull_length / self.mass_factor + return (self.thrust - thrust_factor / 2) / game.avg_fps def update_system(self): """Update vessel turning based on control input @@ -508,7 +554,8 @@ class ManeuveringThrusters(System): name = 'maneuvering thrusters' - mass_factor = 0.1 # mass per unit thrust + attr_name = 'maneuvering_thrusters' + mass_factor = 0.0007 # mass per unit thrust def __init__(self, vessel, thrust): self.vessel = vessel @@ -584,14 +631,16 @@ """Basic thrust engine""" name = 'engine' + attr_name = 'engine' base_mass = 1 - mass_factor = 0.05 # mass per unit thrust - speed_boost = 1.6 + mass_factor = 0.0003 # mass per unit thrust + speed_boost = 1.5 def __init__(self, vessel, thrust): self.vessel = vessel self.thrust = float(thrust) self.mass = self.base_mass + self.thrust * self.mass_factor + self.max_speed = self.vessel.max_speed self.enabled = True def disable(self): @@ -625,11 +674,19 @@ if self.vessel.control.thrust and self.enabled: if self.vessel.control.fw_maneuver: # Thrust + forward maneuver == boost - self.vessel.max_speed = self.vessel.__class__.max_speed * self.speed_boost - else: - self.vessel.max_speed = self.vessel.__class__.max_speed + self.vessel.max_speed = self.max_speed * self.speed_boost + elif self.vessel.max_speed > self.max_speed: + # 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) + elif self.vessel.max_speed > self.max_speed: + # 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) +# Imports for vessel config use +import projectile if __name__ == '__main__': """Run tests if executed directly""" Modified: vessels/naree/cress =================================================================== --- vessels/naree/cress 2007-01-28 08:22:04 UTC (rev 65) +++ vessels/naree/cress 2007-01-29 18:00:38 UTC (rev 66) @@ -5,20 +5,20 @@ vessel_type: fighter description: A ubiquitous presence in Naree space, the cress is employed equally in both patrol and interceptor roles. Eschewing cumbersome armor entirely in favor of powerful shield regenerators, it's lightweight nano-molecular hull belies its efficacy in battle. Quantum differential drive provides motive power and unparalleled agility for a craft of its size. Capable of independent supralight travel, the cress excels in long range escort and scouting capacities. Its lepton particle beam armament imparts a lasting impression on those who have felt its sting. hull_mass: 5 -hull_length: 10 +hull_length: 5 crew: 1 -max_speed: 1000 +max_speed: 600 [vessel.Shield] max_level: 100 regeneration: 10 [vessel.DirectionalThrusters] -thrust: 1 -max_turn_rate: 0.07 +thrust: 0.2 +max_turn_rate: 0.08 [vessel.ManeuveringThrusters] -thrust: 200 +thrust: 6000 [vessel.Engine] -thrust: 150 +thrust: 10000 Modified: vessels/naree/gnat =================================================================== --- vessels/naree/gnat 2007-01-28 08:22:04 UTC (rev 65) +++ vessels/naree/gnat 2007-01-29 18:00:38 UTC (rev 66) @@ -5,16 +5,19 @@ vessel_type: missile description: The Naree can ill afford to squander their pilots in delicate stub fighters, nor can they afford to waste resources on crude "fire and forget" weapons. The gnat was designed as an alternative to both, a reuseable weapon effective in both defense and offense. The gnat is a self contained autonomous unmanned craft wielding a laser cannon. Gnats are launched by a system resembling a cross between a figher bay and a torpedo tube that can be carried by medium-sized warships. Launched in groups the gnats can de directed to swarm hostile targets inflicting damage while using their agility and advanced flight control to avoid being destroyed. Once their energy is depleted, they are automatically programmed to return to their host ship to recharge. In defensive mode, gnats act as mobile point-defense weapons, staying near their host and intercepting incoming threats. hull_mass: 1 -hull_length: 3 +hull_length: 2 +max_speed: 700 [vessel.Shield] max_level: 20 regeneration: 0 [vessel.DirectionalThrusters] -thrust: 0.5 +thrust: 0.18 max_turn_rate: 0.1 [vessel.ManeuveringThrusters] -thrust: 1 +thrust: 700 +[vessel.Engine] +thrust: 700 Modified: vessels/naree/lotus =================================================================== --- vessels/naree/lotus 2007-01-28 08:22:04 UTC (rev 65) +++ vessels/naree/lotus 2007-01-29 18:00:38 UTC (rev 66) @@ -7,7 +7,7 @@ hull_mass: 25 hull_length: 20 crew: 3 -max_speed: 800 +max_speed: 520 [vessel.Shield] max_level: 300 @@ -18,11 +18,17 @@ # dissipation: 10 [vessel.DirectionalThrusters] -thrust: 3 -max_turn_rate: 0.07 +thrust: 0.1 +max_turn_rate: 0.055 [vessel.ManeuveringThrusters] -thrust: 350 +thrust: 6000 [vessel.Engine] -thrust: 500 +thrust: 10000 + +[projectile.FullereneCannon] +charge_rate: 2.5 +min_charge: 0.25 +max_charge: 4.5 +max_charge_time: 4 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-01-31 07:01:43
|
Revision: 67 http://eos-game.svn.sourceforge.net/eos-game/?rev=67&view=rev Author: cduncan Date: 2007-01-30 23:01:41 -0800 (Tue, 30 Jan 2007) Log Message: ----------- - Use ode to model body and vessel rotation and directional thrust, Body class gains read/write heading and turn_rate properties bound to ode. - Scale thrust values by 100 so that lower values are used in the vessel configs (e.g. 1000 before is 10 now) - Increase fullerene pulse cannon damage and charge rate. Full charge is hit is enough to damage a cress ship system but not destroy the ship entirely. - Tweak vessel stats after dogfighting a bit. - Make hulls more vulnerable. - Disabled systems on hull damage now works. Some systems fail utterly when disabled (shields) others just get hobbled (engines, directional thrust). This makes multiple disables interesting too. Also makes larger ships take damage in more interesting ways. Modified Paths: -------------- ai.py body.py projectile.py vessel.py vessels/naree/cress vessels/naree/gnat vessels/naree/lotus Modified: ai.py =================================================================== --- ai.py 2007-01-29 18:00:38 UTC (rev 66) +++ ai.py 2007-01-31 07:01:41 UTC (rev 67) @@ -178,10 +178,11 @@ heading_diff += fullcircle turn_accel = self.vessel.directional_thrusters.accel + turn_rate = self.vessel.turn_rate max_turn_rate = self.vessel.directional_thrusters.max_turn_rate - if heading_diff > self.vessel.turning * abs(self.vessel.turning) / turn_accel: + if heading_diff > turn_rate / 3: self.turn = 1 - elif heading_diff < self.vessel.turning * abs(self.vessel.turning) / turn_accel: + elif heading_diff < -turn_rate / 3: self.turn = -1 else: self.turn = 0 @@ -189,7 +190,7 @@ # not turning at the full rate. The latter prevents "orbiting" # targets and make the ship movement look more "natural" # overshoots tend to correct themselves better this way - if self.vessel.velocity != desired_velocity and abs(self.vessel.turning) < max_turn_rate: + if self.vessel.velocity != desired_velocity and abs(turn_rate) < max_turn_rate: thrust_dir = (desired_velocity - self.vessel.velocity).radians - self.vessel.heading self.thrust = thrust_dir < diagonal or thrust_dir > fullcircle - diagonal self.right_maneuver = halfcircle > thrust_dir + diagonal > rightangle Modified: body.py =================================================================== --- body.py 2007-01-29 18:00:38 UTC (rev 66) +++ body.py 2007-01-31 07:01:41 UTC (rev 67) @@ -4,8 +4,8 @@ # Basic massive objects in the game universe # $Id$ -# $Date$ +import math import ode import game import pygame @@ -67,7 +67,31 @@ @property def force(self): return Vector2D(*self.body.getForce()[:2]) + + @property + def torque(self): + return self.body.getTorque()[2] + def _heading_fget(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): + 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) + + def _turn_rate_fget(self): + ignored, ignored, turn_rate = self.body.getAngularVel() + return turn_rate + def _turn_rate_fset(self, turn_rate): + self.body.setAngularVel((0, 0, turn_rate)) + turn_rate = property(fget=_turn_rate_fget, fset=_turn_rate_fset) + def update(self): """Calculate onscreen position and size""" self.position.sync() Modified: projectile.py =================================================================== --- projectile.py 2007-01-29 18:00:38 UTC (rev 66) +++ projectile.py 2007-01-31 07:01:41 UTC (rev 67) @@ -58,7 +58,7 @@ speed = 450 range = 450 - base_damage = 5 + base_damage = 2 particles = 4 rpm = 80 exploding = False @@ -74,7 +74,7 @@ self.charge = charge self.distance = 0 self.distance_per_frame = self.speed / game.avg_fps - self.partsize = max(charge * 1.25, 2) + self.partsize = max(charge * .6, 2) self.partangle = fullcircle/self.particles self.rot_per_frame = self.rpm * fullcircle / (game.avg_fps * 60) self.rot = 0 @@ -94,7 +94,7 @@ def draw(self, surface): partsize = self.partsize * game.camera.zoom - radius = self.charge * game.camera.zoom + radius = self.charge / 2 * game.camera.zoom rects = [self.rect] bg = randint(0, self.colormax) surface.fill((self.colormax, bg, self.colormax-bg), Modified: vessel.py =================================================================== --- vessel.py 2007-01-29 18:00:38 UTC (rev 66) +++ vessel.py 2007-01-31 07:01:41 UTC (rev 67) @@ -78,7 +78,7 @@ """ hull_damage = 0 # Current damage to hull - disable_factor = 20 # disabled if damage exceeds this factor * hull_mass + disable_factor = 5 # disabled if damage exceeds this factor * hull_mass system_damage = 0 # System damage accumulator system_damage_threshold = 10 # How much damage to disable a system max_speed = 0 @@ -107,7 +107,6 @@ self._sys = [] self._damage_sys = [] self.heading = random.random() * fullcircle - self.turning = 0 @classmethod def load(cls, config_file, **kw): @@ -133,7 +132,7 @@ ... hull_length: 3.1415926 ... ... [vessel.Engine] - ... thrust: 55 + ... thrust: 5 ... ''' >>> import StringIO >>> v = Vessel.load(StringIO.StringIO(config)) @@ -151,7 +150,7 @@ >>> s[0].__class__.__name__ 'Engine' >>> s[0].thrust - 55.0 + 500.0 """ if isinstance(config_file, str): config_file = open(config_file, 'rt') @@ -183,10 +182,10 @@ try: params = dict(parser.items(section)) system = sys_constructor(vessel, **params) - except Exception: + except Exception, err: raise VesselConfigError, ( - 'Exception creating system from section [%s] of vessel config file %s.' - % (section, file_name)), sys.exc_info()[-1] + 'Exception creating system from section [%s] of vessel config file %s: %s' + % (section, file_name, err)), sys.exc_info()[-1] vessel.add_system(system) return vessel @@ -204,7 +203,6 @@ else: # very overspeed, clamp down quick self.velocity.clamp_ip(self.max_speed + overspeed / 2) - self.heading += self.turning self.image = self.vessel_img.rotated(self.heading) self.rect = self.image.get_rect(center=self.rect.center) @@ -331,7 +329,8 @@ else: self.system_damage += value while self.system_damage > self.system_damage_threshold: - random.choice(self._sys).disable() + system = random.choice(self._sys) + system.disable() self.system_damage -= self.system_damage_threshold def kill(self): @@ -505,22 +504,18 @@ max_turn_rate is in radians per second """ self.vessel = vessel - self.thrust = float(thrust) + self.thrust = float(thrust) * 1000 self.max_turn_rate = float(max_turn_rate) self.enabled = True def disable(self): + self.thrust /= 2 + self.max_turn_rate /= 2 self.enabled = False def enable(self): self.enabled = True - @property - def accel(self): - """Per frame turn acceleration""" - thrust_factor = self.thrust / self.vessel.mass * self.vessel.hull_length / self.mass_factor - return (self.thrust - thrust_factor / 2) / game.avg_fps - def update_system(self): """Update vessel turning based on control input @@ -530,37 +525,39 @@ >>> v.add_system(dt) >>> v.control.turn = 1 >>> dt.update_system() - >>> v.turning == dt.accel + >>> v.torque == dt.thrust True - >>> v.turning = 0 >>> v.control.turn = -1 >>> dt.update_system() - >>> v.turning == -dt.accel + >>> v.torque == 0 True - >>> for i in range(1000): dt.update_system() - >>> v.turning == -dt.max_turn_rate + >>> dt.update_system() + >>> v.torque == -dt.thrust True """ - if self.vessel.control.turn and self.enabled: - self.vessel.turning += self.vessel.control.turn * self.accel - self.vessel.turning = min(max( - self.vessel.turning, -self.max_turn_rate), self.max_turn_rate) - elif self.vessel.turning > 0: - self.vessel.turning = max(self.vessel.turning - self.accel, 0) - elif self.vessel.turning < 0: - self.vessel.turning = min(self.vessel.turning + self.accel, 0) + turn_rate = self.vessel.turn_rate + if self.vessel.control.turn and abs(turn_rate) < self.max_turn_rate: + self.vessel.body.addTorque((0, 0, self.vessel.control.turn * self.thrust)) + else: + # slow or stop turning with no input + if abs(turn_rate) < 0.25: + self.vessel.turn_rate = 0 + elif turn_rate > 0: + self.vessel.body.addTorque((0,0,-self.thrust)) + elif turn_rate < 0: + self.vessel.body.addTorque((0,0,self.thrust)) class ManeuveringThrusters(System): name = 'maneuvering thrusters' attr_name = 'maneuvering_thrusters' - mass_factor = 0.0007 # mass per unit thrust + mass_factor = 0.07 # mass per unit thrust def __init__(self, vessel, thrust): self.vessel = vessel - self.thrust = float(thrust) - self.mass = self.thrust * self.mass_factor + self.thrust = float(thrust) * 100 + self.mass = float(thrust) * self.mass_factor self.enabled = True def disable(self): @@ -633,17 +630,19 @@ name = 'engine' attr_name = 'engine' base_mass = 1 - mass_factor = 0.0003 # mass per unit thrust + mass_factor = 0.03 # mass per unit thrust speed_boost = 1.5 def __init__(self, vessel, thrust): self.vessel = vessel - self.thrust = float(thrust) - self.mass = self.base_mass + self.thrust * self.mass_factor + self.thrust = float(thrust) * 100 + self.mass = self.base_mass + float(thrust) * self.mass_factor self.max_speed = self.vessel.max_speed self.enabled = True def disable(self): + self.max_speed /= 2 + self.thrust /= 2 self.enabled = False def enable(self): @@ -671,7 +670,7 @@ >>> v.force.radians == v.heading True """ - if self.vessel.control.thrust and self.enabled: + if self.vessel.control.thrust: if self.vessel.control.fw_maneuver: # Thrust + forward maneuver == boost self.vessel.max_speed = self.max_speed * self.speed_boost Modified: vessels/naree/cress =================================================================== --- vessels/naree/cress 2007-01-29 18:00:38 UTC (rev 66) +++ vessels/naree/cress 2007-01-31 07:01:41 UTC (rev 67) @@ -11,14 +11,17 @@ [vessel.Shield] max_level: 100 -regeneration: 10 +regeneration: 18 [vessel.DirectionalThrusters] -thrust: 0.2 -max_turn_rate: 0.08 +thrust: 30 +max_turn_rate: 3 [vessel.ManeuveringThrusters] -thrust: 6000 +thrust: 50 [vessel.Engine] -thrust: 10000 +thrust: 80 + +#[beam.BeamCannon] +#range=250 Modified: vessels/naree/gnat =================================================================== --- vessels/naree/gnat 2007-01-29 18:00:38 UTC (rev 66) +++ vessels/naree/gnat 2007-01-31 07:01:41 UTC (rev 67) @@ -9,15 +9,15 @@ max_speed: 700 [vessel.Shield] -max_level: 20 +max_level: 40 regeneration: 0 [vessel.DirectionalThrusters] -thrust: 0.18 -max_turn_rate: 0.1 +thrust: 1 +max_turn_rate: 4 [vessel.ManeuveringThrusters] -thrust: 700 +thrust: 30 [vessel.Engine] -thrust: 700 +thrust: 30 Modified: vessels/naree/lotus =================================================================== --- vessels/naree/lotus 2007-01-29 18:00:38 UTC (rev 66) +++ vessels/naree/lotus 2007-01-31 07:01:41 UTC (rev 67) @@ -18,17 +18,17 @@ # dissipation: 10 [vessel.DirectionalThrusters] -thrust: 0.1 -max_turn_rate: 0.055 +thrust: 100 +max_turn_rate: 2.3 [vessel.ManeuveringThrusters] -thrust: 6000 +thrust: 90 [vessel.Engine] -thrust: 10000 +thrust: 150 [projectile.FullereneCannon] -charge_rate: 2.5 +charge_rate: 5 min_charge: 0.25 -max_charge: 4.5 +max_charge: 9 max_charge_time: 4 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-02-15 07:16:39
|
Revision: 110 http://eos-game.svn.sourceforge.net/eos-game/?rev=110&view=rev Author: cduncan Date: 2007-02-14 23:16:36 -0800 (Wed, 14 Feb 2007) Log Message: ----------- gnats are now lotus weapons, launched manually with left ctrl. They automatically target the hostile vessel closest to their mother ship. - Introduce "sensors" used by ai to detect potential threats and targets in a radius surrounding a vessel. Remove experimental proximity detection. - ai tracks targets and mothership (if any) using GroupSingle() and select a new target automatically when the current target is destroyed. - Add --debug cmd line option which drops into pdb at the point where an exception occurs. Modified Paths: -------------- ai.py body.py eos.py game.py netsync.py projectile.py staticbody.py vessel.py vessels/naree/gnat vessels/naree/lotus Modified: ai.py =================================================================== --- ai.py 2007-02-13 08:49:45 UTC (rev 109) +++ ai.py 2007-02-15 07:16:36 UTC (rev 110) @@ -5,8 +5,11 @@ # A.I. # $Id$ +import sys import math +from pygame import sprite from pygame.locals import * +import ode import game import body import netsync @@ -15,15 +18,20 @@ class AIControl(Control): - def __init__(self, vessel, target): + def __init__(self, vessel, target=None, mothership=None, sensor=None): Control.__init__(self) self.vessel = vessel # override vessel collision handler with our own - #self.vessel.collide = self.collide - self.vessel.proximity = self.proximity + self.vessel.collide = self.collide # cache some vessel stats - self.target = target + self.target = sprite.GroupSingle() + if target: + self.target.add(target) + self.mothership = sprite.GroupSingle() + if mothership: + self.mothership.add(mothership) self.close_vessels = [] + self.sensor = sensor self.steerfunc = netsync.random.choice([self.seek, self.pursue]) def seek_position(self, target_position): @@ -45,10 +53,11 @@ """Return desired velocity towards our target >>> game.init() - >>> ai = AIControl(Vessel(), Vessel()) + >>> target = Vessel() + >>> ai = AIControl(Vessel(), target) >>> ai.vessel.max_speed = 33.0 >>> ai.vessel.position = Vector2D(0, 0) - >>> ai.target.position = Vector2D(0, 400) + >>> target.position = Vector2D(0, 400) >>> head, vel = ai.seek() >>> vel.x 0.0 @@ -59,8 +68,8 @@ """ velocity = self.vessel.velocity position = self.vessel.position - target_position = self.target.position - target_velocity = self.target.velocity + target_position = self.target.sprite.position + target_velocity = self.target.sprite.velocity approach = (target_position + target_velocity) - (position + velocity) new_heading = approach.radians #(target_position - self.position).radians if not standoff or approach.length > standoff: @@ -76,10 +85,11 @@ """Return desired velocity away from our target >>> game.init() - >>> ai = AIControl(Vessel(), Vessel()) + >>> target = Vessel() + >>> ai = AIControl(Vessel(), target) >>> ai.vessel.max_speed = 33.0 >>> ai.vessel.position = Vector2D(0, 0) - >>> ai.target.position = Vector2D(0, 400) + >>> target.position = Vector2D(0, 400) >>> head, vel = ai.flee() >>> vel.x -0.0 @@ -97,12 +107,13 @@ when we should intercept them >>> game.init() - >>> ai = AIControl(Vessel(), Vessel()) + >>> target = Vessel() + >>> ai = AIControl(Vessel(), target) >>> ai.vessel.max_speed = 100 >>> ai.vessel.position = Vector2D(0, 0) - >>> ai.target.position = Vector2D(150, 0) - >>> ai.target.velocity = Vector2D(20, 0) - >>> pos = ai.predict_intercept(ai.target) + >>> target.position = Vector2D(150, 0) + >>> target.velocity = Vector2D(20, 0) + >>> pos = ai.predict_intercept(target) >>> pos.x 180.0 >>> pos.y @@ -119,11 +130,12 @@ our target to be >>> game.init() - >>> ai = AIControl(Vessel(), Vessel()) + >>> target = Vessel() + >>> ai = AIControl(Vessel(), target) >>> ai.vessel.max_speed = 100 >>> ai.vessel.position = Vector2D(0, 0) - >>> ai.target.position = Vector2D(150, 0) - >>> ai.target.velocity = Vector2D(-200, 0) + >>> target.position = Vector2D(150, 0) + >>> target.velocity = Vector2D(-200, 0) >>> head, vel = ai.pursue() >>> vel.x -100.0 @@ -132,22 +144,23 @@ >>> head 0.0 """ - heading = (self.target.position - self.vessel.position).radians - return heading, self.seek_position(self.predict_intercept(self.target)) + heading = (self.target.sprite.position - self.vessel.position).radians + return heading, self.seek_position(self.predict_intercept(self.target.sprite)) - def evade(self): + def evade(self, target): """Return desired velocity away from where we predict our target to be. Under evasion, we still turn toward the target, under the assumption that we will resume a pursuit or seek afterward. >>> game.init() - >>> ai = AIControl(Vessel(), Vessel()) + >>> target = Vessel() + >>> ai = AIControl(Vessel(), target) >>> ai.vessel.max_speed = 42 >>> ai.vessel.position = Vector2D(0, 0) - >>> ai.target.position = Vector2D(0, 150) - >>> ai.target.velocity = Vector2D(0, -200) - >>> head, vel = ai.evade() + >>> target.position = Vector2D(0, 150) + >>> target.velocity = Vector2D(0, -200) + >>> head, vel = ai.evade(target) >>> vel.x -0.0 >>> vel.y @@ -203,6 +216,25 @@ self.bw_maneuver = False def update(self): + if (not isinstance(self.target.sprite, Vessel) + or self.target.sprite is self.mothership.sprite): + # acquire a target vessel + if self.sensor is not None: + # Use the sensor to find a target + if self.sensor.closest_vessel: + self.target.add(self.sensor.closest_vessel) + self.steerfunc = self.pursue + self.sensor.disable() + elif not self.sensor.enabled: + self.sensor.enable() + if not self.target: + self.steerfunc = self.seek + if self.mothership: + # head for the mothership + self.target.add(self.mothership) + else: + # No mothership, just head toward a planet + self.target.add(game.planets) # steering desired_heading, desired_velocity = self.steerfunc() desired_velocity += self.avoid_vessels() @@ -212,7 +244,7 @@ for i in range(len(self.vessel.weapons)): self.weapons[i] = self.vessel.weapons[i].targeted - def proximity(self, other): + def collide(self, other, contacts): """Detect contact with other vessels to avoid stacking >>> game.init() @@ -220,10 +252,10 @@ >>> ai.close_vessels [] >>> v = Vessel() - >>> ai.proximity(v) + >>> ai.collide(v, []) >>> ai.close_vessels == [v] True - >>> ai.proximity(object()) + >>> ai.collide(object(), []) >>> ai.close_vessels == [v] True """ @@ -235,12 +267,124 @@ class AIVessel(Vessel): """Vessel under ai control""" - def __init__(self, target=None, **kw): + def __init__(self, target=None, category=body.foe, mothership=None, sensor=None, **kw): Vessel.__init__(self, **kw) - self.setup_collision(body.foe, body.everything & ~body.shot) - self.control = AIControl(self, target) + self.setup_collision(category, body.everything & ~body.shot) + if sensor is None: + sensor = Sensor(self, 1000, body.everything & ~category) + sensor.disable() + self.control = AIControl(self, target, mothership, sensor) +class Sensor: + """Detects bodies in the vicinity of a host ship""" + + detected = None # Sprite group of bodies detected by sensor + enabled = True + + def __init__(self, vessel, radius, detect_bits=body.everything): + """Create a sensor which remains centered around the specified + vessel and detects bodys with categories intersecting detect_bits + within the radius specified. + + >>> game.init() + >>> v = Vessel() + >>> s = Sensor(v, 100, 1) + >>> s.vessel is v + True + >>> s.radius + 100 + >>> s.detect_bits + 1 + """ + self.vessel = vessel + self.radius = radius + self.geom = ode.GeomSphere(game.collision_space, self.radius) + self.geom.setBody(vessel.body) # Move with the vessel + self.geom.parent = self # attach ourself for callback purposes + self.geom.setCategoryBits(body.nothing) # nothing collides with us + self.detect_bits = detect_bits + self.geom.setCollideBits(detect_bits) + self.last_sweep = None + self.detected = sprite.Group() + self.closest_vessel = sprite.GroupSingle() + self._closest_dist = sys.maxint + + def disable(self): + """Turn off sensor + + >>> game.init() + >>> v = Vessel() + >>> s = Sensor(v, 100, 1) + >>> s.enabled + True + >>> s.disable() + >>> s.enabled + False + """ + self.geom.disable() + self.enabled = False + + def enable(self): + """Turn on sensor + + >>> game.init() + >>> v = Vessel() + >>> s = Sensor(v, 100, 1) + >>> s.enabled + True + >>> s.disable() + >>> s.enabled + False + >>> s.enable() + >>> s.enabled + True + """ + self.geom.enable() + self.enabled = True + + def collide(self, other, contacts): + """Detect a body in our radius + + >>> game.init() + >>> v = Vessel() + >>> s = Sensor(v, 100, 1) + >>> o = Vessel() + >>> s.collide(o, []) + >>> s.detected.sprites() == [o] + True + >>> s.collide(o, []) + >>> s.detected.sprites() == [o] + True + >>> s.closest_vessel.sprite is o + True + >>> o.kill() + >>> s.detected.sprites() + [] + """ + if self.last_sweep != game.frame_no and self.detected: + # This is a new frame, start a new sensor sweep + self.detected.empty() + self._closest_dist = sys.maxint + self.closest_vessel.empty() + if isinstance(other, Vessel): + distance = self.vessel.position.distance(other.position) + if distance < self._closest_dist: + self.closest_vessel.sprite = other + self._closest_dist = distance + self.detected.add(other) + + +class SharedSensor(Sensor): + """Sensor shared between multiple AIs""" + + def enable(self): + pass + + def disable(self): + pass + + if __name__ == '__main__': """Run tests if executed directly""" import sys, doctest Modified: body.py =================================================================== --- body.py 2007-02-13 08:49:45 UTC (rev 109) +++ body.py 2007-02-15 07:16:36 UTC (rev 110) @@ -42,6 +42,8 @@ offscreen_img = None # Rotated image to point to offscreen sprite xplode_animation = None + rect = None + 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 @@ -70,7 +72,12 @@ self.position = PositionVector(self.body) self.velocity = VelocityVector(self.body) # on screen size and positioning - self.rect = pygame.Rect([self.position.x, self.position.y, 0, 0]) + 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.enabled = True self.on_screen = True self.explosion = None @@ -182,6 +189,8 @@ return self.rect def disable(self): + if self.geom is not None: + self.geom.disable() # don't participate in collisions anymore self.enabled = False def explode(self): Modified: eos.py =================================================================== --- eos.py 2007-02-13 08:49:45 UTC (rev 109) +++ eos.py 2007-02-15 07:16:36 UTC (rev 110) @@ -32,19 +32,29 @@ parser.add_option('-o', '--profile', dest='do_profile', default=False, action='store_true', help='enable profiling') + parser.add_option('-d', '--debug', + dest='debug', default=False, action='store_true', + help='Drop into the debugger on error') opts, args = parser.parse_args() if args: raise optparse.OptParseError('Unrecognized args: %s' % args) game.init(opts.server_game is not None, opts.server_game or opts.client_game, opts.host, opts.port, opts.player_count, opts.fullscreen) start = pygame.time.get_ticks() - if opts.do_profile: - try: - import cProfile as profile - except ImportError: - import profile - profile.run('game.play()') - else: - game.play() + try: + if opts.do_profile: + try: + import cProfile as profile + except ImportError: + import profile + profile.run('game.play()') + else: + game.play() + except: + if opts.debug: + import pdb, sys + pdb.post_mortem(sys.exc_info()[-1]) + else: + raise print 'Average fps:', game.frame_no * 1000.0/(game.time - start) Modified: game.py =================================================================== --- game.py 2007-02-13 08:49:45 UTC (rev 109) +++ game.py 2007-02-15 07:16:36 UTC (rev 110) @@ -22,6 +22,7 @@ # Sprite groups sprites = None # All sprites +planets = None # Other globally accessible things clock = None @@ -30,15 +31,17 @@ players = None camera = None -def init(run_server=False, game_id=None, host='127.0.0.1', port='11211', player_count=1, fullscreen=False): +def init(run_server=False, game_id=None, host='127.0.0.1', port='11211', + player_count=1, fullscreen=False): import body from sprite import RenderedGroup + from pygame.sprite import Group from stars import StarField from staticbody import Planet from vessel import Vessel, KeyboardControl from media import load_image global universe, collision_space, screen, screen_rect, background - global sprites, fps, avg_fps, clock, time, players, camera, ai + global sprites, planets, fps, avg_fps, clock, time, players, camera, ai # Initialize pygame and setup main screen pygame.init() # Get the available display modes and use the best one @@ -53,7 +56,7 @@ print "Could not find ideal display mode, using", mode screen = pygame.display.set_mode(mode, FULLSCREEN, 16) else: - screen = pygame.display.set_mode((640, 480)) + screen = pygame.display.set_mode((1024, 768)) pygame.display.set_icon(load_image('eos-icon.png')) screen_rect = screen.get_rect() pygame.display.set_caption('Eos') @@ -70,6 +73,7 @@ # Create game elements clock = pygame.time.Clock() sprites = RenderedGroup() + planets = Group() StarField(screen.get_rect()) Planet('earth-east.png', 0, 0) @@ -101,19 +105,8 @@ # then dispatch the local collision handlers eos_body1 = geom1.parent eos_body2 = geom2.parent - eos_body1.proximity(eos_body2) - eos_body2.proximity(eos_body1) eos_body1.collide(eos_body2, contacts) eos_body2.collide(eos_body1, contacts) - elif geom1.placeable() and geom2.placeable(): - # Check for close proximity - x1, y1, ignored = geom1.getPosition() - x2, y2, ignored = geom2.getPosition() - dist2 = (x1 - x2)**2 + (y1 - y2)**2 - if dist2 < (geom1.getRadius() + 100)**2: - geom1.parent.proximity(geom2.parent) - if dist2 < (geom2.getRadius() + 100)**2: - geom2.parent.proximity(geom1.parent) except: # Output traceback hidden by ode import traceback @@ -147,8 +140,5 @@ for i in xrange(2): ai = AIVessel.load('vessels/naree/cress', target=target) ai.position.x, ai.position.y = x, y - for i in xrange(4): - ai = AIVessel.load('vessels/naree/gnat', target=target) - ai.position.x, ai.position.y = x, y ai_interval = max(ai_interval * .9, 5) frame_no += 1 Modified: netsync.py =================================================================== --- netsync.py 2007-02-13 08:49:45 UTC (rev 109) +++ netsync.py 2007-02-15 07:16:36 UTC (rev 110) @@ -12,7 +12,6 @@ import signal import subprocess import time -import vessel from random import Random random = None @@ -34,6 +33,7 @@ def init(run_server, game_id, host, port, expected_clients, debug=False): global random, send_keystate, step, players + import vessel if game_id is None: # no need for memcached random = Random() send_keystate = send_keystate_local Modified: projectile.py =================================================================== --- projectile.py 2007-02-13 08:49:45 UTC (rev 109) +++ projectile.py 2007-02-15 07:16:36 UTC (rev 110) @@ -18,6 +18,7 @@ class FullereneCannon(vessel.Weapon): name = 'fullerene pulse cannon' + priority = 0 mass = 10 def __init__(self, gunmount, charge_rate, min_charge, max_charge, max_charge_time): @@ -52,9 +53,10 @@ @property def targeted(self): - target = getattr(self.gunmount.control, 'target', None) - if target is None or not target.alive(): + if not self.gunmount.control.target: return False + else: + target = self.gunmount.control.target.sprite target_dist = target.position.distance(self.gunmount.position) target_angle = (self.gunmount.position - target.position).radians heading_diff = target.heading - self.gunmount.heading @@ -63,7 +65,7 @@ elif heading_diff < -halfcircle: heading_diff += fullcircle if self.firing: - if (self.charge >= self.max_charge / 3 and abs(heading_diff) < diagonal + if (self.charge >= self.max_charge / 3 and abs(heading_diff) < (diagonal / 2) and target_dist < Fullerene.range * 2): return False else: Modified: staticbody.py =================================================================== --- staticbody.py 2007-02-13 08:49:45 UTC (rev 109) +++ staticbody.py 2007-02-15 07:16:36 UTC (rev 110) @@ -19,13 +19,13 @@ def __init__(self, img_name, x=0, y=0): RoundBody.__init__(self, (x, y)) + game.planets.add(self) self.body.disable() # immobile self.image = media.load_image(img_name) self.offscreen_img = RotatedImage('pointy-blue.gif') clear = self.image.get_colorkey() if clear: self.image.set_colorkey(clear, RLEACCEL) - image_rect = self.image.get_rect() - self.radius = image_rect.width / 2 - self.rect.size = image_rect.size + self.rect = self.image.get_rect(centerx=self.position.x, centery=self.position.y) + self.radius = self.rect.width / 2 Modified: vessel.py =================================================================== --- vessel.py 2007-02-13 08:49:45 UTC (rev 109) +++ vessel.py 2007-02-15 07:16:36 UTC (rev 110) @@ -34,6 +34,8 @@ shield = False weapons = [False] * max_weapons + target = None # body being targeted + def __init__(self): weapons = Control.weapons[:] # shallow copy @@ -53,6 +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] @classmethod def new_player(cls): @@ -312,9 +315,9 @@ return iter(self._sys) def disable(self): + body.RoundBody.disable(self) for system in self: system.disable() - self.enabled = False def damage(self, value): """Apply damage to the vessel @@ -357,6 +360,10 @@ if hasattr(system, 'kill'): system.kill() body.RoundBody.kill(self) + + def __str__(self): + return '<%s@%x "%s" at (%d, %d)>' % ( + self.__class__.__name__, id(self), self.vessel_class, self.position.x, self.position.y) class System: @@ -713,9 +720,11 @@ # this avoids a sudden decceleration when we let off boost self.vessel.max_speed = max(self.vessel.max_speed * 0.98, self.max_speed) + # Imports for vessel config use import projectile import beam +import bay if __name__ == '__main__': """Run tests if executed directly""" Modified: vessels/naree/gnat =================================================================== --- vessels/naree/gnat 2007-02-13 08:49:45 UTC (rev 109) +++ vessels/naree/gnat 2007-02-15 07:16:36 UTC (rev 110) @@ -24,5 +24,5 @@ [beam.BeamWeapon] range: 130 -damage: 15 +damage: 19 color: 255, 80, 120 Modified: vessels/naree/lotus =================================================================== --- vessels/naree/lotus 2007-02-13 08:49:45 UTC (rev 109) +++ vessels/naree/lotus 2007-02-15 07:16:36 UTC (rev 110) @@ -32,3 +32,9 @@ min_charge: 0.25 max_charge: 9 max_charge_time: 4 + +[bay.FighterBay] +fighter_config: vessels/naree/gnat +capacity: 4 +launch_delay: 1.5 +launch_distance: 400 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-03-05 23:24:10
|
Revision: 123 http://eos-game.svn.sourceforge.net/eos-game/?rev=123&view=rev Author: cduncan Date: 2007-03-05 15:24:08 -0800 (Mon, 05 Mar 2007) Log Message: ----------- - Use seek instead of pursue always - Seek does predict ahead similar to pursue - You will probably die fast ;^) Modified Paths: -------------- ai.py vessels/naree/gnat Modified: ai.py =================================================================== --- ai.py 2007-03-05 23:18:06 UTC (rev 122) +++ ai.py 2007-03-05 23:24:08 UTC (rev 123) @@ -34,7 +34,7 @@ self.proximity_radius = self.vessel.radius * 2 self.sensor = sensor self.steerfunc = netsync.random.choice([self.seek, self.pursue]) - self.steerfunc = self.pursue + self.steerfunc = self.seek def seek_position(self, target_position, predict_ahead=0.75): """Return desired velocity towards a fixed position @@ -52,7 +52,7 @@ return ((target_position - (self.vessel.position + self.vessel.velocity * predict_ahead) ) * game.avg_fps).clamp(self.vessel.max_speed) - def seek(self, standoff=150, predict_ahead=1.0): + def seek(self, predict_ahead=2.0): """Return desired velocity towards our target >>> game.init() @@ -73,17 +73,17 @@ position = self.vessel.position target_position = self.target.sprite.position target_velocity = self.target.sprite.velocity + predict_ahead = (position + velocity * predict_ahead).distance( + target_position + target_velocity * predict_ahead) / self.vessel.max_speed + print self.vessel, predict_ahead approach = (target_position + target_velocity * predict_ahead) - ( position + velocity * predict_ahead) new_heading = approach.radians - if not standoff or approach.length > standoff: - if velocity.length and approach.length / velocity.length < game.avg_fps: - return new_heading, approach.clamp(velocity.length) - else: - return new_heading, approach.clamp(self.vessel.max_speed) + if approach.length > max(self.target.sprite.radius * 1.5, 100): + return new_heading, approach * game.avg_fps else: # Close in, match speed with the target - return new_heading, target_velocity.copy() + return (target_position - position).radians, target_velocity.copy() def flee(self): """Return desired velocity away from our target @@ -247,7 +247,7 @@ # Use the sensor to find a target if self.sensor.closest_vessel: self.target.add(self.sensor.closest_vessel) - self.steerfunc = self.pursue + self.steerfunc = self.seek self.sensor.disable() elif not self.sensor.enabled: self.sensor.enable() Modified: vessels/naree/gnat =================================================================== --- vessels/naree/gnat 2007-03-05 23:18:06 UTC (rev 122) +++ vessels/naree/gnat 2007-03-05 23:24:08 UTC (rev 123) @@ -14,7 +14,7 @@ [vessel.DirectionalThrusters] thrust: 1 -max_turn_rate: 4 +max_turn_rate: 5 [vessel.ManeuveringThrusters] thrust: 30 @@ -23,6 +23,6 @@ thrust: 30 [beam.BeamWeapon] -range: 130 -damage: 19 +range: 150 +damage: 25 color: 255, 80, 120 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-03-06 00:02:14
|
Revision: 126 http://eos-game.svn.sourceforge.net/eos-game/?rev=126&view=rev Author: cduncan Date: 2007-03-05 16:02:12 -0800 (Mon, 05 Mar 2007) Log Message: ----------- - Add a little initial lookahead to reduce ai "orbiting" - Reduce gnat range, they don't need it - Reduce min flak bomb flight time so you can get things that are closer Modified Paths: -------------- ai.py projectile.py vessels/naree/gnat Modified: ai.py =================================================================== --- ai.py 2007-03-05 23:39:30 UTC (rev 125) +++ ai.py 2007-03-06 00:02:12 UTC (rev 126) @@ -52,7 +52,7 @@ return ((target_position - (self.vessel.position + self.vessel.velocity * predict_ahead) ) * game.avg_fps).clamp(self.vessel.max_speed) - def seek(self, predict_ahead=0.0): + def seek(self, predict_ahead=0.25): """Return desired velocity towards our target >>> game.init() Modified: projectile.py =================================================================== --- projectile.py 2007-03-05 23:39:30 UTC (rev 125) +++ projectile.py 2007-03-06 00:02:12 UTC (rev 126) @@ -262,7 +262,7 @@ shard_velocity = 500 shard_range = 100 shard_damage = 3.0 - min_time = 0.7 + min_time = 0.5 def __init__(self, shooter, bomb_bay, velocity, max_ttl): bomb_img = media.RotatedImage('flak-bomb.png') Modified: vessels/naree/gnat =================================================================== --- vessels/naree/gnat 2007-03-05 23:39:30 UTC (rev 125) +++ vessels/naree/gnat 2007-03-06 00:02:12 UTC (rev 126) @@ -23,6 +23,6 @@ thrust: 30 [beam.BeamWeapon] -range: 150 +range: 100 damage: 25 color: 255, 80, 120 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-05-27 18:18:42
|
Revision: 229 http://eos-game.svn.sourceforge.net/eos-game/?rev=229&view=rev Author: cduncan Date: 2007-05-27 11:18:40 -0700 (Sun, 27 May 2007) Log Message: ----------- - Add a tracking arc feature to beam weapons allowing them to aim off-axis at a the selected target. This is designed to make the cress more user-friendly. Currently tracking is instantaneous, eventually it should probably have a max velocity. - Set cress lepton beam arc at 10 degrees. That's not much but it's enough to make it much more effective. Note that the ai does not select ships like the player does, thus the ai does not use tracking, but it really doesn't need it IMO. - Fix selection reticule size to use the collision radius rather than the mass radius. Modified Paths: -------------- beam.py selection.py vessels/naree/cress Modified: beam.py =================================================================== --- beam.py 2007-05-24 06:05:17 UTC (rev 228) +++ beam.py 2007-05-27 18:18:40 UTC (rev 229) @@ -20,13 +20,15 @@ mass_factor = 0.1 # Mass per unit damage - def __init__(self, gunmount, range, damage, efficiency, width=1, color='255,255,255'): + def __init__(self, gunmount, range, damage, efficiency, arc_degrees=0, + width=1, color='255,255,255'): pygame.sprite.Sprite.__init__(self, game.new_sprites) self.gunmount = gunmount self.range = float(range) self.length = self.range self.beam_damage = float(damage) self.efficiency = float(efficiency) + self.arc = float(arc_degrees) / 360.0 * vector.fullcircle self.mass = self.beam_damage * self.mass_factor self.width = int(width) self.color = tuple(int(c) for c in color.split(',')) @@ -44,8 +46,16 @@ self.ray.setCategoryBits(body.nothing) self.ray.setCollideBits(body.everything & ~self.gunmount.category) self.ray.parent = self + if self.arc > 0 and self.gunmount is game.local_player and game.target.target: + # Track the selected target + track_angle = self.gunmount.bearing(game.target.target) + if abs(track_angle) > self.arc: + # limit angle to maximum arc + track_angle /= track_angle / self.arc + else: + track_angle = 0 self.ray.set(vector.to_tuple3(self.gunmount.position), - vector.to_tuple3(vector.unit(self.gunmount.heading))) + vector.to_tuple3(vector.unit(self.gunmount.heading + track_angle))) self.firing = self.firing and self.gunmount.use_energy( self.beam_damage / self.efficiency / game.fps) Modified: selection.py =================================================================== --- selection.py 2007-05-24 06:05:17 UTC (rev 228) +++ selection.py 2007-05-27 18:18:40 UTC (rev 229) @@ -73,8 +73,8 @@ if target is not None: # To avoid lagging behind the target, we update our rect here # which guarantees that the target's rect is already updated - if hasattr(target, 'radius'): - width = target.radius * 2 * self.size_factor + if hasattr(target, 'collision_radius'): + width = target.collision_radius * 2 * self.size_factor else: width = max(target.rect.size) * self.size_factor rect.center = target.rect.center Modified: vessels/naree/cress =================================================================== --- vessels/naree/cress 2007-05-24 06:05:17 UTC (rev 228) +++ vessels/naree/cress 2007-05-27 18:18:40 UTC (rev 229) @@ -28,5 +28,6 @@ range=200 damage=95 efficiency: 0.97 +arc_degrees: 10 width=2 color: 100, 255, 100 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-10-12 07:15:09
|
Revision: 285 http://eos-game.svn.sourceforge.net/eos-game/?rev=285&view=rev Author: cduncan Date: 2007-10-12 00:15:02 -0700 (Fri, 12 Oct 2007) Log Message: ----------- Add Naree corde assault ship. You can fly it, but it has no weapons and can't take planets yet Modified Paths: -------------- game.py vessel.py Added Paths: ----------- art/naree/corde.xcf data/corde.png vessels/naree/corde Added: art/naree/corde.xcf =================================================================== (Binary files differ) Property changes on: art/naree/corde.xcf ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: data/corde.png =================================================================== (Binary files differ) Property changes on: data/corde.png ___________________________________________________________________ Name: svn:mime-type + image/png Modified: game.py =================================================================== --- game.py 2007-10-11 05:09:22 UTC (rev 284) +++ game.py 2007-10-12 07:15:02 UTC (rev 285) @@ -124,6 +124,7 @@ Vessel.load('vessels/rone/drach'), Vessel.load('vessels/naree/lotus'), Vessel.load('vessels/naree/cress'), + Vessel.load('vessels/naree/corde'), Vessel.load('vessels/sc/pegasus'), Vessel.load('vessels/sc/striker'), ] Modified: vessel.py =================================================================== --- vessel.py 2007-10-11 05:09:22 UTC (rev 284) +++ vessel.py 2007-10-12 07:15:02 UTC (rev 285) @@ -5,6 +5,7 @@ # Vessel classes # $Id$ +import os.path import body import ConfigParser import game @@ -676,7 +677,11 @@ 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, enhanced=False) + shield_img_file = '%s-shield.png' % vessel.vessel_class + if os.path.exists(shield_img_file): + self.rot_images = RotatedImage(shield_img_file, enhanced=False) + else: + self.rot_images = None self.time = 0 self.enabled = True @@ -728,7 +733,7 @@ self.max_regeneration) def draw(self, surface): - if self.time >= game.time and self.level: + if self.time >= game.time and self.level and self.rot_images is not None: image = self.rot_images.rotated( math.radians(game.time / 30)) apparent_size, pos = vector.to_screen(self.vessel.position) Added: vessels/naree/corde =================================================================== --- vessels/naree/corde (rev 0) +++ vessels/naree/corde 2007-10-12 07:15:02 UTC (rev 285) @@ -0,0 +1,27 @@ +# $Id: lotus 271 2007-09-29 16:46:25Z cduncan $ + +[general] +vessel_class: corde +vessel_type: assault ship +description: As a pacifist race, the Naree have had no use for a planetary assault vessel. When it became clear that one was required, they refitted one of their ubiquitous freighter hulls for the purpose. The Naree abhor barbaric infantry warfare, and instead utilize a powerful stasis technology which renders the indigenous populations or enemy forces inert indefinitely with little or no bloodshed. Naree scientists and engineers are currently working on a space-born application of this technology to curtail the senseless violent plague of destruction in the skies. +hull_mass: 80 +hull_length: 50 +crew: 100 +max_speed: 80 +max_energy: 1000 +ai: EvaderAI +cost: 300 + +[vessel.Shield] +max_level: 1200 +regeneration: 10 + +[vessel.DirectionalThrusters] +thrust: 200 +max_turn_rate: 1.0 + +[vessel.ManeuveringThrusters] +thrust: 100 + +[vessel.Engine] +thrust: 80 Property changes on: vessels/naree/corde ___________________________________________________________________ Name: svn:eol-style + native This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-11-01 22:01:58
|
Revision: 318 http://eos-game.svn.sourceforge.net/eos-game/?rev=318&view=rev Author: cduncan Date: 2007-11-01 15:01:50 -0700 (Thu, 01 Nov 2007) Log Message: ----------- - Add event handler apis and supporting code for higher level mouse events: click, double click and drag - Add click and drag selection support to playing field and minimap. - Add 'incidental' flag to body objects so they can be configured not to show on maps or be selectable (e.g., gnats). This removes a bunch of special case checking throughout. - Fix target timeout when setting the target of ai vessels explicitly. Also, always set the objective as well, to allow ai vessels to engage enemies on their way to their eventual target objective. - Fix rare div by zero bug in tracker Modified Paths: -------------- ai.py body.py camera.py event.py game.py panel.py selection.py vessel.py vessels/naree/gnat Modified: ai.py =================================================================== --- ai.py 2007-10-30 07:25:40 UTC (rev 317) +++ ai.py 2007-11-01 22:01:50 UTC (rev 318) @@ -22,7 +22,6 @@ class BasicAI(Control): """Basic AI Control""" - target_timeout = 5000 # Maximum distance to target when we have an objective target_max_distance_with_objective = 2000 @@ -286,21 +285,37 @@ vector.distance(self.vessel.position, self.sensor.closest_vessel.sprite.position) < self.target_max_distance_with_objective)): - self.target.add(self.sensor.closest_vessel) - self.target_time = game.time + self.target_timeout + self.set_target(self.sensor.closest_vessel) self.sensor.disable() elif not self.sensor.enabled: self.sensor.enable() if not self.target: - self.target_time = 0 # look for other targets immediately - if self.objective: - # head for the objective - self.target.add(self.objective) - else: - # No objective, just head toward a planet - self.target.add(game.map.planets) + if not self.objective: + self.choose_objective() + # head for the objective looking for other targets + self.set_target(self.objective, timeout=0) - def set_target(self, target, timeout=None): + def choose_objective(self): + """Set the ship's objective, which by default is to head to the nearest + friendly or neutral planet + """ + def distance(planet): + return vector.distance(self.vessel.position, planet.position) + planets = sorted(game.map.planets, key=distance) + # Try for the closest friendly planet first + for planet in planets: + if planet.base is not None and planet.base.owner.is_friendly(self.vessel): + self.objective.add(planet) + return + # Try for the closest neutral planet next + for planet in planets: + if planet.base is None: + self.objective.add(planet) + return + # Just head for the closest planet at all + self.objective.add(planets[0]) + + def set_target(self, target, timeout=3000): """Set the target for the ai, timing out in timeout seconds at which time it will acquire another target """ @@ -591,16 +606,16 @@ # This is a new frame, start a new sensor sweep self._detected = [] self._closest_dist = sys.maxint - self._closest_type = None + self._closest_incidental = False self.closest_vessel.empty() self.last_sweep = game.frame_no 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'): + or self._closest_incidental and other.incidental): self.closest_vessel.sprite = other self._closest_dist = distance - self._closest_type = other.vessel_type + self._closest_incidental = other.incidental @property def detected(self): @@ -679,7 +694,7 @@ top += x * y bot_left += x * x bot_right += y * y - correlation = abs(top / math.sqrt(bot_left * bot_right)) + correlation = abs(top / math.sqrt(bot_left * bot_right or float('nan'))) endx, endy = self.samples[-1] v2 = vector.vector2(endx - startx, endy - starty) / ( len(self.samples) * self.sample_time) * time_ahead Modified: body.py =================================================================== --- body.py 2007-10-30 07:25:40 UTC (rev 317) +++ body.py 2007-11-01 22:01:50 UTC (rev 318) @@ -42,6 +42,7 @@ collision_radius = None # Optional collision radius if different from above max_visible_dist = 10000 # Maximum distance pointy is shown scale = 1.0 # Onscreen scale adjustment + incidental = False # Incidental bodies cannot be selected, do not show on maps, etc category = nothing # category for collision interaction collides_with = nothing # category bitmap this body collides with Modified: camera.py =================================================================== --- camera.py 2007-10-30 07:25:40 UTC (rev 317) +++ camera.py 2007-11-01 22:01:50 UTC (rev 318) @@ -58,8 +58,8 @@ def acquire_target(self): # Select a new target - for ship in sprite.layers.vessels.sprites(): - if (ship.alive() and getattr(ship, 'vessel_type', None) not in ('missile', 'station')): + for ship in sprite.layers.vessels: + if ship.alive() and not ship.incidental: self.follow(ship) break Modified: event.py =================================================================== --- event.py 2007-10-30 07:25:40 UTC (rev 317) +++ event.py 2007-11-01 22:01:50 UTC (rev 318) @@ -13,6 +13,9 @@ import vector import panel import selection +import sprite +from vessel import Vessel +from staticbody import Planet class Handler: @@ -26,6 +29,18 @@ def mouse_down(self, event): """Handle mouse button press""" + + def mouse_click(self, event, pos): + """Single mouse click at pos, event contains mouse button info""" + + def mouse_double_click(self, event, pos): + """Double mouse click at pos, event contains mouse button info""" + + def mouse_drag(self, event, start_pos, end_pos): + """Mouse drag in progress from start_pos to end_pos, event contains mouse button info""" + + def mouse_drag_end(self, event, start_pos, end_pos): + """Mouse drag completed from tart_pos to end_pos, event contains mouse button info""" def key_down(self, event): """Handle key press""" @@ -35,6 +50,8 @@ """Top-level event handler""" mouse_timeout = 3000 # Time to hide mouse if not moved or clicked + double_click_time = 750 # max millis between click to be considered a double + mouse_slop = 5 # Amount mouse is allowed to move to differential between clicks and drags def __init__(self, handlers=()): """Dispatches pygame events to the handler's methods""" @@ -48,6 +65,11 @@ self.handlers = list(handlers) self.mouse_visible = pygame.mouse.set_visible(not display.fullscreen) self.mouse_hide_time = 0 + self.last_click_time = 0 + self.last_click_pos = None + self.mouse_down_pos = None + self.mouse_handler = None + self.drag = False self.running = True def handle_events(self): @@ -68,22 +90,58 @@ def mouse_move(self, event): """Handle mouse movement""" self.show_mouse() + if event.buttons and not self.drag and self.mouse_down_pos is not None: + # See if the user has moved enough to begin a drag + self.drag = (vector.distance(vector.vector2(*event.pos), self.mouse_down_pos) + > self.mouse_slop) + if self.drag and self.mouse_down_pos is not None: + if self.mouse_handler is not None and self.mouse_handler.mouse_drag( + event, vector.to_tuple(self.mouse_down_pos), event.pos): + return + elif self.mouse_handler is None: + for handler in self.handlers: + if handler.mouse_drag(event, vector.to_tuple(self.mouse_down_pos), event.pos): + self.mouse_handler = handler + return for handler in self.handlers: handler.mouse_move(event) def mouse_up(self, event): """Handle mouse button release""" self.show_mouse() + click_pos = vector.to_tuple(self.mouse_down_pos) + self.mouse_down_pos = None + if self.mouse_handler is not None: + if self.drag and self.mouse_handler.mouse_drag_end(event, click_pos, event.pos): + self.drag = False + self.last_click_time = 0 + return + if game.time - self.last_click_time < self.double_click_time: + was_double_click = not self.drag + self.last_click_time = 0 + else: + was_double_click = False + self.last_click_time = game.time + if was_double_click and self.mouse_handler.mouse_double_click(event, click_pos): + return + if not self.drag and self.mouse_handler.mouse_click(event, click_pos): + return for handler in self.handlers: + if not self.drag and handler.mouse_click(event, click_pos): + break if handler.mouse_up(event): break def mouse_down(self, event): """Handle mouse button press""" self.show_mouse() + self.mouse_down_pos = vector.vector2(*event.pos) + self.drag = False for handler in self.handlers: if handler.mouse_down(event): + self.mouse_handler = handler break + self.mouse_handler = None def quit(self, event=None): self.running = False @@ -106,9 +164,12 @@ game.exit() -class GameCommandsHandler(Handler): - """Global game command event handler""" +class PlayingFieldHandler(Handler, sprite.Sprite): + """Game playing field event handler""" + select_rect = None + rect = pygame.Rect(0, 0, 0, 0) + def zoom_in(self): """Zoom in""" game.camera.zoom_in() @@ -147,6 +208,7 @@ game.target.select(game.local_player) def __init__(self): + sprite.Sprite.__init__(self, sprite.layers.ui) key_map = { '=': self.zoom_in, '-': self.zoom_out, @@ -165,6 +227,47 @@ self.key_map[event.key]() return True + def mouse_click(self, event, pos): + # First check for vessel click + click_rect = pygame.Rect(0, 0, 10, 10) + click_rect.center = pos + vessels = [s for s in sprite.layers.vessels if isinstance(s, Vessel)] + vessels.reverse() + for vessel in vessels: + if (isinstance(vessel, Vessel) and vessel.rect.colliderect(click_rect) + and vessel is not game.local_player and not vessel.incidental): + if vessel.is_friendly(game.local_player): + selection.group.select(vessel) + else: + game.target.select(vessel) + return True + # No vessel was there, see about planets + planets = [s for s in sprite.layers.background if isinstance(s, Planet)] + planets.reverse() + for planet in planets: + if planet.rect.colliderect(click_rect): + game.target.select(planet) + return True + # Nothing specific was clicked, just clear the current selected group + selection.group.select_none() + return True + + def mouse_drag(self, event, start_pos, end_pos): + self.select_rect = pygame.Rect( + min(start_pos[0], end_pos[0]), min(start_pos[1], end_pos[1]), + abs(start_pos[0] - end_pos[0]), abs(start_pos[1] - end_pos[1])) + return True - - + def mouse_drag_end(self, event, start_pos, end_pos): + selection.group.select_none() + selection.group.select_screen_rect(game.local_player.category, self.select_rect) + self.select_rect = None + + def draw(self, surface): + if self.select_rect is not None: + pygame.draw.rect(surface, (100, 100, 200), self.select_rect, 1) + return self.select_rect + return self.rect + +playing_field_handler = PlayingFieldHandler() + Modified: game.py =================================================================== --- game.py 2007-10-30 07:25:40 UTC (rev 317) +++ game.py 2007-11-01 22:01:50 UTC (rev 318) @@ -224,7 +224,7 @@ enemies = pygame.sprite.Group() next_wave = 0 this_frame_time = last_frame_time = 1000 / fps - event_handler = event.MainHandler([event.GameCommandsHandler(), panel.handler]) + event_handler = event.MainHandler([panel.handler, event.playing_field_handler]) if windowed: sleep_time = 0.01 else: Modified: panel.py =================================================================== --- panel.py 2007-10-30 07:25:40 UTC (rev 317) +++ panel.py 2007-11-01 22:01:50 UTC (rev 318) @@ -18,6 +18,7 @@ import widget import sprite import media +import selection class Panel(sprite.Sprite, event.Handler): """User interface panel. Panels contain controls. Enabled panels receive @@ -268,6 +269,7 @@ BottomPanel.__init__(self, width, height, align_right=True) self.tab = PanelTab(self, align_right=True) self.controls.add(self.tab) + self.select_rect = None def create_image(self): image = pygame.Surface(self.rect.size, pygame.SRCALPHA, 32) @@ -302,7 +304,7 @@ self.tab.rect.right = self.rect.right def update_vessel(self, vessel): - if vessel.vessel_type != 'missile': + if not vessel.incidental: if vessel.is_friendly(game.local_player): if vessel.selected: color = (255, 255, 255) @@ -315,11 +317,51 @@ pos_x + self.map_rect.centerx, pos_y + self.map_rect.centery, 2, 2) self.map_points.fill(color, point_rect) self.map_mask.fill((0, 0, 0, 120), point_rect.inflate(4, 4)) + + def mouse_drag(self, event, start_pos, end_pos): + if self.select_rect is None and not self.rect.collidepoint(start_pos): + return False # Drag did not originate in our panel + self.select_rect = pygame.Rect( + min(start_pos[0], end_pos[0]), min(start_pos[1], end_pos[1]), + abs(start_pos[0] - end_pos[0]), abs(start_pos[1] - end_pos[1])) + return True + def mouse_drag_end(self, event, start_pos, end_pos): + if self.select_rect is not None: + selection.group.select_none() + # Translate the select rect to map coordinates + (left, top), (width, height) = self.select_rect.topleft, self.select_rect.size + rect = pygame.Rect((left - self.rect.centerx) / self.map_scale, + (top - self.rect.centery) / self.map_scale, + width / self.map_scale, height / self.map_scale) + selection.group.select_map_rect(game.local_player.category, rect) + self.select_rect = None + return True + + def mouse_click(self, event, pos): + if self.rect.collidepoint(pos): + # Translate the position to map coodinates + map_pos = ((pos[0] - self.rect.centerx) / self.map_scale, + (pos[1] - self.rect.centery) / self.map_scale) + planet_rect = pygame.Rect(0, 0, 0, 0) + for planet in game.map.planets: + planet_rect.width = planet_rect.height = planet.collision_radius * 4 + planet_rect.center = vector.to_tuple(planet.position) + if planet_rect.collidepoint(map_pos): + game.target.select(planet) + break + return True + def draw(self, surface): + old_clip = surface.get_clip() + surface.set_clip(self.rect) surface.blit(self.image, self.rect) + surface.set_clip(self.rect.inflate(-self.border_width * 2, -self.border_width * 2)) surface.blit(self.map_mask, self.rect) surface.blit(self.map_points, self.rect) + if self.select_rect is not None: + pygame.draw.rect(surface, (200, 100, 0), self.select_rect, 1) + surface.set_clip(old_clip) return self.rect @@ -640,11 +682,28 @@ def mouse_down(self, event): """Handle mouse button press""" - handled = False for panel in self.panels: if panel.mouse_down(event): return panel + + def mouse_click(self, event, pos): + """Handle click events""" + for panel in self.panels: + if panel.mouse_click(event, pos): + return panel + + def mouse_drag(self, event, start_pos, end_pos): + """Handle drag events""" + for panel in self.panels: + if panel.mouse_drag(event, start_pos, end_pos): + return panel + def mouse_drag_end(self, event, start_pos, end_pos): + """Handle drag events""" + for panel in self.panels: + if panel.mouse_drag_end(event, start_pos, end_pos): + return panel + def key_down(self, event): """Handle key press""" for panel in self.panels: Modified: selection.py =================================================================== --- selection.py 2007-10-30 07:25:40 UTC (rev 317) +++ selection.py 2007-11-01 22:01:50 UTC (rev 318) @@ -53,8 +53,7 @@ try: while 1: next = self._nearest_vessel_iter.next() - if next is not self.selected.sprite and ( - getattr(next, 'vessel_type', None) != 'missile'): + if next is not self.selected.sprite and not next.incidental: # Ensure we always select a new target self.select(next) break @@ -141,13 +140,19 @@ if clear_class_iter: self.by_class_iter = None - def select_rect(self, category, rect): - """Select all vessels matching the category inside the given rect""" + def select(self, vessel): + """Select a single vessel""" + self.select_none() + self.selected.add(vessel) + vessel.selected = True + + def select_screen_rect(self, category, rect): + """Select all vessels matching the category inside the given rect in screen coordinates""" in_rect = rect.colliderect by_class = {} for spr in sprite.layers.vessels: if (isinstance(spr, Vessel) and spr.category & category and in_rect(spr.rect) - and spr is not game.local_player and spr.vessel_type != 'missile'): + and spr is not game.local_player and not spr.incidental): self.selected.add(spr) if spr.vessel_class not in by_class: by_class[spr.vessel_class] = pygame.sprite.Group() @@ -158,6 +163,24 @@ else: self.by_class = None + def select_map_rect(self, category, rect): + """Select all vessels matching the category inside the given rect in map coordinates""" + in_rect = rect.collidepoint + by_class = {} + for spr in sprite.layers.vessels: + if (isinstance(spr, Vessel) and spr.category & category + and in_rect(vector.to_tuple(spr.position)) + and spr is not game.local_player and not spr.incidental): + self.selected.add(spr) + if spr.vessel_class not in by_class: + by_class[spr.vessel_class] = pygame.sprite.Group() + by_class[spr.vessel_class].add(spr) + spr.selected = True + if by_class: + self.by_class_iter = itertools.cycle(by_class.iteritems()) + else: + self.by_class = None + def select_near_vessel(self, vessel, category): """Select vessels in an expanding area surround the vessel""" if game.time > self._select_timeout: @@ -167,8 +190,8 @@ size = self._last_select_size = self._last_select_size*2 or self.initial_select_size size += vessel.collision_radius * 2 rect = pygame.Rect(0, 0, size, size) - rect.center = vessel.rect.center - self.select_rect(category, rect) + rect.center = vector.to_tuple(vessel.position) + self.select_map_rect(category, rect) def constrain_by_class(self): """Constrain an existing selection to a single vessel class. Repeated @@ -182,12 +205,11 @@ self.selected.add(vessel) def set_target(self, target): - """Set the target for all selected vessels. If the target is a planet - or a friendly ship, it is also set as the objective for all + """Set the target and objective for all selected vessels. """ for vessel in self.selected: vessel.control.set_target(target) - if isinstance(target, staticbody.Planet) or vessel.is_friendly(target): - vessel.control.objective.add(target) + vessel.control.objective.add(target) group = GroupSelector() # Singleton + Modified: vessel.py =================================================================== --- vessel.py 2007-10-30 07:25:40 UTC (rev 317) +++ vessel.py 2007-11-01 22:01:50 UTC (rev 318) @@ -145,7 +145,8 @@ 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, ai=None, cost=None, config_file=None, net_id=None): + standoff_distance=0, race=None, ai=None, cost=None, config_file=None, net_id=None, + incidental=False): if image_name is None and vessel_class: image_name = vessel_class + '.png' if image_name is not None: @@ -177,6 +178,7 @@ self._repair_time = None self.damage_time = 0 self.race = race + self.incidental = str(incidental) not in ('0', 'False') @classmethod def load(cls, config_file, **kw): @@ -527,7 +529,7 @@ 5 """ self.damage_time = game.time - if self.vessel_type != 'missile': + if not self.incidental: self.show_status() for s in self._damage_sys: value = s.damage(value) @@ -551,7 +553,7 @@ self.system_damage -= self.system_damage_threshold if game.local_player is self: media.play_sound('power_down.wav', min_spacing=2) - if self.damage_smoke is None and self.vessel_type != 'missile': + if self.damage_smoke is None and not self.incidental: self.damage_smoke = particle.SmokeTrail(self) if (self.hull_damage > self.disable_factor * self.hull_mass * 0.75 and self.explosion is None): Modified: vessels/naree/gnat =================================================================== --- vessels/naree/gnat 2007-10-30 07:25:40 UTC (rev 317) +++ vessels/naree/gnat 2007-11-01 22:01:50 UTC (rev 318) @@ -8,6 +8,7 @@ hull_length: 2 max_speed: 300 max_energy: 50 +incidental: True ai: GnatAI [vessel.Shield] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-11-09 09:14:17
|
Revision: 326 http://eos-game.svn.sourceforge.net/eos-game/?rev=326&view=rev Author: cduncan Date: 2007-11-09 01:14:15 -0800 (Fri, 09 Nov 2007) Log Message: ----------- Changes to lotus fullerene cannon: - New alpha blended eye-candy - Fullerene charges in view now, which helps with timing shots and aiming, plus it looks cool. - Fullerene draw energy as it charges rather than when fired - Set min energy higher Modified Paths: -------------- projectile.py vessels/naree/lotus Modified: projectile.py =================================================================== --- projectile.py 2007-11-08 22:47:41 UTC (rev 325) +++ projectile.py 2007-11-09 09:14:15 UTC (rev 326) @@ -43,24 +43,29 @@ self.max_charge_time = float(max_charge_time) self.efficiency = float(efficiency) self.charge_start = None - self.charge = 0 + self.shot = None self.gunmount = gunmount + self.offset = self.gunmount.collision_radius * 1.6 def update_system(self): - if (self.firing and (self.charge_start is None - 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.fps, self.max_charge) - elif self.charge_start and self.charge >= self.min_charge: - desired_energy = (self.charge+1)**2 / self.efficiency - energy = self.gunmount.use_energy(desired_energy, partial=True) - if energy: - Fullerene(self.charge * energy / desired_energy, self.gunmount) - self.charge = 0 - elif not self.firing: - self.charge = 0 - self.charge_start = None + if self.firing and self.shot is None: + self.charge_start = game.time + self.shot = Fullerene(self.gunmount) + if self.shot is not None: + self.shot.set_position(self.gunmount.position + + vector.unit(self.gunmount.heading) * self.offset) + if self.firing: + old_charge_cost = (self.shot.charge + 1)**2 / self.efficiency + new_charge = min(self.shot.charge + self.charge_rate / game.fps, self.max_charge) + new_charge_cost = (new_charge + 1)**2 / self.efficiency + if self.gunmount.use_energy(new_charge_cost - old_charge_cost): + self.shot.charge = new_charge + elif self.shot.charge >= self.min_charge: + self.shot.launch() + self.shot = None + else: # Not charged enough to fire + self.shot.kill() + self.shot = None @property def targeted(self): @@ -72,14 +77,13 @@ or not target.category & everything & ~self.gunmount.category): return False target_dist = vector.distance(target.position, self.gunmount.position) - if self.firing: - if (self.charge >= self.max_charge / 1.5 + if self.shot is not None: + if (self.shot.charge >= self.max_charge / 1.5 and self.gunmount.bearing(target) < (diagonal / 4.0) and target_dist < Fullerene.range * 2): return False else: - return (game.time - self.charge_start < self.max_charge_time * 1000 - and self.gunmount.energy > 150) + return True else: return target_dist < Fullerene.range * 1.25 @@ -89,32 +93,34 @@ speed = 600 range = 600 base_damage = 1.0 - particles = 4 - rpm = 80 + particles = 3 + rpm = 250 + growth = 1.5 + max_shadows = 2 exploding = False layer = sprite.layers.effects + exploding = False - def __init__(self, charge, gunmount, net_id=None): - self.mass = charge - self.radius = max(charge, 1) + def __init__(self, gunmount, net_id=None): + self.radius = 1 if gunmount is None: RoundBody.__init__(self, net_id=net_id) else: - RoundBody.__init__( - self, gunmount.position, - vector.unit(gunmount.heading) * self.speed + gunmount.velocity, - collides_with=everything & ~gunmount.category, - net_id=net_id) - self.charge = charge + RoundBody.__init__(self, gunmount.position, net_id=net_id) + self.gunmount = gunmount + self.charge = 0 self.distance = 0 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.fps * 60) self.rot = 0 self.step = range(self.particles) self.colormax = 255 - media.play_sound('fullerene.wav', position=self.position) + self.image = pygame.Surface((31, 31)) + self.image.fill((0,0,0)) + self.image.set_colorkey((0,0,0)) + self.rect = self.image.get_rect() + self.shadows = [] def get_state(self): state = RoundBody.get_state(self) @@ -129,40 +135,63 @@ self.position = vector.vector2(*self.body.getPosition()[:2]) self.velocity = vector.vector2(*self.body.getLinearVel()[:2]) self.heading = self.get_heading() - # place self relative to the camera at proper zoom level + self.radius = max(self.charge, 1) + self.partsize = max(self.charge * .45, 1) self.apparent_size, screen_pos = vector.to_screen(self.position) + self.rect.center = vector.to_tuple(screen_pos) - self.distance += self.distance_per_frame - if self.range - self.distance < self.distance_per_frame * 10: - self.colormax = int(self.colormax * 0.85) - if self.distance >= self.range: - self.kill() + if self.velocity: + self.distance += self.distance_per_frame + if self.range - self.distance < self.distance_per_frame * 10: + self.colormax = int(self.colormax * 0.85) + if self.distance >= self.range: + self.kill() + if len(self.shadows) > self.max_shadows: + self.shadows = self.shadows[1:] + self.shadows = [ + pygame.transform.rotozoom(image, 0, self.growth) + for image in self.shadows + [self.image]] + rect = self.image.get_rect() + self.image.fill((0,0,0)) + if not self.exploding: + partsize = self.partsize * self.apparent_size + bg = random.randint(0, self.colormax) + self.image.fill( + (self.colormax, bg, self.colormax-bg), (rect.center, (partsize, partsize))) + if partsize >= 1: + for i in self.step: + direction_x, direction_y = vector.to_tuple(vector.unit(self.rot)) + part = (rect.centerx + direction_x * partsize, + rect.centery + direction_y * partsize) + bg = random.randint(0, self.colormax) + pygame.draw.circle( + self.image, (self.colormax, bg, self.colormax-bg), part, partsize) + self.rot += self.partangle + self.rot += self.rot_per_frame + else: + if self.exploding > self.max_shadows + 2: + self.kill() + self.exploding += 1 + def launch(self): + self.set_velocity(vector.unit(self.gunmount.heading) * self.speed + self.gunmount.velocity) + self.setup_collision(body.nothing, everything & ~self.gunmount.category) + media.play_sound('fullerene.wav', position=self.position) + def draw(self, surface): - partsize = self.partsize * self.apparent_size - radius = self.charge / 2 * self.apparent_size - bg = random.randint(0, self.colormax) - surface.fill((self.colormax, bg, self.colormax-bg), - (self.rect.center, (partsize, partsize))) - if radius >= 2: - rects = [self.rect] - for i in self.step: - 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) - surface.fill((self.colormax, bg, self.colormax-bg) , part) - self.rot += self.partangle - self.rot += self.rot_per_frame - return Rect(rects[0]).unionall(rects) - else: - return self.rect + shade = 7 + self.exploding * 3 + for shadow in self.shadows: + shade = shade * 11 / 6 + shadow.set_colorkey(shadow.get_at((0, 0))) + shadow.set_alpha(shade) + surface.blit(shadow, shadow.get_rect(center=self.rect.center)) + surface.blit(self.image, self.image.get_rect(center=self.rect.center)) + return self.rect def collide(self, other, contacts): - if self.alive(): - self.kill() + if not self.exploding: + self.exploding = 1 + self.set_velocity(other.velocity) other.damage((self.charge + self.base_damage)**2) if other.explosion is None: media.play_sound('hit.wav', volume=(self.charge/5), position=self.position) Modified: vessels/naree/lotus =================================================================== --- vessels/naree/lotus 2007-11-08 22:47:41 UTC (rev 325) +++ vessels/naree/lotus 2007-11-09 09:14:15 UTC (rev 326) @@ -30,7 +30,7 @@ [projectile.FullereneCannon] charge_rate: 12 -min_charge: 1.5 +min_charge: 4 max_charge: 11 max_charge_time: 4 efficiency: 0.95 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-02-01 09:23:34
|
Revision: 69 http://eos-game.svn.sourceforge.net/eos-game/?rev=69&view=rev Author: cduncan Date: 2007-02-01 01:23:31 -0800 (Thu, 01 Feb 2007) Log Message: ----------- - Add generic beam weapon and install on cress and gnat. It's autofire for now until I teach the ai how to shoot - The baddies attack now, so be prepared to fight or run - Some experimental proximity support piggy-backed on the existing collision detection. Doesn't do much yet, still a wip - collision callback prints a traceback on exception for easier debugging (ode eats exceptions in there) - Parameterize collision bitmaps and add a separate method for setting up collsion detection for bodies. Modified Paths: -------------- ai.py body.py game.py projectile.py vessel.py vessels/naree/cress vessels/naree/gnat vessels/naree/lotus Added Paths: ----------- beam.py Modified: ai.py =================================================================== --- ai.py 2007-01-31 07:04:02 UTC (rev 68) +++ ai.py 2007-02-01 09:23:31 UTC (rev 69) @@ -19,7 +19,8 @@ def __init__(self, vessel, target): self.vessel = vessel # override vessel collision handler with our own - self.vessel.collide = self.collide + #self.vessel.collide = self.collide + self.vessel.proximity = self.proximity # cache some vessel stats self.target = target self.close_vessels = [] @@ -40,7 +41,7 @@ """ return (target_position - self.vessel.position).clamp(self.vessel.max_speed) - def seek(self, standoff=200): + def seek(self, standoff=100): """Return desired velocity towards our target >>> game.init() @@ -210,7 +211,7 @@ # shooting self.weapons[0] = False - def collide(self, other, contacts): + def proximity(self, other): """Detect contact with other vessels to avoid stacking >>> game.init() @@ -218,10 +219,10 @@ >>> ai.close_vessels [] >>> v = Vessel() - >>> ai.collide(v, []) + >>> ai.proximity(v) >>> ai.close_vessels == [v] True - >>> ai.collide(object(), []) + >>> ai.proximity(object()) >>> ai.close_vessels == [v] True """ @@ -234,9 +235,8 @@ """Vessel under ai control""" def __init__(self, target=None, **kw): - self.category = body.foe - self.collides_with = body.everything Vessel.__init__(self, **kw) + self.setup_collision(body.foe, body.everything & ~body.shot) self.control = AIControl(self, target) Added: beam.py =================================================================== --- beam.py (rev 0) +++ beam.py 2007-02-01 09:23:31 UTC (rev 69) @@ -0,0 +1,63 @@ +## Eos, Dawn of Light -- A Space Opera +## Copyright (c) 2007 Casey Duncan and contributors +## See LICENSE.txt for licensing details + +# Beam weapons +# $Id$ + +import random +import ode +import game +import pygame +import body +import vessel +from vector import Vector2D + +_empty_rect = pygame.rect.Rect(0, 0, 0, 0) + + +class BeamWeapon(vessel.System, pygame.sprite.Sprite): + + def __init__(self, gunmount, range, damage, width=1, color='255,255,255'): + pygame.sprite.Sprite.__init__(self, game.sprites) + self.gunmount = gunmount + self.range = float(range) + self.length = self.range + self.beam_damage = float(damage) + self.width = int(width) + self.color = tuple(int(c) for c in color.split(',')) + assert len(self.color) == 3, 'Invalid color value "%s"' % color + # Create a ray geom to determine beam collision + self.ray = ode.GeomRay(game.collision_space, self.range) + self.ray.setCategoryBits(body.nothing) + self.ray.setCollideBits(body.everything & ~gunmount.category) + self.ray.parent = self + self.fire = False + + def update_system(self): + self.ray.set(self.gunmount.position, Vector2D.unit(self.gunmount.heading)) + self.rect = pygame.rect.Rect(self.ray.getAABB()[:4]) + + def draw(self, surface): + if self.fire: + 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 + dx *= self.length * camera.zoom + dy *= self.length * camera.zoom + self.fire = False + c = random.randint(1, 4) + return pygame.draw.line(surface, (self.color[0]/c, self.color[1]/c, self.color[2]/c), + (sx, sy), (sx + dx, sy + dy), self.width) + else: + return _empty_rect + + def proximity(self, other): + pass + + def collide(self, other, contact): + self.length = max(contact[0].getContactGeomParams()[2], 30) + other.damage(self.beam_damage / game.avg_fps) + self.fire = True + Property changes on: beam.py ___________________________________________________________________ Name: svn:keywords + Id Name: svn:eol-style + native Modified: body.py =================================================================== --- body.py 2007-01-31 07:04:02 UTC (rev 68) +++ body.py 2007-02-01 09:23:31 UTC (rev 69) @@ -18,6 +18,7 @@ # intersects the collide bitmap of the other. friend = 1 foe = 2 +shot = 4 nothing = 0 everything = 0x7fffffff categories = (friend, foe) @@ -29,17 +30,21 @@ mass = 0.1 # Mass in tons radius = 0.1 # radius in meters - category = nothing # category for collision interaction - collides_with = nothing # category bitmap this body collides with + category = None # category for collision interaction + collides_with = None # category bitmap this body collides with + body = None # ode dynamics body geom = None # ode collision geom - def __init__(self, position=None, velocity=None): + 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 initial velocity may be specified if omitted the object is stationary. + + category -- category bitmap this body belongs to + collides_with = category bitmap this body collides with """ pygame.sprite.Sprite.__init__(self, game.sprites) self.body = ode.Body(game.universe) @@ -51,19 +56,27 @@ self.body.setPosition((position[0], position[1], 0)) if velocity is not None: self.body.setLinearVel((velocity[0], velocity[1], 0)) - if self.category or self.collides_with: - # Add a geom for this body to the collision space - self.geom = ode.GeomSphere(game.collision_space, self.radius) - self.geom.setCategoryBits(self.category) - self.geom.setCollideBits(self.collides_with) - self.geom.setBody(self.body) # Move with the ode body - self.geom.parent = self # attach ourself for callback purposes + 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 self.rect = pygame.Rect([self.position.x, self.position.y, 0, 0]) + 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) + """ + if self.geom is None: + self.geom = ode.GeomSphere(game.collision_space, self.radius) + self.geom.setBody(self.body) # Move with the ode body + self.geom.parent = self # attach ourself for callback purposes + self.geom.setCategoryBits(category) + self.geom.setCollideBits(collides_with) + self.category = category + self.collides_with = collides_with + @property def force(self): return Vector2D(*self.body.getForce()[:2]) @@ -122,3 +135,10 @@ """ # Subclasses to provide specific behavior pass + + def proximity(self, other): + """Called when this object is close to another but not necessarily + colliding. + """ + # Subclasses to provide specific behavior + pass Modified: game.py =================================================================== --- game.py 2007-01-31 07:04:02 UTC (rev 68) +++ game.py 2007-02-01 09:23:31 UTC (rev 69) @@ -6,6 +6,7 @@ # $Id$ # $Date$ +import math import random import ode import pygame @@ -30,6 +31,7 @@ ai = None def init(fullscreen=False): + import body from sprite import RenderedGroup from stars import StarField from staticbody import Planet @@ -61,13 +63,18 @@ sprites = RenderedGroup() StarField(screen.get_rect()) Planet('earth-east.png', 0, 0) - camera = player = Vessel.load(random.choice(['vessels/naree/lotus', 'vessels/naree/cress'])) + camera = player = Vessel.load(random.choice(['vessels/naree/lotus'])) + player.setup_collision(body.friend, body.nothing) player.control = KeyboardControl() camera.rect.center = screen.get_rect().center camera.zoom = 1 + for i in range(5): + ship = random.choice(['vessels/naree/cress', 'vessels/naree/lotus']) + ai = AIVessel.load(ship, target=player) + ai.position.x, ai.position.y = random.randint(-1000, 1000), random.randint(-1000,1000) for i in range(10): - ship = random.choice(['vessels/naree/lotus', 'vessels/naree/cress', 'vessels/naree/gnat']) - AIVessel.load(ship, target=player) + ai = AIVessel.load('vessels/naree/gnat', target=player) + ai.position.x, ai.position.y = random.randint(-1000, 1000), random.randint(-1000,1000) def handle_events(): for event in pygame.event.get(): @@ -81,14 +88,31 @@ correct collision business logic on the affected objects for further processing """ - contacts = ode.collide(geom1, geom2) - if contacts: - # objects collided, first lookup the eos Body objects - # then dispatch the local collision handlers - eos_body1 = geom1.parent - eos_body2 = geom2.parent - eos_body1.collide(eos_body2, contacts) - eos_body2.collide(eos_body1, contacts) + try: + contacts = ode.collide(geom1, geom2) + if contacts: + # objects collided, first lookup the eos Body objects + # then dispatch the local collision handlers + eos_body1 = geom1.parent + eos_body2 = geom2.parent + eos_body1.proximity(eos_body2) + eos_body2.proximity(eos_body1) + eos_body1.collide(eos_body2, contacts) + eos_body2.collide(eos_body1, contacts) + elif geom1.placeable() and geom2.placeable(): + # Check for close proximity + x1, y1, ignored = geom1.getPosition() + x2, y2, ignored = geom2.getPosition() + dist2 = (x1 - x2)**2 + (y1 - y2)**2 + if dist2 < (geom1.getRadius() + 100)**2: + geom1.parent.proximity(geom2.parent) + if dist2 < (geom2.getRadius() + 100)**2: + geom2.parent.proximity(geom1.parent) + except: + # Output traceback hidden by ode + import traceback + traceback.print_exc() + raise def play(): global universe, collision_space, screen, background, sprites Modified: projectile.py =================================================================== --- projectile.py 2007-01-31 07:04:02 UTC (rev 68) +++ projectile.py 2007-02-01 09:23:31 UTC (rev 69) @@ -65,12 +65,12 @@ def __init__(self, charge, gunmount): self.mass = charge - self.category = gunmount.category - self.collides_with = everything & ~gunmount.category self.radius = max(charge, 1) RoundBody.__init__( self, gunmount.position, - Vector2D.unit(gunmount.heading) * self.speed + gunmount.velocity) + Vector2D.unit(gunmount.heading) * self.speed + gunmount.velocity, + collides_with=everything & ~gunmount.category + ) self.charge = charge self.distance = 0 self.distance_per_frame = self.speed / game.avg_fps Modified: vessel.py =================================================================== --- vessel.py 2007-01-31 07:04:02 UTC (rev 68) +++ vessel.py 2007-02-01 09:23:31 UTC (rev 69) @@ -686,6 +686,7 @@ # Imports for vessel config use import projectile +import beam if __name__ == '__main__': """Run tests if executed directly""" Modified: vessels/naree/cress =================================================================== --- vessels/naree/cress 2007-01-31 07:04:02 UTC (rev 68) +++ vessels/naree/cress 2007-02-01 09:23:31 UTC (rev 69) @@ -23,5 +23,8 @@ [vessel.Engine] thrust: 80 -#[beam.BeamCannon] -#range=250 +[beam.BeamWeapon] +range=200 +damage=40 +width=2 +color: 100, 255, 100 Modified: vessels/naree/gnat =================================================================== --- vessels/naree/gnat 2007-01-31 07:04:02 UTC (rev 68) +++ vessels/naree/gnat 2007-02-01 09:23:31 UTC (rev 69) @@ -21,3 +21,8 @@ [vessel.Engine] thrust: 30 + +[beam.BeamWeapon] +range: 130 +damage: 15 +color: 255, 80, 120 Modified: vessels/naree/lotus =================================================================== --- vessels/naree/lotus 2007-01-31 07:04:02 UTC (rev 68) +++ vessels/naree/lotus 2007-02-01 09:23:31 UTC (rev 69) @@ -1,4 +1,4 @@ -# $id$ +# $Id$ [general] vessel_class: lotus This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-02-10 17:22:16
|
Revision: 98 http://eos-game.svn.sourceforge.net/eos-game/?rev=98&view=rev Author: cduncan Date: 2007-02-10 09:22:08 -0800 (Sat, 10 Feb 2007) Log Message: ----------- - Beam weapons have mass based on weapon damage rating - Make sure beam weapons can't fire when disabled, i.e., during/after ship destruction. - Increase damage of cress particle beam a bit Modified Paths: -------------- beam.py vessels/naree/cress Modified: beam.py =================================================================== --- beam.py 2007-02-10 17:05:55 UTC (rev 97) +++ beam.py 2007-02-10 17:22:08 UTC (rev 98) @@ -18,12 +18,15 @@ class BeamWeapon(vessel.Weapon, pygame.sprite.Sprite): + mass_factor = 0.1 # Mass per unit damage + def __init__(self, gunmount, range, damage, width=1, color='255,255,255'): pygame.sprite.Sprite.__init__(self, game.sprites) self.gunmount = gunmount self.range = float(range) self.length = self.range self.beam_damage = float(damage) + self.mass = self.beam_damage * self.mass_factor self.width = int(width) self.color = tuple(int(c) for c in color.split(',')) assert len(self.color) == 3, 'Invalid color value "%s"' % color @@ -35,10 +38,9 @@ def update_system(self): self.ray.set(self.gunmount.position, Vector2D.unit(self.gunmount.heading)) - self.rect = pygame.rect.Rect(self.ray.getAABB()[:4]) def draw(self, surface): - if self.firing: + 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 @@ -47,10 +49,12 @@ dy *= self.length * camera.zoom self.targeted = False c = netsync.randint(1, 4) - return pygame.draw.line(surface, (self.color[0]/c, self.color[1]/c, self.color[2]/c), + self.rect = pygame.draw.line(surface, + (self.color[0]/c, self.color[1]/c, self.color[2]/c), (sx, sy), (sx + dx, sy + dy), self.width) else: - return _empty_rect + self.rect = _empty_rect + return self.rect def proximity(self, other): pass @@ -58,6 +62,6 @@ def collide(self, other, contact): self.length = max(contact[0].getContactGeomParams()[2], 30) self.targeted = True - if self.firing: + if self.firing and self.enabled: other.damage(self.beam_damage / game.avg_fps) Modified: vessels/naree/cress =================================================================== --- vessels/naree/cress 2007-02-10 17:05:55 UTC (rev 97) +++ vessels/naree/cress 2007-02-10 17:22:08 UTC (rev 98) @@ -25,6 +25,6 @@ [beam.BeamWeapon] range=200 -damage=40 +damage=45 width=2 color: 100, 255, 100 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-02-22 08:52:20
|
Revision: 116 http://eos-game.svn.sourceforge.net/eos-game/?rev=116&view=rev Author: cduncan Date: 2007-02-22 00:52:18 -0800 (Thu, 22 Feb 2007) Log Message: ----------- - Greatly improve ai pursuit algorithm, use it now by default - Friendly ais now track their close neighbors and try to keep a buffer between them. This uses the standard collision detection with a bit of extra tracking. - ai use fw thrust as well as main engines (but still no boost) - Off-screen pointies now properly identify friend-or-foe - Increase lotus turn rate, turn thrust and double the fullerene charge rate to give us humans a fighting chance. - Increase gnat top speed, the improved ai makes them much more effective (and annoying ;^) - When the player dies, switch the camera to a random remaining (ai) ship. This isn't perfect but it's an improvement - Release ai waves a little less often - Don't use pygame.display.flip() for now. This is only useful for HW surfaces, which the Mac never seems to grant you, and slows things down a lot on PPC machines. Modified Paths: -------------- ai.py bay.py body.py game.py vessel.py vessels/naree/gnat vessels/naree/lotus Modified: ai.py =================================================================== --- ai.py 2007-02-18 19:07:41 UTC (rev 115) +++ ai.py 2007-02-22 08:52:18 UTC (rev 116) @@ -30,11 +30,13 @@ self.mothership = sprite.GroupSingle() if mothership: self.mothership.add(mothership) - self.close_vessels = [] + self.close_vessels = sprite.Group() + self.proximity_radius = self.vessel.radius * 2 self.sensor = sensor self.steerfunc = netsync.random.choice([self.seek, self.pursue]) + self.steerfunc = self.pursue - def seek_position(self, target_position): + def seek_position(self, target_position, predict_ahead=1.0): """Return desired velocity towards a fixed position >>> game.init() @@ -47,9 +49,10 @@ >>> vel.y 0.0 """ - return (target_position - self.vessel.position).clamp(self.vessel.max_speed) + return ((target_position - (self.vessel.position + self.vessel.velocity * predict_ahead) + ) * game.avg_fps).clamp(self.vessel.max_speed) - def seek(self, standoff=100): + def seek(self, standoff=150, predict_ahead=1.0): """Return desired velocity towards our target >>> game.init() @@ -70,8 +73,9 @@ position = self.vessel.position target_position = self.target.sprite.position target_velocity = self.target.sprite.velocity - approach = (target_position + target_velocity) - (position + velocity) - new_heading = approach.radians #(target_position - self.position).radians + approach = (target_position + target_velocity * predict_ahead) - ( + position + velocity * predict_ahead) + new_heading = approach.radians if not standoff or approach.length > standoff: if velocity.length and approach.length / velocity.length < game.avg_fps: return new_heading, approach.clamp(velocity.length) @@ -102,10 +106,14 @@ return (heading + halfcircle) % fullcircle, -velocity - def predict_intercept(self, target): + def predict_intercept(self, target, predict_ahead=2): """Return predicted position of target at the time when we should intercept them + predict_ahead -- Number of seconds to predict the position ahead when + determining the time and distance to target. Low values encourage + "undershoot" behavior, high values "overshoot" + >>> game.init() >>> target = Vessel() >>> ai = AIControl(Vessel(), target) @@ -114,13 +122,20 @@ >>> target.position = Vector2D(150, 0) >>> target.velocity = Vector2D(20, 0) >>> pos = ai.predict_intercept(target) - >>> pos.x - 180.0 - >>> pos.y - 0.0 + >>> int(pos.x) + 180 + >>> int(pos.y) + 0 """ if target.velocity: - T = self.vessel.position.distance(target.position) / self.vessel.max_speed + predict_ahead *= (self.vessel.velocity.distance(target.velocity) / + (self.vessel.position.distance(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) return target.position + (target.velocity * T) else: return target.position @@ -175,11 +190,18 @@ """Return a vector away from other nearby vessels to avoid stacking up """ center = Vector2D() - if self.close_vessels: - # Compute the center vector amongst our vessels and avoid it - for vessel in self.close_vessels: + too_close = [] + # 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) + if proximity < self.proximity_radius: center += vessel.position - center /= len(self.close_vessels) + too_close.append(vessel) + elif proximity > self.proximity_radius * 2: + # Other vessel is not considered "close" anymore + self.close_vessels.remove(vessel) + if too_close: + center /= len(too_close) return (self.vessel.position - center).normal * self.vessel.velocity.length else: return center @@ -203,9 +225,11 @@ # not turning at the full rate. The latter prevents "orbiting" # targets and make the ship movement look more "natural" # overshoots tend to correct themselves better this way - if self.vessel.velocity != desired_velocity and abs(turn_rate) < max_turn_rate: + if self.vessel.velocity != desired_velocity: thrust_dir = (desired_velocity - self.vessel.velocity).radians - self.vessel.heading self.thrust = thrust_dir < diagonal or thrust_dir > fullcircle - diagonal + self.fw_maneuver = not self.thrust and ( + thrust_dir < rightangle or thrust_dir > fullcircle - rightangle) self.right_maneuver = halfcircle > thrust_dir + diagonal > rightangle self.bw_maneuver = halfcircle + rightangle > thrust_dir + diagonal > halfcircle self.left_maneuver = fullcircle > thrust_dir + diagonal > halfcircle + rightangle @@ -239,7 +263,6 @@ desired_heading, desired_velocity = self.steerfunc() desired_velocity += self.avoid_vessels() self.steer(desired_heading, desired_velocity) - self.close_vessels = [] # Reset for next frame # Fire all targeted weapons for i in range(len(self.vessel.weapons)): self.weapons[i] = self.vessel.weapons[i].targeted @@ -249,19 +272,19 @@ >>> game.init() >>> ai = AIControl(Vessel(), None) - >>> ai.close_vessels - [] + >>> len(ai.close_vessels) + 0 >>> v = Vessel() >>> ai.collide(v, []) - >>> ai.close_vessels == [v] - True + >>> len(ai.close_vessels) + 1 >>> ai.collide(object(), []) - >>> ai.close_vessels == [v] - True + >>> len(ai.close_vessels) + 1 """ if isinstance(other, Vessel): # Keep track of other vessels were are stacked on - self.close_vessels.append(other) + self.close_vessels.add(other) class AIVessel(Vessel): Modified: bay.py =================================================================== --- bay.py 2007-02-18 19:07:41 UTC (rev 115) +++ bay.py 2007-02-22 08:52:18 UTC (rev 116) @@ -47,7 +47,6 @@ 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.offscreen_img = media.RotatedImage('pointy-green.gif') fighter.update() self.last_launch = game.time Modified: body.py =================================================================== --- body.py 2007-02-18 19:07:41 UTC (rev 115) +++ body.py 2007-02-22 08:52:18 UTC (rev 116) @@ -94,6 +94,13 @@ self.geom.setCollideBits(collides_with) self.category = category self.collides_with = collides_with + if game.local_player and self.category & game.local_player.category: + pointy = 'pointy-green.gif' + elif self.category: + pointy = 'pointy-red.gif' + else: + pointy = 'pointy-blue.gif' + self.offscreen_img = media.RotatedImage(pointy) @property def force(self): @@ -150,7 +157,7 @@ camera_rect = game.camera.rect camera_pos = game.camera.position distance = self.position.distance(camera_pos) - if distance < 10000: + if distance < 10000 and self.position != camera_pos: opacity = 170 - (distance - camera_rect.centerx) / 10000 * 120 angle = (self.position - camera_pos).radians image = self.offscreen_img.rotated(angle) Modified: game.py =================================================================== --- game.py 2007-02-18 19:07:41 UTC (rev 115) +++ game.py 2007-02-22 08:52:18 UTC (rev 116) @@ -10,10 +10,11 @@ import ode import pygame from pygame.locals import * +import vector fps = 32 # Desired fps avg_fps = fps # Running average of actual fps -ai_interval = 60 # Release AI every N seconds +ai_interval = 70 # Release AI every N seconds universe = None # ode world collision_space = None # ode collision space screen = None # pygame screen @@ -29,6 +30,7 @@ time = 0 # milliseconds since game began frame_no = 0 # current frame number players = None +local_player = None camera = None def init(run_server=False, game_id=None, host='127.0.0.1', port='11211', @@ -40,8 +42,8 @@ from staticbody import Planet from vessel import Vessel, KeyboardControl from media import load_image - global universe, collision_space, screen, screen_rect, background - global sprites, planets, fps, avg_fps, clock, time, players, camera, ai + global universe, collision_space, screen, screen_rect, background, planets + global sprites, fps, avg_fps, clock, time, players, local_player, camera, ai # Initialize pygame and setup main screen pygame.init() # Get the available display modes and use the best one @@ -78,8 +80,8 @@ Planet('earth-east.png', 0, 0) # Establish networking - players, this_player = netsync.init(run_server, game_id, host, port, player_count) - camera = this_player + players, local_player = netsync.init(run_server, game_id, host, port, player_count) + camera = local_player camera.rect.center = screen.get_rect().center camera.zoom = 1 @@ -125,15 +127,16 @@ sprites.update() dirty = sprites.draw(screen) pygame.display.update(dirty) - pygame.display.flip() + #pygame.display.flip() universe.quickStep(1.0 / fps) # target FPS time += clock.tick(fps) avg_fps = clock.get_fps() or fps netsync.step(frame_no) # retrieve late, after clock.tick throttle - if (frame_no % (fps * ai_interval)) == 0: + if (frame_no % int(fps * ai_interval)) == 0: target = netsync.random.choice(players) - x = target.position.x + netsync.random.randint(-1000, 1000) - y = target.position.y + netsync.random.randint(-1000, 1000) + position = vector.Vector2D.unit(netsync.random.random() * vector.fullcircle) * 1000 + x = target.position.x + position.x + y = target.position.y + position.y for i in xrange(1): ai = AIVessel.load('vessels/naree/lotus', target=target) ai.position.x, ai.position.y = x, y Modified: vessel.py =================================================================== --- vessel.py 2007-02-18 19:07:41 UTC (rev 115) +++ vessel.py 2007-02-22 08:52:18 UTC (rev 116) @@ -106,7 +106,6 @@ else: image_name = 'ship.png' self.vessel_img = RotatedImage(image_name) - self.offscreen_img = RotatedImage('pointy-red.gif') img_rect = self.vessel_img.get_rect() self.radius = max(img_rect.width, img_rect.height) / 2 body.RoundBody.__init__(self) @@ -360,6 +359,14 @@ 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): + game.camera = sprite + game.camera.zoom = 1 + sprite.off_screen = False + sprite.rect.center = game.screen_rect.center + break def __str__(self): return '<%s@%x "%s" at (%d, %d)>' % ( Modified: vessels/naree/gnat =================================================================== --- vessels/naree/gnat 2007-02-18 19:07:41 UTC (rev 115) +++ vessels/naree/gnat 2007-02-22 08:52:18 UTC (rev 116) @@ -6,7 +6,7 @@ description: The Naree can ill afford to squander their pilots in delicate stub fighters, nor can they afford to waste resources on crude "fire and forget" weapons. The gnat was designed as an alternative to both, a reuseable weapon effective in both defense and offense. The gnat is a self contained autonomous unmanned craft wielding a laser cannon. Gnats are launched by a system resembling a cross between a figher bay and a torpedo tube that can be carried by medium-sized warships. Launched in groups the gnats can de directed to swarm hostile targets inflicting damage while using their agility and advanced flight control to avoid being destroyed. Once their energy is depleted, they are automatically programmed to return to their host ship to recharge. In defensive mode, gnats act as mobile point-defense weapons, staying near their host and intercepting incoming threats. hull_mass: 1 hull_length: 2 -max_speed: 700 +max_speed: 800 [vessel.Shield] max_level: 40 Modified: vessels/naree/lotus =================================================================== --- vessels/naree/lotus 2007-02-18 19:07:41 UTC (rev 115) +++ vessels/naree/lotus 2007-02-22 08:52:18 UTC (rev 116) @@ -18,8 +18,8 @@ # dissipation: 10 [vessel.DirectionalThrusters] -thrust: 100 -max_turn_rate: 2.3 +thrust: 160 +max_turn_rate: 2.5 [vessel.ManeuveringThrusters] thrust: 90 @@ -28,8 +28,8 @@ thrust: 150 [projectile.FullereneCannon] -charge_rate: 5 -min_charge: 0.25 +charge_rate: 10 +min_charge: 0.4 max_charge: 9 max_charge_time: 4 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-03-15 23:07:09
|
Revision: 136 http://eos-game.svn.sourceforge.net/eos-game/?rev=136&view=rev Author: cduncan Date: 2007-03-15 16:07:06 -0700 (Thu, 15 Mar 2007) Log Message: ----------- More fun: - Your posse now grows more rapidly and not only when an opponent warship appears - phase disruptors disrupt less per hit, but for much longer. - Beef up cress damage for balance - Bombs/gnats now launched only at enemy vessels - Tone down ai predict-ahead just a hair to prevent premature "turn-around" when attacking - AI now targets directly at close range rather than using approach angle. Note close range varies depending on the size of the opponent Modified Paths: -------------- ai.py bay.py body.py game.py projectile.py vessel.py vessels/naree/cress Modified: ai.py =================================================================== --- ai.py 2007-03-15 08:32:37 UTC (rev 135) +++ ai.py 2007-03-15 23:07:06 UTC (rev 136) @@ -131,7 +131,7 @@ else: return target.position - def pursue(self, predict_ahead=1.5): + def pursue(self, predict_ahead=1.4): """Return desired velocity towards where we predict our target to be @@ -158,13 +158,18 @@ position.distance(target_position) < self.target.sprite.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 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) - ( position + velocity * predict_ahead) - new_heading = approach.radians - return new_heading, approach * game.avg_fps + if position.distance(target_position) < self.target.sprite.radius * 6: + heading = (target_position - position).radians + else: + heading = approach.radians + return heading, approach * game.avg_fps def evade(self): """Return desired velocity away from where we predict @@ -258,7 +263,7 @@ else: # No mothership, just head toward a planet self.target.add(game.planets) - elif game.time - self.vessel.damage_time < 1500: + elif (self.vessel.health < 0.5 and game.time - self.vessel.damage_time < 2000): self.steerfunc = self.evade elif (self.vessel.health > 0.66 or self.target.sprite.position.distance(self.vessel.position) > 350): Modified: bay.py =================================================================== --- bay.py 2007-03-15 08:32:37 UTC (rev 135) +++ bay.py 2007-03-15 23:07:06 UTC (rev 136) @@ -54,12 +54,11 @@ @property def targeted(self): - target = self.vessel.control.target - if len(self.fighters) < self.capacity and target: - distance = self.vessel.position.distance(target.sprite.position) - return distance < self.launch_distance - else: + target = self.vessel.control.target.sprite + 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) class BombBay(vessel.Weapon): @@ -86,6 +85,8 @@ @property def targeted(self): - target = self.vessel.control.target - return (target and abs(self.vessel.bearing(target.sprite)) < diagonal - and self.vessel.position.distance(target.sprite.position) <= self.range) + target = self.vessel.control.target.sprite + if (not target 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: body.py =================================================================== --- body.py 2007-03-15 08:32:37 UTC (rev 135) +++ body.py 2007-03-15 23:07:06 UTC (rev 136) @@ -33,8 +33,8 @@ mass = 0.1 # Mass in tons radius = 0.1 # radius in meters - category = None # category for collision interaction - collides_with = None # category bitmap this body collides with + category = nothing # category for collision interaction + collides_with = nothing # category bitmap this body collides with body = None # ode dynamics body geom = None # ode collision geom Modified: game.py =================================================================== --- game.py 2007-03-15 08:32:37 UTC (rev 135) +++ game.py 2007-03-15 23:07:06 UTC (rev 136) @@ -150,13 +150,16 @@ ai = AIVessel.load('vessels/naree/lotus') ai.position.x, ai.position.y = x, y enemies.add(ai) - if netsync.random.random() * frame_no < 1000: + barrier = 300 + while netsync.random.random() * frame_no > barrier: + if netsync.random.random() * frame_no < barrier * 3: ship = 'vessels/rone/drach' 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 + barrier *= 2 barrier = 200 while 1: if warship and netsync.random.random() * frame_no < barrier: Modified: projectile.py =================================================================== --- projectile.py 2007-03-15 08:32:37 UTC (rev 135) +++ projectile.py 2007-03-15 23:07:06 UTC (rev 136) @@ -315,7 +315,7 @@ if self.alive(): other.damage(self.damage_value) if hasattr(other, 'shield'): - other.shield.disrupt(0.5) + other.shield.disrupt(0.75) class FlakBomb(Shot): @@ -325,7 +325,7 @@ shard_velocity = 500 shard_range = 100 shard_damage = 3.0 - min_time = 0.5 + min_time = 0.6 def __init__(self, shooter, bomb_bay, velocity, max_ttl): bomb_img = media.RotatedImage('flak-bomb.png') Modified: vessel.py =================================================================== --- vessel.py 2007-03-15 08:32:37 UTC (rev 135) +++ vessel.py 2007-03-15 23:07:06 UTC (rev 136) @@ -492,6 +492,8 @@ mass = 0 priority = 0 # Shields get hit first + regen_recovery = 5.0 # Time regeneration recovers after disruption + # Cosmetic opacity = 60 # 0-255 flicker = 12 # variance in opacity @@ -549,7 +551,8 @@ if self.regeneration < self.max_regeneration: # Recover regeneration rate if disrupted self.regeneration = min( - self.regeneration + (self.max_regeneration / game.avg_fps), self.max_regeneration) + self.regeneration + (self.max_regeneration / (game.avg_fps * self.regen_recovery)), + self.max_regeneration) def draw(self, surface): if self.time >= game.time and self.level: Modified: vessels/naree/cress =================================================================== --- vessels/naree/cress 2007-03-15 08:32:37 UTC (rev 135) +++ vessels/naree/cress 2007-03-15 23:07:06 UTC (rev 136) @@ -25,6 +25,6 @@ [beam.BeamWeapon] range=200 -damage=45 +damage=55 width=2 color: 100, 255, 100 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-08-29 00:56:13
|
Revision: 241 http://eos-game.svn.sourceforge.net/eos-game/?rev=241&view=rev Author: cduncan Date: 2007-08-28 17:56:05 -0700 (Tue, 28 Aug 2007) Log Message: ----------- - Increase charge rate and max charge of lotus fullerene cannon - Gnats no longer show on minimap to reduce noise in large battle groups Modified Paths: -------------- panel.py vessels/naree/lotus Modified: panel.py =================================================================== --- panel.py 2007-08-07 07:17:31 UTC (rev 240) +++ panel.py 2007-08-29 00:56:05 UTC (rev 241) @@ -197,14 +197,16 @@ self.map_mask.fill((0, 0, 0, 0)) def update_vessel(self, vessel): - if vessel.is_friendly(game.local_player): - color = (0, 255, 0) - else: - color = (255, 0, 0) - pos_x, pos_y = vector.to_tuple(vessel.position * self.map_scale) - point_rect = pygame.Rect(pos_x + self.map_rect.centerx, pos_y + self.map_rect.centery, 2, 2) - self.map_points.fill(color, point_rect) - self.map_mask.fill((0, 0, 0, 120), point_rect.inflate(4, 4)) + if vessel.vessel_type != 'missile': + if vessel.is_friendly(game.local_player): + color = (0, 255, 0) + else: + color = (255, 0, 0) + pos_x, pos_y = vector.to_tuple(vessel.position * self.map_scale) + point_rect = pygame.Rect( + pos_x + self.map_rect.centerx, pos_y + self.map_rect.centery, 2, 2) + self.map_points.fill(color, point_rect) + self.map_mask.fill((0, 0, 0, 120), point_rect.inflate(4, 4)) def draw(self, surface): surface.blit(self.image, self.rect) Modified: vessels/naree/lotus =================================================================== --- vessels/naree/lotus 2007-08-07 07:17:31 UTC (rev 240) +++ vessels/naree/lotus 2007-08-29 00:56:05 UTC (rev 241) @@ -29,9 +29,9 @@ thrust: 75 [projectile.FullereneCannon] -charge_rate: 10 -min_charge: 0.4 -max_charge: 9 +charge_rate: 12 +min_charge: 1.5 +max_charge: 11 max_charge_time: 4 efficiency: 0.95 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <cd...@us...> - 2007-11-07 08:22:35
|
Revision: 323 http://eos-game.svn.sourceforge.net/eos-game/?rev=323&view=rev Author: cduncan Date: 2007-11-07 00:22:34 -0800 (Wed, 07 Nov 2007) Log Message: ----------- Add a custom hit-and-run ai personality for the cress so it takes better advantage of its maneuverability and doesn't just hang out in the line of fire and get killed. Modified Paths: -------------- ai.py vessels/naree/cress Modified: ai.py =================================================================== --- ai.py 2007-11-07 08:17:21 UTC (rev 322) +++ ai.py 2007-11-07 08:22:34 UTC (rev 323) @@ -387,8 +387,8 @@ class AggroAI(BasicAI): - evade_min_health = 0.5 - evade_max_health = 0.7 + evade_min_health = 0.4 + evade_max_health = 0.5 def evade(self): """Head through the target""" @@ -408,6 +408,16 @@ self.steerfunc = self.standoff +class HitAndRun(BasicAI): + + evade_min_health = 0.7 + evade_max_health = 0.75 + evade_damage_timeout = 1200 + evade_max_distance = 250 + + evade = BasicAI.flee + + class AssaultAI(BasicAI): def select_steerfunc(self): Modified: vessels/naree/cress =================================================================== --- vessels/naree/cress 2007-11-07 08:17:21 UTC (rev 322) +++ vessels/naree/cress 2007-11-07 08:22:34 UTC (rev 323) @@ -11,7 +11,7 @@ max_energy: 200 standoff_distance: 125 cost: 125 -ai: Standoffish +ai: HitAndRun [vessel.Shield] max_level: 100 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |