Commits

Andrew Peterson committed 9ad3818

Initial conversion to using an Equipment Database. Constant Equipment Data is now shared between instantiations of Equipment. The data is loaded from a psuedo-csv file that can be easily modified in the future.

Comments (0)

Files changed (4)

++engine.id,name,rank,size,thrust
+
++radar.id,name,rank,size,range
+1001,"Standard Radar",1,100,400
+
++comm.id,name,rank,size,range,limit
+1002,"Standard Comm",1,100,1000,256
+1003,"Mega Comm",2,500,10000,512
+
++weapon.id,name,rank,size,dmg,rate,range
+1004,"Stardard Laser",1,100,5,5.0,200
+1005,"Mega Laser",2,500,20,5.0,400
+
++shield.id,name,rank,size,maxPower,regen
+1006,"Mega Shield",2,200,15,5
+
++tool.id,name,rank,size,power,range
+1007,"Mining Laser",1,100,10,200
+
++factory.id,name,rank,size
+1008,"Ship Factory",1,1000

pyshipcommand/equipment.py

 
 # !!!!! Everything in this file should be player script safe !!!!!!!
 
+from zope.interface import implements
 from djlib import primitives
+from djlib.utils import IManager
 
 from logging import getLogger
 log = getLogger('pyshipcommand.equipment')
 
 
 class Type(object):
-	# Base Equipment
-	ENGINE = "engine"
-	RADAR = "radar"
-	COMM = "comm"
-
-	# Auxilary
-	WEAPON = "weapon"
-	SHIELD = "shield"
-	TOOL = "tool"
-	FACTORY = "factory"
+    INVALID = ""
+
+    # Base Equipment
+    ENGINE = "engine"
+    RADAR = "radar"
+    COMM = "comm"
+
+    # Auxilary
+    WEAPON = "weapon"
+    SHIELD = "shield"
+    TOOL = "tool"
+    FACTORY = "factory"
 #end Type
 
+# Equipment Data
+# Share Database class for equipment constants
+class EquipmentData(object):
+    def __init__(self, type_, **more_data):
+        # methods don't need to be _protected as EquipmentData
+        # should itself be _protected within the equipment.
+        self.type = type_
+        #self.id = id_
+        #self.name = name
+        #self.rank = rank
+        #self.size = size
+        self.__dict__.update(more_data)
+#end EquipmentData
 
 class Equipment(object):
-	def __init__(self, type_, name, rank = 1, size = 100):
-		self._type = type_
-		self._name = name
-		self._rank = rank
-		self._size = size
-		self._ship = None
 
-	def getType(self):
-		return self._type
+    TYPE = Type.INVALID
+    REQ_DATA = {'type', 'id', 'name', 'rank', 'size'}
 
-	def getName(self):
-		return self._name
+    @classmethod
+    def validateData(cls, equip_data):
+        missing = cls.REQ_DATA - equip_data.__dict__.viewkeys()
+        if len(missing):
+            log.error("Equipment %s missing data %s", cls.TYPE, str(missing))
+        if equip_data.type != cls.TYPE:
+            log.error("Equipment %s is not of expected type %s", equip_data.type, cls.TYPE)
+            return False
+        return len(missing) == 0
 
-	def getRank(self):
-		return self._rank
+    def __init__(self, equip_data):
+        assert self.validateData(equip_data), "Equipment Data is invalid"
+        self._data = equip_data
+        self._ship = None
 
-	def getSize(self):
-		return self._size
+    def getType(self):
+        return self._data.type
 
-	def _install(self, ship):
-		self._ship = ship
+    def getName(self):
+        return self._data.name
 
-	def _update(self, dt):
-		pass
+    def getRank(self):
+        return self._data.rank
+
+    def getSize(self):
+        return self._data.size
+
+    def _install(self, ship):
+        self._ship = ship
+
+    def _update(self, dt):
+        pass
 #end Equipment
 
 
 
 
 class Engine(BaseEquipment):
-    def __init__(self, name, rank, thrust):
-    	Equipment.__init__(self, Type.ENGINE, name, rank)
-    	self._thrust = thrust
+
+    TYPE = Type.ENGINE
+    REQ_DATA = Equipment.REQ_DATA | {'thrust'}
+
+    def __init__(self, equip_data):
+        Equipment.__init__(self, Type.ENGINE, equip_data)
 
     def getThrust(self):
-    	return self._thrust
+        return self._data.thrust
 #end Engine
 
 
 class Radar(BaseEquipment):
 
-	class Entity(primitives.Entity):
-		def __init__(self, type_, id_, pos_, radius_):
-			primitives.Entity.__init__(self, pos_)
-			self.type = type_
-			self.id = id_
-			self.radius = radius_
-	#end Radar.Entity
+    TYPE = Type.RADAR
+    REQ_DATA = Equipment.REQ_DATA | {'range'}
 
-	def __init__(self, name, rank, range_):
-		Equipment.__init__(self, Type.RADAR, name, rank)
-		self._range = range_
+    class Entity(primitives.Entity):
+        def __init__(self, type_, id_, pos_, radius_):
+            primitives.Entity.__init__(self, pos_)
+            self.type = type_
+            self.id = id_
+            self.radius = radius_
+    #end Radar.Entity
 
-	def getRange(self):
-		return self._range
+    def __init__(self, equip_data):
+        Equipment.__init__(self, equip_data)
 
-	def detectEntities(self, type_ = "all", dist = 0):
-		#Set or cap radar distance
-		if dist == 0 or dist > self._range:
-			dist = self._range
+    def getRange(self):
+        return self._data.range
+
+    def detectEntities(self, type_ = "all", dist = 0):
+        #Set or cap radar distance
+        if dist == 0 or dist > self.getRange():
+            dist = self.getRange()
 
-		# Convert default
-		if type_ == "all":
-			type_ = None
+        # Convert default
+        if type_ == "all":
+            type_ = None
 
-		entities = self._ship.getEntityInRange(dist, type_)
-		return [Radar.Entity(entity.__class__.__name__, entity.id, entity.getPosition(), entity.radius) for entity in entities]
+        entities = self._ship.getEntityInRange(dist, type_)
+        return [Radar.Entity(entity.__class__.__name__, entity.id, entity.getPosition(), entity.radius) for entity in entities]
 #end Radar
 
 
 class Communication(BaseEquipment):
-	def __init__(self, name, rank, range_, limit):
-		Equipment.__init__(self, Type.COMM, name, rank)
-		self._range = range_
-		self._limit = limit
 
-	def getRange(self):
-		return self._range
+    TYPE = Type.COMM
+    REQ_DATA = Equipment.REQ_DATA | {'range', 'limit'}
+
+    def __init__(self, equip_data):
+        Equipment.__init__(self, equip_data)
+
+    def getRange(self):
+        return self._data.range
 
-	def send(self, target_id, msg):
-		if len(msg) > self._limit:
-			return False
-		return self._ship.sendMessage(target_id, self._range, msg)
+    def send(self, target_id, msg):
+        if len(msg) > self._data.limit:
+            return False
+        return self._ship.sendMessage(target_id, self.getRange(), msg)
 
-	def queryMessages(self):
-		return self._ship._getMessages()
+    def queryMessages(self):
+        return self._ship._getMessages()
 #end Communication
 
 
 class Weapon(BaseEquipment):
-    def __init__(self, name, rank, dmg, rate, range_):
-    	Equipment.__init__(self, Type.WEAPON, name, rank)
-    	self._range = range_
-    	self._rate = rate
-    	self._dmg = dmg
-    	self._target = None
-    	self._reload = 0
+
+    TYPE = Type.WEAPON
+    REQ_DATA = Equipment.REQ_DATA | {'dmg', 'rate', 'range'}
+
+    def __init__(self, equip_data):
+        Equipment.__init__(self, equip_data)
+        self._target = None
+        self._reload = 0
 
     def getDamage(self):
-    	return self._dmg
+        return self._data.dmg
 
     def getRate(self):
-    	return self._rate
+        return self._data.rate
 
     def getRange(self):
-    	return self._range
+        return self._data.range
 
     def setTarget(self, target_id):
-    	if hasattr(target_id, "id"):
-    		target_id = target_id.id
-    	self._target = target_id
+        if hasattr(target_id, "id"):
+            target_id = target_id.id
+        self._target = target_id
 
     def getTarget(self):
-    	return self._target
+        return self._target
 
     def canShoot(self):
-    	return self._reload <= 0.0
+        return self._reload <= 0.0
 
     def shoot(self, target_id = None):
-    	if not self.canShoot():
-    		return False
+        if not self.canShoot():
+            return False
 
-    	if not target_id:
-    		target_id = self._target
+        if not target_id:
+            target_id = self._target
 
-    	if target_id:
-    		self._reload = self._rate
-    		return self._ship.shoot(target_id, self._dmg, self._range)
-    	return False
+        if target_id:
+            self._reload = self.getRate()
+            return self._ship.shoot(target_id, self.getDamage(), self.getRange())
+        return False
 
     def _update(self, dt):
-    	self._reload = (self._reload - dt if self._reload > 0.0 else 0.0)
+        self._reload = (self._reload - dt if self._reload > 0.0 else 0.0)
 #end Weapon
 
 
 
 
 class Shield(AuxEquipment):
-    def __init__(self, name, rank, power, regen):
-    	Equipment.__init__(self, Type.SHIELD, name, rank)
-    	self._maxPower = power
-    	self._power = power
-    	self._regen = regen
-    	self._hits = []
+
+    TYPE = Type.SHIELD
+    REQ_DATA = Equipment.REQ_DATA | {'maxPower', 'regen'}
+
+    def __init__(self, equip_data):
+        Equipment.__init__(self, equip_data)
+        self._power = self.getMaxPower()
+        self._hits = []
 
     def getPower(self):
-    	return self._power
+        return self._power
 
     def getMaxPower(self):
-    	return self._maxPower
+        return self._data.maxPower
+
+    def getRegen(self):
+        return self._data.regen
 
     def atMax(self):
-    	return self._power == self._maxPower
+        return self._power == self.getMaxPower()
 
     def isDown(self):
-    	return self._power == 0
+        return self._power == 0
 
     def isHit(self):
-    	return len(self._hits) > 0
+        return len(self._hits) > 0
 
     def reportHits(self):
-    	hits = self._hits
-    	self._hits = []
-    	return hits
+        hits = self._hits
+        self._hits = []
+        return hits
 
     def _update(self, dt):
-    	self._power += self._regen
-    	if self._power > self._maxPower:
-    		self._power = self._maxPower
+        self._power += self.getRegen()
+        if self._power > self.getMaxPower():
+            self._power = self.getMaxPower()
 
-    	#Clear any past hits
-    	#self._hits = []
+        #Clear any past hits
+        #self._hits = []
 
     def _hit(self, _dir, dmg):
-    	actual_dmg = 0
-    	self._power -= dmg
-    	if self._power < 0:
-    		actual_dmg = -self._power
-    		self._power = 0
-    	self._hits.append(_dir)
-    	return actual_dmg
+        actual_dmg = 0
+        self._power -= dmg
+        if self._power < 0:
+            actual_dmg = -self._power
+            self._power = 0
+        self._hits.append(_dir)
+        return actual_dmg
 
 #end Shield
 
 class MiningLaser(AuxEquipment):
-    def __init__(self, name, rank, power, range_):
-    	Equipment.__init__(self, Type.TOOL, name, rank)
-    	self._range = range_
-    	self._power = power
-    	self._target = None
-    	self._reload = True
+
+    TYPE = Type.TOOL
+    REQ_DATA = Equipment.REQ_DATA | {'power', 'range'}
+
+    def __init__(self, equip_data):
+        Equipment.__init__(self, equip_data)
+        self._target = None
+        self._reload = True
 
     def getPower(self):
-    	return self._power
+        return self._data.power
 
     def getRange(self):
-    	return self._range
+        return self._data.range
 
     def setTarget(self, target_id):
-    	if hasattr(target_id, "id"):
-    		target_id = target_id.id
-    	self._target = target_id
+        if hasattr(target_id, "id"):
+            target_id = target_id.id
+        self._target = target_id
 
     def getTarget(self):
-    	return self._target
+        return self._target
 
     def canMine(self):
-    	return self._reload
+        return self._reload
 
     def mine(self, target_id = None):
-    	if not self.canMine():
-    		return False
+        if not self.canMine():
+            return False
 
-    	if not target_id:
-    		target_id = self._target
+        if not target_id:
+            target_id = self._target
 
-    	if target_id:
-    		sm = self._ship._getMgr()
-    		return sm.mineEntity(target_id, self._ship.id, self._power)
-    	return False
+        if target_id:
+            sm = self._ship._getMgr()
+            return sm.mineEntity(target_id, self._ship.id, self.getPower())
+        return False
 
     def _update(self, dt):
-    	self._reload = True
+        self._reload = True
 #end MiningLaser
 
 
 class ShipFactory(AuxEquipment):
-	from target import TargetPosition
+    from target import TargetPosition
+
+    TYPE = Type.FACTORY
+
+    def __init__(self, equip_data):
+        Equipment.__init__(self, equip_data)
+        self._cvector = primitives.Vector(-100, 100)
+        self._attrs = None
 
-	def __init__(self, ship_attrs):
-		Equipment.__init__(self, Type.FACTORY, "Ship Factory", 1, 1000)
-		self._cvector = primitives.Vector(-100, 100)
-		self._attrs = ship_attrs
+    def setShipTemplate(self, attrs):
+        self._attrs = attrs
 
-	def constructShip(self, script_name):
+    def constructShip(self, script_name):
+        if not self._attrs:
+            return None
 
-		sm = self._ship._getMgr()
-		ship_id, ship = sm.createShip(self._ship.owner, self._attrs)
+        sm = self._ship._getMgr()
+        ship_id, ship = sm.createShip(self._ship.owner, self._attrs)
 
-		ship.setPosition(self._ship.pos.copy())
-		ship.setTarget(self.TargetPosition(ship.pos + self._cvector))
-		ship.setActiveScript(script_name)
+        ship.setPosition(self._ship.pos.copy())
+        ship.setTarget(self.TargetPosition(ship.pos + self._cvector))
+        ship.setActiveScript(script_name)
 
-		return ship_id
+        return ship_id
 #end ShipFactory
+
+
+class EquipmentManager(object):
+    implements(IManager)
+
+    EQUIP_BY_TYPE = {Type.ENGINE : Engine,
+                     Type.RADAR : Radar,
+                     Type.COMM : Communication,
+                     Type.WEAPON : Weapon,
+                     Type.SHIELD : Shield,
+                     Type.TOOL : MiningLaser,
+                     Type.FACTORY : ShipFactory}
+
+    def __init__(self):
+        self.equip_by_id = {}
+        self.equip_by_name = {}
+
+    def loadDatabase(self, equip_file):
+        from csv import DictReader
+
+        with open(equip_file, 'rb') as equip_db:
+
+            curr_type = None
+            equip_keys = []
+            num_total = 0
+            num_type = 0
+
+            for line in equip_db:
+                line = line.strip()
+                if len(line) == 0:
+                    continue
+                log.debug(line)
+
+                # look for custom CSV format line
+                #+<equip_type>.<comma, seperated, keys>
+                if line[0] == '+':
+                    if curr_type:
+                        log.info("Loaded %d of Type <%s>", num_type, curr_type)
+                        num_type = 0
+
+                    curr_type, csv_fields = line[1:].split('.')
+                    equip_keys = csv_fields.split(',')
+                    log.info("Type.%s %s", curr_type, str(equip_keys))
+                    continue
+
+                data = self._readEquipData(line, equip_keys)
+                if data:
+                    log.debug(str(data))
+
+                    ed = EquipmentData(curr_type, **data)
+                    self.equip_by_id[ed.id] = ed
+                    self.equip_by_name[ed.name] = ed
+
+                    num_type += 1
+                    num_total += 1
+
+            log.info("==== Loaded %d Total Equipment ====", num_total)
+
+    def createEquipByName(self, name):
+        ed = self.equip_by_name.get(name)
+        if not ed:
+            return None
+        return self._createEquip(ed)
+
+    def createEquipById(self, id_):
+        ed = self.equip_by_id.get(id_)
+        if not ed:
+            return None
+        return self._createEquip(ed)
+
+    def _createEquip(self, ed):
+        cls = self.EQUIP_BY_TYPE.get(ed.type)
+        if not cls:
+            return None
+        return cls(ed)
+
+    def _readEquipData(self, line, equip_keys):
+        vals = line.split(',')
+        if len(vals) != len(equip_keys):
+            log.error("Invalid Equipment Data: %s", line)
+            return None
+
+        equip_dict = {}
+        for key, val in zip(equip_keys, vals):
+            # attempt to convert int/floats
+            try:
+                val = int(val)
+            except ValueError:
+                try:
+                    val = float(val)
+                except ValueError:
+                    val = val.strip('"')
+
+            equip_dict[key] = val
+
+        return equip_dict
+
+
+#end EquipmentManager

pyshipcommand/server.py

 from players import PlayerManager
 from universe import Universe
 from ship import ShipMgr
+from equipment import EquipmentManager
 from djlib.utils import IManager, IGame, IdAllocator, IAllocator
 
 from djlib.logger import getLogger, log_method, add_log_args, set_log_options
         self.player_mgr = PlayerManager()
         self.script_mgr = ScriptMgr()
         self.ship_mgr = ShipMgr()
+        self.equip_mgr = EquipmentManager()
         self.id_gen = IdAllocator(SERVER_INDEX_MOD)
         self.accounts = None
         self.tick = 0
         gsm.registerUtility(self.player_mgr, IManager, "Player")
         gsm.registerUtility(self.script_mgr, IManager, "Script")
         gsm.registerUtility(self.ship_mgr, IManager, "Ship")
+        gsm.registerUtility(self.equip_mgr, IManager, "Equipment")
 
         self.universe.theBigBang()
         self.player_mgr.loadPlayers("players.dat")
         self.script_mgr.loadAllScripts()
         #self.universe.loadWorld("")
 
+        self.equip_mgr.loadDatabase("equip_db.dat")
+
         # Initialize Twisted authentication service
         p = portal.Portal(self.player_mgr)
         self.accounts = checkers.InMemoryUsernamePasswordDatabaseDontUse()

pyshipcommand/ship.py

         if len(self.aux_equip) >= self.attrs.aux_equip_slots:
             return False
 
-        self.aux_equip[equip._type] = equip
+        self.aux_equip[equip.getType()] = equip
         equip._install(self)
         return True
 
         if not isinstance(equip, equipment.BaseEquipment):
             raise TypeError()
 
-        self.std_equip[equip._type] = equip
+        self.std_equip[equip.getType()] = equip
         equip._install(self)
 
     def isAlive(self):
         player_ships.append(ship)
 
         # install standard equipment
-        ship.upgradeEquipment(equipment.Communication("Standard Comm", 1, 1000, 256))
-        ship.upgradeEquipment(equipment.Radar("Standard Radar", 1, 400))
-        ship.upgradeEquipment(equipment.Weapon("Stardard Laser", 1, 5, 5.0, 200))
-        ship.addAuxEquipment(equipment.MiningLaser("Mining Laser", 1, 10, 200))
-        
+        #ship.upgradeEquipment(equipment.Communication("Standard Comm", 1, 1000, 256))
+        #ship.upgradeEquipment(equipment.Radar("Standard Radar", 1, 400))
+        #ship.upgradeEquipment(equipment.Weapon("Stardard Laser", 1, 5, 5.0, 200))
+        #ship.addAuxEquipment(equipment.MiningLaser("Mining Laser", 1, 10, 200))
+        em = getUtility(IManager, "Equipment")
+        ship.upgradeEquipment(em.createEquipByName("Standard Comm"))
+        ship.upgradeEquipment(em.createEquipByName("Standard Radar"))
+        ship.upgradeEquipment(em.createEquipByName("Stardard Laser"))
+        ship.addAuxEquipment(em.createEquipByName("Mining Laser"))
 
         return (ship_id, ship)
 
 
         self.ships_by_id[ship_id] = mship
         player_ships.append(mship)
-
         mship.setPosition(pos)
-        mship.upgradeEquipment(equipment.Communication("Mega Comm", 2, 10000, 512))
-        mship.upgradeEquipment(equipment.Weapon("Mega Laser", 2, 20, 5.0, 400))
-        mship.addAuxEquipment(equipment.Shield("Mega Shield", 2, 15, 5))
-        mship.addAuxEquipment(equipment.ShipFactory(ShipAttributes()))
+
+        em = getUtility(IManager, "Equipment")
+        #mship.upgradeEquipment(equipment.Communication("Mega Comm", 2, 10000, 512))
+        #mship.upgradeEquipment(equipment.Weapon("Mega Laser", 2, 20, 5.0, 400))
+        #mship.addAuxEquipment(equipment.Shield("Mega Shield", 2, 15, 5))
+        #mship.addAuxEquipment(equipment.ShipFactory(ShipAttributes()))
+        mship.upgradeEquipment(em.createEquipByName("Mega Comm"))
+        mship.upgradeEquipment(em.createEquipByName("Mega Laser"))
+        mship.addAuxEquipment(em.createEquipByName("Mega Shield"))
+        factory = em.createEquipByName("Ship Factory")
+        factory.setShipTemplate(ShipAttributes())
+        mship.addAuxEquipment(factory)
+
         return mship