view atc_plane.py @ 45:c93c3c910c85

Updated headers/copyrights ATC is now licensed under GPLv2 only
author Josef "Jeff" Sipek <jeffpc@josefsipek.net>
date Fri, 01 Jun 2007 02:04:37 -0400
parents 05245904f997
children
line wrap: on
line source

#/*
# * ATC - Air Traffic Controller simulation game
# *
# * Copyright (C) 2004-2007 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
# *
# * This program is free software; you can redistribute it and/or modify
# * it under the terms of the GNU General Public License version 2 as
# * published by the Free Software Foundation.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# * GNU General Public License for more details.
# */

import os
import sys
import time
import pygame
from math import *

import atc_config

import atc_colors
import atc_utils
from atc_plane_specs import *
from atc_maps import *

mperpix		= MAPS[0].mperpix
mperdeg		= 1852.0*60.0		# == 1852 m/min * 60 min/deg

def m2pix(m):
	""" Meters to pixels conversion fn """
	return m/mperpix

def pix2m(pix):
	""" Pixels to meters conversion fn """
	return pix*mperpix
	
def pix2geo(pix):
	return pix2m(pix)/mperdeg

plane_OK	= 0
plane_RANGE	= 1
plane_CRASHED	= 2
plane_STALL	= 3
plane_DEAD	= 4
plane_PROXY	= 5

class Plane(pygame.sprite.Sprite):
	""" Class to manage one plane's motion """
	def __init__(self,type,callsign,flightno,squawk="1200",pos=(0.0, 0.0, 0.0),vel=(0.0, 0.0, 0.0)):
		""" Set up everything """
		pygame.sprite.Sprite.__init__(self)
		
		# screen
		screen = pygame.display.get_surface()
		self.area = screen.get_rect()
		
		# plane image
		(self.image, self.rect) = atc_utils.load_png('data/image/plane.png')
		
		# call sign
		self.callsign	= callsign
		
		# flight number
		self.flightno	= flightno
		
		# squawk code (shoud be octal number digits 0-7 only)
		self.squawk = squawk
		
		# position (X, Y, Z)
		self.pos	= { \
			"X":pos[0], \
			"Y":pos[1], \
			"Z":pos[2]}
		
		# target position
		self.targetPos	= { \
			"X":None, \
			"Y":None, \
			"Z":None}
		
		# velocity
		self.vel	= { \
			"i":vel[0], \
			"j":vel[1], \
			"k":vel[2], \
			
			"heading":0, \
			"speed":0, \
			"climb":0}
		self.complete_vel(ijk=1)
		
		self.timer = 0 # this is timeout the next time we try to recalculate position
		
		# plane specs
		self.specs = None
		for p in plane_SPECS:
			if p['name'] == type:
				self.specs= p
				break
		
		if self.specs == None:
			self.specs = plane_SPECS[0]
		
		# status flag
		self.status	= plane_OK
	
	def complete_vel(self, ijk=None, hac=None):
		""" Recalculate all the velocity variables """
		if ijk == None and hac == None:
			return

		if ijk == None:
			self.vel["i"] = sin(atc_utils.torad(self.vel["heading"])) * sqrt(self.vel["speed"]**2 - self.vel["climb"]**2)
			self.vel["j"] = cos(atc_utils.torad(self.vel["heading"])) * sqrt(self.vel["speed"]**2 - self.vel["climb"]**2)
			self.vel["k"] = self.vel["climb"]
		else:
			self.vel["heading"] = self.calc_head()
			self.vel["speed"] = sqrt(self.vel["i"]**2 + self.vel["j"]**2 + self.vel["k"]**2)
			self.vel["climb"] = self.vel["k"]
	
	def update(self):
		""" Move the plane, and check for collisions """
		if (self.timer > time.time()):
			return

		if (self.status != plane_OK):
			return
		
		self.pos["X"] += self.vel["i"]/10.0
		self.pos["Y"] += self.vel["j"]/10.0
		self.pos["Z"] += self.vel["k"]/10.0
		
		# FIXME: need physics
		if (self.targetPos["Z"] != None) and (self.pos["Z"] >= (self.targetPos["Z"] - 25)) and (self.pos["Z"] <= (self.targetPos["Z"] + 25)):
			self.vel["climb"] = 0
			self.targetPos["Z"] = None
			
			self.complete_vel(hac=1)
		
		#if (self.pos["X"] < 0) or (self.pos["Y"] < 0): # FIXME: max values
		#	self.status = plane_RANGE
		
		if (self.pos["Z"] < 0):
			self.vel["i"] = self.vel["j"] = self.vel["k"] = 0
			self.status = plane_CRASHED
		
		if (self.vel["speed"]<m2pix(self.specs["stall_speed"])) and (not self.status==plane_CRASHED):
			self.status = plane_STALL
		
		self.rect.move(m2pix(self.vel["i"])/10.0,m2pix(self.vel["j"])/10.0)
		
		self.timer = time.time() + m2pix(self.vel["speed"])**-1/10.0

	def calc_head(self):
		""" Calculate heading of the airplane (in degrees)"""
		if (self.vel["i"] == self.vel["j"] == 0): # no movement
			return 0
	
		try:
			head = fabs(atan(self.vel["i"]/self.vel["j"]))	# yes this is "backwards"
		except ZeroDivisionError:
			if (self.vel["i"]>0):
				return 90        		# east
			return 270               		# west
		
		head = abs(head*180/pi)
		if self.vel["i"]>0 and self.vel["j"]>0:		# quad I
			return head
		if self.vel["i"]<0 and self.vel["j"]>0:		# quad II
			return 360-head
		if self.vel["i"]<0 and self.vel["j"]<0:		# quad III
			return 270-head
		if self.vel["i"]>0 and self.vel["j"]<0:		# quad IV
			return 180-head
	
		if self.vel["i"]==0 and self.vel["j"]>0:	# north
			return 0
		return 180					# south
	
	def calc_geo(self, xy, prefix):
		""" Return a geographic coodrinate-formated xy """
		df = m2pix(pix2geo(xy))
		minus = prefix[0]
		if df<0:
			df *= -1
			minus = prefix[1]
		d  = floor(df)
		mf = (df-d) * 60.0
		m  = floor(mf)
		s  = (mf-m) * 60.0
		
		return "%s %dd %02dm %02ds" % (minus, int(d), int(m), int(s))
	
	def process(self,cmd):
		""" Process a user command """
		
		# FIXME: convert multiple spaces to single
		parts = cmd.split(' ')
		
		if (parts[0] == "ALT"):
			print "Changing altitude to " + parts[1] + "m"
			self.targetPos["Z"] = int(parts[1])
			self.vel["climb"] = self.specs["climb_normal"]
			if (self.pos["Z"] > self.targetPos["Z"]):
				self.vel["climb"] *= -1
			self.complete_vel(hac=1)
			
		if (parts[0] == "HEAD"):
			self.vel["heading"] = int(parts[1])
			while(True):
				if self.vel["heading"]>=360:
					self.vel["heading"] -= 360
				elif self.vel["heading"]<0:
					self.vel["heading"] += 360
				else:
					break
			
			print "Changing heading to " + str(self.vel["heading"])
			self.complete_vel(hac=1)
		
		if (parts[0] == "SQUAWK"):
			self.squawk = parts[1]
	
	def display(self,screen):
		""" Put everything onto the screen """
		rotimage = pygame.transform.rotate(self.image, 360.0-self.vel["heading"])
		screen.blit(rotimage, (int(1024/2+m2pix(self.pos["X"])), int(768/2-m2pix(self.pos["Y"]))))
		
		# Plane info
		font = pygame.font.Font(None, 16)
		
		color = atc_colors.planeinfo_ok
		if (self.status == plane_CRASHED) or (self.status == plane_DEAD):
			color = atc_colors.planeinfo_crashed
		
		if (self.squawk == "7700") or \
		   (self.squawk == "7600") or \
		   (self.squawk == "7500") or \
		   (self.squawk == "0000"):
			color = atc_colors.planeinfo_emergency
		
		x = int(1024/2+m2pix(self.pos["X"])) + 10
		y = int(768/2-m2pix(self.pos["Y"])) - 5
		
		if (atc_config.plane_label == 2):
			strings = (	self.flightno,
					self.callsign + " " + str(self.status),
					"Alt:  " + str(int(self.pos["Z"])),
					"Lat:  " + self.calc_geo(self.pos["Y"],"NS"),
					"Long: " + self.calc_geo(self.pos["X"],"EW"),
					"Head: " + str(int(self.vel["heading"])),
					"AS:   " + str(int(self.vel["speed"]*3.6)))
		else:
			strings = (	self.flightno,
					"%03d %03d" % (int(round(self.vel["heading"])), int(round(self.vel["speed"]*3.6))),
					self.specs["name"])
				
		for stri in strings:
			alt = font.render(stri, 1, color)
			altpos = alt.get_rect()
			altpos.topleft = (x,y)
			screen.blit(alt, altpos)
			(x,y) = (x,y+10)