import weakref import random import bs import bsUtils import bsInternal # list of defined spazzes appearances = {} def getAppearances(includeLocked=False): disallowed = [] if not includeLocked: # hmm yeah this'll be tough to hack... if not bsInternal._getPurchased('characters.santa'): disallowed.append('Santa Claus') if not bsInternal._getPurchased('characters.frosty'): disallowed.append('Frosty') if not bsInternal._getPurchased('characters.bones'): disallowed.append('Bones') if not bsInternal._getPurchased('characters.bernard'): disallowed.append('Bernard') if not bsInternal._getPurchased('characters.pixie'): disallowed.append('Pixel') if not bsInternal._getPurchased('characters.pascal'): disallowed.append('Pascal') if not bsInternal._getPurchased('characters.actionhero'): disallowed.append('Todd McBurton') if not bsInternal._getPurchased('characters.taobaomascot'): disallowed.append('Taobao Mascot') if not bsInternal._getPurchased('characters.agent'): disallowed.append('Agent Johnson') if not bsInternal._getPurchased('characters.jumpsuit'): disallowed.append('Lee') if not bsInternal._getPurchased('characters.assassin'): disallowed.append('Zola') if not bsInternal._getPurchased('characters.wizard'): disallowed.append('Grumbledorf') if not bsInternal._getPurchased('characters.cowboy'): disallowed.append('Butch') if not bsInternal._getPurchased('characters.witch'): disallowed.append('Witch') if not bsInternal._getPurchased('characters.warrior'): disallowed.append('Warrior') if not bsInternal._getPurchased('characters.superhero'): disallowed.append('Middle-Man') if not bsInternal._getPurchased('characters.alien'): disallowed.append('Alien') if not bsInternal._getPurchased('characters.oldlady'): disallowed.append('OldLady') if not bsInternal._getPurchased('characters.gladiator'): disallowed.append('Gladiator') if not bsInternal._getPurchased('characters.wrestler'): disallowed.append('Wrestler') if not bsInternal._getPurchased('characters.operasinger'): disallowed.append('Gretel') if not bsInternal._getPurchased('characters.robot'): disallowed.append('Robot') if not bsInternal._getPurchased('characters.cyborg'): disallowed.append('B-9000') if not bsInternal._getPurchased('characters.bunny'): disallowed.append('Easter Bunny') if not bsInternal._getPurchased('characters.kronk'): disallowed.append('Kronk') if not bsInternal._getPurchased('characters.zoe'): disallowed.append('Zoe') if not bsInternal._getPurchased('characters.jackmorgan'): disallowed.append('Jack Morgan') if not bsInternal._getPurchased('characters.mel'): disallowed.append('Mel') if not bsInternal._getPurchased('characters.snakeshadow'): disallowed.append('Snake Shadow') return [s for s in appearances.keys() if s not in disallowed] gPowerupWearOffTime = 20000 gBasePunchPowerScale = 1.2 # obsolete - just used for demo guy now gBasePunchCooldown = 400 gLameBotColor = (1.2, 0.9, 0.2) gLameBotHighlight = (1.0, 0.5, 0.6) gDefaultBotColor = (0.6, 0.6, 0.6) gDefaultBotHighlight = (0.1, 0.3, 0.1) gProBotColor = (1.0, 0.2, 0.1) gProBotHighlight = (0.6, 0.1, 0.05) gLastTurboSpamWarningTime = -99999 class _PickupMessage(object): 'We wanna pick something up' pass class _PunchHitMessage(object): 'Message saying an object was hit' pass class _CurseExplodeMessage(object): 'We are cursed and should blow up now.' pass class _BombDiedMessage(object): "A bomb has died and thus can be recycled" pass class Appearance(object): """Create and fill out one of these suckers to define a spaz appearance""" def __init__(self, name): self.name = name if appearances.has_key(self.name): raise Exception("spaz appearance name \"" + self.name + "\" already exists.") appearances[self.name] = self self.colorTexture = "" self.headModel = "" self.torsoModel = "" self.pelvisModel = "" self.upperArmModel = "" self.foreArmModel = "" self.handModel = "" self.upperLegModel = "" self.lowerLegModel = "" self.toesModel = "" self.jumpSounds = [] self.attackSounds = [] self.impactSounds = [] self.deathSounds = [] self.pickupSounds = [] self.fallSounds = [] self.style = 'spaz' self.defaultColor = None self.defaultHighlight = None class SpazFactory(object): """ Category: Game Flow Classes Wraps up media and other resources used by bs.Spaz instances. Generally one of these is created per bs.Activity and shared between all spaz instances. Use bs.Spaz.getFactory() to return the shared factory for the current activity. Attributes: impactSoundsMedium A tuple of bs.Sounds for when a bs.Spaz hits something kinda hard. impactSoundsHard A tuple of bs.Sounds for when a bs.Spaz hits something really hard. impactSoundsHarder A tuple of bs.Sounds for when a bs.Spaz hits something really really hard. singlePlayerDeathSound The sound that plays for an 'importan' spaz death such as in co-op games. punchSound A standard punch bs.Sound. punchSoundsStrong A tuple of stronger sounding punch bs.Sounds. punchSoundStronger A really really strong sounding punch bs.Sound. swishSound A punch swish bs.Sound. blockSound A bs.Sound for when an attack is blocked by invincibility. shatterSound A bs.Sound for when a frozen bs.Spaz shatters. splatterSound A bs.Sound for when a bs.Spaz blows up via curse. spazMaterial A bs.Material applied to all of parts of a bs.Spaz. rollerMaterial A bs.Material applied to the invisible roller ball body that a bs.Spaz uses for locomotion. punchMaterial A bs.Material applied to the 'fist' of a bs.Spaz. pickupMaterial A bs.Material applied to the 'grabber' body of a bs.Spaz. curseMaterial A bs.Material applied to a cursed bs.Spaz that triggers an explosion. """ def _preload(self, character): """ Preload media that will be needed for a given character. """ self._getMedia(character) def __init__(self): """ Instantiate a factory object. """ self.impactSoundsMedium = (bs.getSound('impactMedium'), bs.getSound('impactMedium2')) self.impactSoundsHard = (bs.getSound('impactHard'), bs.getSound('impactHard2'), bs.getSound('impactHard3')) self.impactSoundsHarder = (bs.getSound('bigImpact'), bs.getSound('bigImpact2')) self.singlePlayerDeathSound = bs.getSound('playerDeath') self.punchSound = bs.getSound('punch01') self.punchSoundsStrong = (bs.getSound('punchStrong01'), bs.getSound('punchStrong02')) self.punchSoundStronger = bs.getSound('superPunch') self.swishSound = bs.getSound('punchSwish') self.blockSound = bs.getSound('block') self.shatterSound = bs.getSound('shatter') self.splatterSound = bs.getSound('splatter') self.spazMaterial = bs.Material() self.rollerMaterial = bs.Material() self.punchMaterial = bs.Material() self.pickupMaterial = bs.Material() self.curseMaterial = bs.Material() footingMaterial = bs.getSharedObject('footingMaterial') objectMaterial = bs.getSharedObject('objectMaterial') playerMaterial = bs.getSharedObject('playerMaterial') regionMaterial = bs.getSharedObject('regionMaterial') # send footing messages to spazzes so they know when they're on solid # ground. # eww this should really just be built into the spaz node self.rollerMaterial.addActions( conditions=('theyHaveMaterial', footingMaterial), actions=(('message', 'ourNode', 'atConnect', 'footing', 1), ('message', 'ourNode', 'atDisconnect', 'footing', -1))) self.spazMaterial.addActions( conditions=('theyHaveMaterial', footingMaterial), actions=(('message', 'ourNode', 'atConnect', 'footing', 1), ('message', 'ourNode', 'atDisconnect', 'footing', -1))) # punches self.punchMaterial.addActions( conditions=('theyAreDifferentNodeThanUs',), actions=(('modifyPartCollision', 'collide', True), ('modifyPartCollision', 'physical', False), ('message', 'ourNode', 'atConnect', _PunchHitMessage()))) # pickups self.pickupMaterial.addActions( conditions=(('theyAreDifferentNodeThanUs',), 'and', ('theyHaveMaterial', objectMaterial)), actions=(('modifyPartCollision', 'collide', True), ('modifyPartCollision', 'physical', False), ('message', 'ourNode', 'atConnect', _PickupMessage()))) # curse self.curseMaterial.addActions( conditions=(('theyAreDifferentNodeThanUs',), 'and', ('theyHaveMaterial', playerMaterial)), actions=('message', 'ourNode', 'atConnect', _CurseExplodeMessage())) self.footImpactSounds = (bs.getSound('footImpact01'), bs.getSound('footImpact02'), bs.getSound('footImpact03')) self.footSkidSound = bs.getSound('skid01') self.footRollSound = bs.getSound('scamper01') self.rollerMaterial.addActions( conditions=('theyHaveMaterial', footingMaterial), actions=(('impactSound', self.footImpactSounds, 1, 0.2), ('skidSound', self.footSkidSound, 20, 0.3), ('rollSound', self.footRollSound, 20, 3.0))) self.skidSound = bs.getSound('gravelSkid') self.spazMaterial.addActions( conditions=('theyHaveMaterial', footingMaterial), actions=(('impactSound', self.footImpactSounds, 20, 6), ('skidSound', self.skidSound, 2.0, 1), ('rollSound', self.skidSound, 2.0, 1))) self.shieldUpSound = bs.getSound('shieldUp') self.shieldDownSound = bs.getSound('shieldDown') self.shieldHitSound = bs.getSound('shieldHit') # we dont want to collide with stuff we're initially overlapping # (unless its marked with a special region material) self.spazMaterial.addActions( conditions=((('weAreYoungerThan', 51), 'and', ('theyAreDifferentNodeThanUs',)), 'and', ('theyDontHaveMaterial', regionMaterial)), actions=(('modifyNodeCollision', 'collide', False))) self.spazMedia = {} # lets load some basic rules (allows them to be tweaked from the # master server) self.shieldDecayRate = bsInternal._getAccountMiscReadVal('rsdr', 10.0) self.punchCooldown = bsInternal._getAccountMiscReadVal('rpc', 400) self.punchCooldownGloves = \ bsInternal._getAccountMiscReadVal('rpcg', 300) self.punchPowerScale = bsInternal._getAccountMiscReadVal('rpp', 1.2) self.punchPowerScaleGloves = \ bsInternal._getAccountMiscReadVal('rppg', 1.4) self.maxShieldSpilloverDamage = \ bsInternal._getAccountMiscReadVal('rsms', 500) def _getStyle(self, character): return appearances[character].style def _getMedia(self, character): t = appearances[character] if not self.spazMedia.has_key(character): m = self.spazMedia[character] = { 'jumpSounds':[bs.getSound(s) for s in t.jumpSounds], 'attackSounds':[bs.getSound(s) for s in t.attackSounds], 'impactSounds':[bs.getSound(s) for s in t.impactSounds], 'deathSounds':[bs.getSound(s) for s in t.deathSounds], 'pickupSounds':[bs.getSound(s) for s in t.pickupSounds], 'fallSounds':[bs.getSound(s) for s in t.fallSounds], 'colorTexture':bs.getTexture(t.colorTexture), 'colorMaskTexture':bs.getTexture(t.colorMaskTexture), 'headModel':bs.getModel(t.headModel), 'torsoModel':bs.getModel(t.torsoModel), 'pelvisModel':bs.getModel(t.pelvisModel), 'upperArmModel':bs.getModel(t.upperArmModel), 'foreArmModel':bs.getModel(t.foreArmModel), 'handModel':bs.getModel(t.handModel), 'upperLegModel':bs.getModel(t.upperLegModel), 'lowerLegModel':bs.getModel(t.lowerLegModel), 'toesModel':bs.getModel(t.toesModel) } else: m = self.spazMedia[character] return m class Spaz(bs.Actor): """ category: Game Flow Classes Base class for various Spazzes. A Spaz is the standard little humanoid character in the game. It can be controlled by a player or by AI, and can have various different appearances. The name 'Spaz' is not to be confused with the 'Spaz' character in the game, which is just one of the skins available for instances of this class. Attributes: node The 'spaz' bs.Node. """ pointsMult = 1 curseTime = 5000 defaultBombCount = 1 defaultBombType = 'normal' defaultBoxingGloves = False defaultShields = False def __init__(self, color=(1, 1, 1), highlight=(0.5, 0.5, 0.5), character="Spaz", sourcePlayer=None, startInvincible=True, canAcceptPowerups=True, powerupsExpire=False, demoMode=False): """ Create a new spaz with the requested color, character, etc. """ bs.Actor.__init__(self) activity = self.getActivity() factory = self.getFactory() # we need to behave slightly different in the tutorial self._demoMode = demoMode self.playBigDeathSound = False # translate None into empty player-ref if sourcePlayer is None: sourcePlayer = bs.Player(None) # scales how much impacts affect us (most damage calcs) self._impactScale = 1.0 self.sourcePlayer = sourcePlayer self._dead = False if self._demoMode: # preserve old behavior self._punchPowerScale = gBasePunchPowerScale else: self._punchPowerScale = factory.punchPowerScale self.fly = bs.getSharedObject('globals').happyThoughtsMode self._hockey = activity._map.isHockey self._punchedNodes = set() self._cursed = False self._connectedToPlayer = None materials = [factory.spazMaterial, bs.getSharedObject('objectMaterial'), bs.getSharedObject('playerMaterial')] rollerMaterials = [factory.rollerMaterial, bs.getSharedObject('playerMaterial')] extrasMaterials = [] if canAcceptPowerups: pam = bs.Powerup.getFactory().powerupAcceptMaterial materials.append(pam) rollerMaterials.append(pam) extrasMaterials.append(pam) media = factory._getMedia(character) self.node = bs.newNode( type="spaz", delegate=self, attrs={'color':color, 'behaviorVersion':0 if demoMode else 1, 'demoMode':True if demoMode else False, 'highlight':highlight, 'jumpSounds':media['jumpSounds'], 'attackSounds':media['attackSounds'], 'impactSounds':media['impactSounds'], 'deathSounds':media['deathSounds'], 'pickupSounds':media['pickupSounds'], 'fallSounds':media['fallSounds'], 'colorTexture':media['colorTexture'], 'colorMaskTexture':media['colorMaskTexture'], 'headModel':media['headModel'], 'torsoModel':media['torsoModel'], 'pelvisModel':media['pelvisModel'], 'upperArmModel':media['upperArmModel'], 'foreArmModel':media['foreArmModel'], 'handModel':media['handModel'], 'upperLegModel':media['upperLegModel'], 'lowerLegModel':media['lowerLegModel'], 'toesModel':media['toesModel'], 'style':factory._getStyle(character), 'fly':self.fly, 'hockey':self._hockey, 'materials':materials, 'rollerMaterials':rollerMaterials, 'extrasMaterials':extrasMaterials, 'punchMaterials':(factory.punchMaterial, bs.getSharedObject('attackMaterial')), 'pickupMaterials':(factory.pickupMaterial, bs.getSharedObject('pickupMaterial')), 'invincible':startInvincible, 'sourcePlayer':sourcePlayer}) self.shield = None if startInvincible: def _safeSetAttr(node, attr, val): if node.exists(): setattr(node, attr, val) bs.gameTimer(1000, bs.Call(_safeSetAttr, self.node, 'invincible', False)) self.hitPoints = 1000 self.hitPointsMax = 1000 self.bombCount = self.defaultBombCount self._maxBombCount = self.defaultBombCount self.bombTypeDefault = self.defaultBombType self.bombType = self.bombTypeDefault self.landMineCount = 0 self.blastRadius = 2.0 self.powerupsExpire = powerupsExpire if self._demoMode: # preserve old behavior self._punchCooldown = gBasePunchCooldown else: self._punchCooldown = factory.punchCooldown self._jumpCooldown = 250 self._pickupCooldown = 0 self._bombCooldown = 0 self._hasBoxingGloves = False if self.defaultBoxingGloves: self.equipBoxingGloves() self.lastPunchTime = -9999 self.lastJumpTime = -9999 self.lastPickupTime = -9999 self.lastRunTime = -9999 self._lastRunValue = 0 self.lastBombTime = -9999 self._turboFilterTimes = {} self._turboFilterTimeBucket = 0 self._turboFilterCounts = {} self.frozen = False self.shattered = False self._lastHitTime = None self._numTimesHit = 0 self._bombHeld = False if self.defaultShields: self.equipShields() self._droppedBombCallbacks = [] # deprecated stuff.. need to make these into lists self.punchCallback = None self.pickUpPowerupCallback = None def onFinalize(self): bs.Actor.onFinalize(self) # release callbacks/refs so we don't wind up with dependency loops.. self._droppedBombCallbacks = [] self.punchCallback = None self.pickUpPowerupCallback = None def addDroppedBombCallback(self, call): """ Add a call to be run whenever this Spaz drops a bomb. The spaz and the newly-dropped bomb are passed as arguments. """ self._droppedBombCallbacks.append(call) def isAlive(self): """ Method override; returns whether ol' spaz is still kickin'. """ return not self._dead @classmethod def getFactory(cls): """ Returns the shared bs.SpazFactory object, creating it if necessary. """ activity = bs.getActivity() if activity is None: raise Exception("no current activity") try: return activity._sharedSpazFactory except Exception: f = activity._sharedSpazFactory = SpazFactory() return f def exists(self): return self.node.exists() def _hideScoreText(self): if self._scoreText.exists(): bs.animate(self._scoreText, 'scale', {0:self._scoreText.scale, 200:0}) def _turboFilterAddPress(self, source): """ Can pass all button presses through here; if we see an obscene number of them in a short time let's shame/pushish this guy for using turbo """ t = bs.getNetTime() tBucket = int(t/1000) if tBucket == self._turboFilterTimeBucket: # add only once per timestep (filter out buttons triggering # multiple actions) if t != self._turboFilterTimes.get(source, 0): self._turboFilterCounts[source] = \ self._turboFilterCounts.get(source, 0) + 1 self._turboFilterTimes[source] = t # (uncomment to debug; prints what this count is at) # bs.screenMessage( str(source) + " " # + str(self._turboFilterCounts[source])) if self._turboFilterCounts[source] == 15: # knock 'em out. That'll learn 'em. self.node.handleMessage("knockout", 500.0) # also issue periodic notices about who is turbo-ing realTime = bs.getRealTime() global gLastTurboSpamWarningTime if realTime > gLastTurboSpamWarningTime + 30000: gLastTurboSpamWarningTime = realTime bs.screenMessage( bs.Lstr(translate=('statements', ('Warning to ${NAME}: ' 'turbo / button-spamming knocks' ' you out.')), subs=[('${NAME}', self.node.name)]), color=(1, 0.5, 0)) bs.playSound(bs.getSound('error')) else: self._turboFilterTimes = {} self._turboFilterTimeBucket = tBucket self._turboFilterCounts = {source:1} def setScoreText(self, t, color=(1, 1, 0.4), flash=False): """ Utility func to show a message momentarily over our spaz that follows him around; Handy for score updates and things. """ colorFin = bs.getSafeColor(color)[:3] if not self.node.exists(): return try: exists = self._scoreText.exists() except Exception: exists = False if not exists: startScale = 0.0 m = bs.newNode('math', owner=self.node, attrs={ 'input1':(0, 1.4, 0), 'operation':'add' }) self.node.connectAttr('torsoPosition', m, 'input2') self._scoreText = bs.newNode('text', owner=self.node, attrs={'text':t, 'inWorld':True, 'shadow':1.0, 'flatness':1.0, 'color':colorFin, 'scale':0.02, 'hAlign':'center'}) m.connectAttr('output', self._scoreText, 'position') else: self._scoreText.color = colorFin startScale = self._scoreText.scale self._scoreText.text = t if flash: combine = bs.newNode("combine", owner=self._scoreText, attrs={'size':3}) sc = 1.8 offs = 0.5 t = 300 for i in range(3): c1 = offs+sc*colorFin[i] c2 = colorFin[i] bs.animate(combine, 'input'+str(i), {0.5*t:c2, 0.75*t:c1, 1.0*t:c2}) combine.connectAttr('output', self._scoreText, 'color') bs.animate(self._scoreText, 'scale', {0:startScale, 200:0.02}) self._scoreTextHideTimer = bs.Timer(1000, bs.WeakCall(self._hideScoreText)) def onJumpPress(self): """ Called to 'press jump' on this spaz; used by player or AI connections. """ if not self.node.exists(): return t = bs.getGameTime() if t - self.lastJumpTime >= self._jumpCooldown: self.node.jumpPressed = True self.lastJumpTime = t self._turboFilterAddPress('jump') def onJumpRelease(self): """ Called to 'release jump' on this spaz; used by player or AI connections. """ if not self.node.exists(): return self.node.jumpPressed = False def onPickUpPress(self): """ Called to 'press pick-up' on this spaz; used by player or AI connections. """ if not self.node.exists(): return t = bs.getGameTime() if t - self.lastPickupTime >= self._pickupCooldown: self.node.pickUpPressed = True self.lastPickupTime = t self._turboFilterAddPress('pickup') def onPickUpRelease(self): """ Called to 'release pick-up' on this spaz; used by player or AI connections. """ if not self.node.exists(): return self.node.pickUpPressed = False def _onHoldPositionPress(self): """ Called to 'press hold-position' on this spaz; used for player or AI connections. """ if not self.node.exists(): return self.node.holdPositionPressed = True self._turboFilterAddPress('holdposition') def _onHoldPositionRelease(self): """ Called to 'release hold-position' on this spaz; used for player or AI connections. """ if not self.node.exists(): return self.node.holdPositionPressed = False def onPunchPress(self): """ Called to 'press punch' on this spaz; used for player or AI connections. """ if (not self.node.exists() or self.frozen or self.node.knockout > 0.0): return t = bs.getGameTime() if t - self.lastPunchTime >= self._punchCooldown: if self.punchCallback is not None: self.punchCallback(self) self._punchedNodes = set() # reset this.. self.lastPunchTime = t self.node.punchPressed = True if not self.node.holdNode.exists(): bs.gameTimer(100, bs.WeakCall(self._safePlaySound, self.getFactory().swishSound, 0.8)) self._turboFilterAddPress('punch') def _safePlaySound(self, sound, volume): """ Plays a sound at our position if we exist. """ if self.node.exists(): bs.playSound(sound, volume, self.node.position) def onPunchRelease(self): """ Called to 'release punch' on this spaz; used for player or AI connections. """ if not self.node.exists(): return self.node.punchPressed = False def onBombPress(self): """ Called to 'press bomb' on this spaz; used for player or AI connections. """ if not self.node.exists(): return if self._dead or self.frozen: return if self.node.knockout > 0.0: return t = bs.getGameTime() if t - self.lastBombTime >= self._bombCooldown: self.lastBombTime = t self.node.bombPressed = True if not self.node.holdNode.exists(): self.dropBomb() self._turboFilterAddPress('bomb') def onBombRelease(self): """ Called to 'release bomb' on this spaz; used for player or AI connections. """ if not self.node.exists(): return self.node.bombPressed = False def onRun(self, value): """ Called to 'press run' on this spaz; used for player or AI connections. """ if not self.node.exists(): return t = bs.getGameTime() self.lastRunTime = t self.node.run = value # filtering these events would be tough since its an analog # value, but lets still pass full 0-to-1 presses along to # the turbo filter to punish players if it looks like they're turbo-ing if self._lastRunValue < 0.01 and value > 0.99: self._turboFilterAddPress('run') self._lastRunValue = value def onFlyPress(self): """ Called to 'press fly' on this spaz; used for player or AI connections. """ if not self.node.exists(): return # not adding a cooldown time here for now; slightly worried # input events get clustered up during net-games and we'd wind up # killing a lot and making it hard to fly.. should look into this. self.node.flyPressed = True self._turboFilterAddPress('fly') def onFlyRelease(self): """ Called to 'release fly' on this spaz; used for player or AI connections. """ if not self.node.exists(): return self.node.flyPressed = False def onMove(self, x, y): """ Called to set the joystick amount for this spaz; used for player or AI connections. """ if not self.node.exists(): return self.node.handleMessage("move", x, y) def onMoveUpDown(self, value): """ Called to set the up/down joystick amount on this spaz; used for player or AI connections. value will be between -32768 to 32767 WARNING: deprecated; use onMove instead. """ if not self.node.exists(): return self.node.moveUpDown = value def onMoveLeftRight(self, value): """ Called to set the left/right joystick amount on this spaz; used for player or AI connections. value will be between -32768 to 32767 WARNING: deprecated; use onMove instead. """ if not self.node.exists(): return self.node.moveLeftRight = value def onPunched(self, damage): """ Called when this spaz gets punched. """ pass def getDeathPoints(self, how): 'Get the points awarded for killing this spaz' numHits = float(max(1, self._numTimesHit)) # base points is simply 10 for 1-hit-kills and 5 otherwise importance = 2 if numHits < 2 else 1 return ((10 if numHits < 2 else 5) * self.pointsMult, importance) def curse(self): """ Give this poor spaz a curse; he will explode in 5 seconds. """ if not self._cursed: factory = self.getFactory() self._cursed = True # add the curse material.. for attr in ['materials', 'rollerMaterials']: materials = getattr(self.node, attr) if not factory.curseMaterial in materials: setattr(self.node, attr, materials + (factory.curseMaterial,)) # -1 specifies no time limit if self.curseTime == -1: self.node.curseDeathTime = -1 else: self.node.curseDeathTime = bs.getGameTime()+5000 bs.gameTimer(5000, bs.WeakCall(self.curseExplode)) def equipBoxingGloves(self): """ Give this spaz some boxing gloves. """ self.node.boxingGloves = 1 if self._demoMode: # preserve old behavior self._punchPowerScale = 1.7 self._punchCooldown = 300 else: factory = self.getFactory() self._punchPowerScale = factory.punchPowerScaleGloves self._punchCooldown = factory.punchCooldownGloves def equipShields(self, decay=False): """ Give this spaz a nice energy shield. """ if not self.node.exists(): bs.printError('Can\'t equip shields; no node.') return factory = self.getFactory() if self.shield is None: self.shield = bs.newNode('shield', owner=self.node, attrs={'color':(0.3, 0.2, 2.0), 'radius':1.3}) self.node.connectAttr('positionCenter', self.shield, 'position') self.shieldHitPoints = self.shieldHitPointsMax = 650 self.shieldDecayRate = factory.shieldDecayRate if decay else 0 self.shield.hurt = 0 bs.playSound(factory.shieldUpSound, 1.0, position=self.node.position) if self.shieldDecayRate > 0: self.shieldDecayTimer = bs.Timer(500, bs.WeakCall(self.shieldDecay), repeat=True) self.shield.alwaysShowHealthBar = True # so user can see the decay def shieldDecay(self): 'Called repeatedly to decay shield HP over time.' if self.shield is not None and self.shield.exists(): self.shieldHitPoints = \ max(0, self.shieldHitPoints - self.shieldDecayRate) self.shield.hurt = \ 1.0 - float(self.shieldHitPoints)/self.shieldHitPointsMax if self.shieldHitPoints <= 0: self.shield.delete() self.shield = None self.shieldDecayTimer = None bs.playSound(self.getFactory().shieldDownSound, 1.0, position=self.node.position) else: self.shieldDecayTimer = None def handleMessage(self, msg): self._handleMessageSanityCheck() if isinstance(msg, bs.PickedUpMessage): self.node.handleMessage("hurtSound") self.node.handleMessage("pickedUp") # this counts as a hit self._numTimesHit += 1 elif isinstance(msg, bs.ShouldShatterMessage): # eww; seems we have to do this in a timer or it wont work right # (since we're getting called from within update() perhaps?..) bs.gameTimer(1, bs.WeakCall(self.shatter)) elif isinstance(msg, bs.ImpactDamageMessage): # eww; seems we have to do this in a timer or it wont work right # (since we're getting called from within update() perhaps?..) bs.gameTimer(1, bs.WeakCall(self._hitSelf, msg.intensity)) elif isinstance(msg, bs.PowerupMessage): if self._dead: return True if self.pickUpPowerupCallback is not None: self.pickUpPowerupCallback(self) if (msg.powerupType == 'tripleBombs'): tex = bs.Powerup.getFactory().texBomb self._flashBillboard(tex) self.setBombCount(3) if self.powerupsExpire: self.node.miniBillboard1Texture = tex t = bs.getGameTime() self.node.miniBillboard1StartTime = t self.node.miniBillboard1EndTime = t+gPowerupWearOffTime self._multiBombWearOffFlashTimer = \ bs.Timer(gPowerupWearOffTime-2000, bs.WeakCall(self._multiBombWearOffFlash)) self._multiBombWearOffTimer = \ bs.Timer(gPowerupWearOffTime, bs.WeakCall(self._multiBombWearOff)) elif msg.powerupType == 'landMines': self.setLandMineCount(min(self.landMineCount+3, 3)) elif msg.powerupType == 'impactBombs': self.bombType = 'impact' tex = self._getBombTypeTex() self._flashBillboard(tex) if self.powerupsExpire: self.node.miniBillboard2Texture = tex t = bs.getGameTime() self.node.miniBillboard2StartTime = t self.node.miniBillboard2EndTime = t+gPowerupWearOffTime self._bombWearOffFlashTimer = \ bs.Timer( gPowerupWearOffTime-2000, bs.WeakCall(self._bombWearOffFlash)) self._bombWearOffTimer = \ bs.Timer(gPowerupWearOffTime, bs.WeakCall(self._bombWearOff)) elif msg.powerupType == 'stickyBombs': self.bombType = 'sticky' tex = self._getBombTypeTex() self._flashBillboard(tex) if self.powerupsExpire: self.node.miniBillboard2Texture = tex t = bs.getGameTime() self.node.miniBillboard2StartTime = t self.node.miniBillboard2EndTime = t+gPowerupWearOffTime self._bombWearOffFlashTimer = \ bs.Timer(gPowerupWearOffTime-2000, bs.WeakCall(self._bombWearOffFlash)) self._bombWearOffTimer = \ bs.Timer(gPowerupWearOffTime, bs.WeakCall(self._bombWearOff)) elif msg.powerupType == 'punch': self._hasBoxingGloves = True tex = bs.Powerup.getFactory().texPunch self._flashBillboard(tex) self.equipBoxingGloves() if self.powerupsExpire: self.node.boxingGlovesFlashing = 0 self.node.miniBillboard3Texture = tex t = bs.getGameTime() self.node.miniBillboard3StartTime = t self.node.miniBillboard3EndTime = t+gPowerupWearOffTime self._boxingGlovesWearOffFlashTimer = \ bs.Timer(gPowerupWearOffTime-2000, bs.WeakCall(self._glovesWearOffFlash)) self._boxingGlovesWearOffTimer = \ bs.Timer(gPowerupWearOffTime, bs.WeakCall(self._glovesWearOff)) elif msg.powerupType == 'shield': factory = self.getFactory() # let's allow powerup-equipped shields to lose hp over time self.equipShields( decay=True if factory.shieldDecayRate > 0 else False) elif msg.powerupType == 'curse': self.curse() elif (msg.powerupType == 'iceBombs'): self.bombType = 'ice' tex = self._getBombTypeTex() self._flashBillboard(tex) if self.powerupsExpire: self.node.miniBillboard2Texture = tex t = bs.getGameTime() self.node.miniBillboard2StartTime = t self.node.miniBillboard2EndTime = t+gPowerupWearOffTime self._bombWearOffFlashTimer = \ bs.Timer(gPowerupWearOffTime-2000, bs.WeakCall(self._bombWearOffFlash)) self._bombWearOffTimer = \ bs.Timer(gPowerupWearOffTime, bs.WeakCall(self._bombWearOff)) elif (msg.powerupType == 'health'): if self._cursed: self._cursed = False # remove cursed material factory = self.getFactory() for attr in ['materials', 'rollerMaterials']: materials = getattr(self.node, attr) if factory.curseMaterial in materials: setattr(self.node, attr, tuple(m for m in materials if m != factory.curseMaterial)) self.node.curseDeathTime = 0 self.hitPoints = self.hitPointsMax self._flashBillboard(bs.Powerup.getFactory().texHealth) self.node.hurt = 0 self._lastHitTime = None self._numTimesHit = 0 self.node.handleMessage("flash") if msg.sourceNode.exists(): msg.sourceNode.handleMessage(bs.PowerupAcceptMessage()) return True elif isinstance(msg, bs.FreezeMessage): if not self.node.exists(): return if self.node.invincible == True: bs.playSound(self.getFactory().blockSound, 1.0, position=self.node.position) return if self.shield is not None: return if not self.frozen: self.frozen = True self.node.frozen = 1 bs.gameTimer(5000, bs.WeakCall(self.handleMessage, bs.ThawMessage())) # instantly shatter if we're already dead # (otherwise its hard to tell we're dead) if self.hitPoints <= 0: self.shatter() elif isinstance(msg, bs.ThawMessage): if self.frozen and not self.shattered and self.node.exists(): self.frozen = False self.node.frozen = 0 elif isinstance(msg, bs.HitMessage): if not self.node.exists(): return if self.node.invincible == True: bs.playSound(self.getFactory().blockSound, 1.0, position=self.node.position) return True # if we were recently hit, don't count this as another # (so punch flurries and bomb pileups essentially count as 1 hit) gameTime = bs.getGameTime() if self._lastHitTime is None or gameTime-self._lastHitTime > 1000: self._numTimesHit += 1 self._lastHitTime = gameTime mag = msg.magnitude * self._impactScale velocityMag = msg.velocityMagnitude * self._impactScale damageScale = 0.22 # if they've got a shield, deliver it to that instead.. if self.shield is not None: if msg.flatDamage: damage = msg.flatDamage * self._impactScale else: # hit our spaz with an impulse but tell it to only return # theoretical damage; not apply the impulse.. self.node.handleMessage( "impulse", msg.pos[0], msg.pos[1], msg.pos[2], msg.velocity[0], msg.velocity[1], msg.velocity[2], mag , velocityMag, msg.radius, 1, msg.forceDirection[0], msg.forceDirection[1], msg.forceDirection[2]) damage = damageScale * self.node.damage self.shieldHitPoints -= damage self.shield.hurt = (1.0 - float(self.shieldHitPoints) /self.shieldHitPointsMax) # its a cleaner event if a hit just kills the shield # without damaging the player.. # however, massive damage events should still be able to # damage the player.. this hopefully gives us a happy medium. maxSpillover = self.getFactory().maxShieldSpilloverDamage if self.shieldHitPoints <= 0: # fixme - transition out perhaps?.. self.shield.delete() self.shield = None bs.playSound(self.getFactory().shieldDownSound, 1.0, position=self.node.position) # emit some cool lookin sparks when the shield dies t = self.node.position bs.emitBGDynamics(position=(t[0], t[1]+0.9, t[2]), velocity=self.node.velocity, count=random.randrange(20, 30), scale=1.0, spread=0.6, chunkType='spark') else: bs.playSound(self.getFactory().shieldHitSound, 0.5, position=self.node.position) # emit some cool lookin sparks on shield hit bs.emitBGDynamics(position=msg.pos, velocity=(msg.forceDirection[0]*1.0, msg.forceDirection[1]*1.0, msg.forceDirection[2]*1.0), count=min(30, 5+int(damage*0.005)), scale=0.5, spread=0.3, chunkType='spark') # if they passed our spillover threshold, # pass damage along to spaz if self.shieldHitPoints <= -maxSpillover: leftoverDamage = -maxSpillover-self.shieldHitPoints shieldLeftoverRatio = leftoverDamage/damage # scale down the magnitudes applied to spaz accordingly.. mag *= shieldLeftoverRatio velocityMag *= shieldLeftoverRatio else: return True # good job shield! else: shieldLeftoverRatio = 1.0 if msg.flatDamage: damage = (msg.flatDamage * self._impactScale * shieldLeftoverRatio) else: # hit it with an impulse and get the resulting damage self.node.handleMessage( "impulse", msg.pos[0], msg.pos[1], msg.pos[2], msg.velocity[0], msg.velocity[1], msg.velocity[2], mag, velocityMag, msg.radius, 0, msg.forceDirection[0], msg.forceDirection[1], msg.forceDirection[2]) damage = damageScale * self.node.damage self.node.handleMessage("hurtSound") # play punch impact sound based on damage if it was a punch if msg.hitType == 'punch': self.onPunched(damage) # if damage was significant, lets show it if damage > 350: bsUtils.showDamageCount('-' + str(int(damage/10)) + "%", msg.pos, msg.forceDirection) # lets always add in a super-punch sound with boxing # gloves just to differentiate them if msg.hitSubType == 'superPunch': bs.playSound(self.getFactory().punchSoundStronger, 1.0, position=self.node.position) if damage > 500: sounds = self.getFactory().punchSoundsStrong sound = sounds[random.randrange(len(sounds))] else: sound = self.getFactory().punchSound bs.playSound(sound, 1.0, position=self.node.position) # throw up some chunks bs.emitBGDynamics(position=msg.pos, velocity=(msg.forceDirection[0]*0.5, msg.forceDirection[1]*0.5, msg.forceDirection[2]*0.5), count=min(10, 1+int(damage*0.0025)), scale=0.3, spread=0.03); bs.emitBGDynamics(position=msg.pos, chunkType='sweat', velocity=(msg.forceDirection[0]*1.3, msg.forceDirection[1]*1.3+5.0, msg.forceDirection[2]*1.3), count=min(30, 1+int(damage*0.04)), scale=0.9, spread=0.28); # momentary flash hurtiness = damage*0.003 punchPos = (msg.pos[0]+msg.forceDirection[0]*0.02, msg.pos[1]+msg.forceDirection[1]*0.02, msg.pos[2]+msg.forceDirection[2]*0.02) flashColor = (1.0, 0.8, 0.4) light = bs.newNode("light", attrs={'position':punchPos, 'radius':0.12+hurtiness*0.12, 'intensity':0.3*(1.0+1.0*hurtiness), 'heightAttenuated':False, 'color':flashColor}) bs.gameTimer(60, light.delete) flash = bs.newNode("flash", attrs={'position':punchPos, 'size':0.17+0.17*hurtiness, 'color':flashColor}) bs.gameTimer(60, flash.delete) if msg.hitType == 'impact': bs.emitBGDynamics(position=msg.pos, velocity=(msg.forceDirection[0]*2.0, msg.forceDirection[1]*2.0, msg.forceDirection[2]*2.0), count=min(10, 1+int(damage*0.01)), scale=0.4, spread=0.1); if self.hitPoints > 0: # its kinda crappy to die from impacts, so lets reduce # impact damage by a reasonable amount if it'll keep us alive if msg.hitType == 'impact' and damage > self.hitPoints: # drop damage to whatever puts us at 10 hit points, # or 200 less than it used to be whichever is greater # (so it *can* still kill us if its high enough) newDamage = max(damage-200, self.hitPoints-10) damage = newDamage self.node.handleMessage("flash") # if we're holding something, drop it if damage > 0.0 and self.node.holdNode.exists(): self.node.holdNode = bs.Node(None) self.hitPoints -= damage self.node.hurt = 1.0 - float(self.hitPoints)/self.hitPointsMax # if we're cursed, *any* damage blows us up if self._cursed and damage > 0: bs.gameTimer(50, bs.WeakCall(self.curseExplode, msg.sourcePlayer)) # if we're frozen, shatter.. otherwise die if we hit zero if self.frozen and (damage > 200 or self.hitPoints <= 0): self.shatter() elif self.hitPoints <= 0: self.node.handleMessage(bs.DieMessage(how='impact')) # if we're dead, take a look at the smoothed damage val # (which gives us a smoothed average of recent damage) and shatter # us if its grown high enough if self.hitPoints <= 0: damageAvg = self.node.damageSmoothed * damageScale if damageAvg > 1000: self.shatter() elif isinstance(msg, _BombDiedMessage): self.bombCount += 1 elif isinstance(msg, bs.DieMessage): wasDead = self._dead self._dead = True self.hitPoints = 0 if msg.immediate: self.node.delete() elif self.node.exists(): self.node.hurt = 1.0 if self.playBigDeathSound and not wasDead: bs.playSound(self.getFactory().singlePlayerDeathSound) self.node.dead = True bs.gameTimer(2000, self.node.delete) elif isinstance(msg, bs.OutOfBoundsMessage): # by default we just die here self.handleMessage(bs.DieMessage(how='fall')) elif isinstance(msg, bs.StandMessage): self._lastStandPos = (msg.position[0], msg.position[1], msg.position[2]) self.node.handleMessage("stand", msg.position[0], msg.position[1], msg.position[2], msg.angle) elif isinstance(msg, _CurseExplodeMessage): self.curseExplode() elif isinstance(msg, _PunchHitMessage): node = bs.getCollisionInfo("opposingNode") # only allow one hit per node per punch if (node is not None and node.exists() and not node in self._punchedNodes): punchMomentumAngular = (self.node.punchMomentumAngular * self._punchPowerScale) punchPower = self.node.punchPower * self._punchPowerScale # ok here's the deal: we pass along our base velocity for use # in the impulse damage calculations since that is a more # predictable value than our fist velocity, which is rather # erratic. ...however we want to actually apply force in the # direction our fist is moving so it looks better.. so we still # pass that along as a direction ..perhaps a time-averaged # fist-velocity would work too?.. should try that. # if its something besides another spaz, just do a muffled punch # sound if node.getNodeType() != 'spaz': sounds = self.getFactory().impactSoundsMedium sound = sounds[random.randrange(len(sounds))] bs.playSound(sound, 1.0, position=self.node.position) t = self.node.punchPosition punchDir = self.node.punchVelocity v = self.node.punchMomentumLinear self._punchedNodes.add(node) node.handleMessage( bs.HitMessage( pos=t, velocity=v, magnitude=punchPower*punchMomentumAngular*110.0, velocityMagnitude=punchPower*40, radius=0, srcNode=self.node, sourcePlayer=self.sourcePlayer, forceDirection = punchDir, hitType='punch', hitSubType=('superPunch' if self._hasBoxingGloves else 'default'))) # also apply opposite to ourself for the first punch only # ..this is given as a constant force so that it is more # noticable for slower punches where it matters.. for fast # awesome looking punches its ok if we punch 'through' # the target mag = -400.0 if self._hockey: mag *= 0.5 if len(self._punchedNodes) == 1: self.node.handleMessage("kickBack", t[0], t[1], t[2], punchDir[0], punchDir[1], punchDir[2], mag) elif isinstance(msg, _PickupMessage): opposingNode, opposingBody = bs.getCollisionInfo('opposingNode', 'opposingBody') if opposingNode is None or not opposingNode.exists(): return True # dont allow picking up of invincible dudes try: if opposingNode.invincible == True: return True except Exception: pass # if we're grabbing the pelvis of a non-shattered spaz, we wanna # grab the torso instead if (opposingNode.getNodeType() == 'spaz' and not opposingNode.shattered and opposingBody == 4): opposingBody = 1 # special case - if we're holding a flag, dont replace it # ( hmm - should make this customizable or more low level ) held = self.node.holdNode if (held is not None and held.exists() and held.getNodeType() == 'flag'): return True self.node.holdBody = opposingBody # needs to be set before holdNode self.node.holdNode = opposingNode else: bs.Actor.handleMessage(self, msg) def dropBomb(self): """ Tell the spaz to drop one of his bombs, and returns the resulting bomb object. If the spaz has no bombs or is otherwise unable to drop a bomb, returns None. """ if (self.landMineCount <= 0 and self.bombCount <= 0) or self.frozen: return p = self.node.positionForward v = self.node.velocity if self.landMineCount > 0: droppingBomb = False self.setLandMineCount(self.landMineCount-1) bombType = 'landMine' else: droppingBomb = True bombType = self.bombType bomb = bs.Bomb(position=(p[0], p[1] - 0.0, p[2]), velocity=(v[0], v[1], v[2]), bombType=bombType, blastRadius=self.blastRadius, sourcePlayer=self.sourcePlayer, owner=self.node).autoRetain() if droppingBomb: self.bombCount -= 1 bomb.node.addDeathAction(bs.WeakCall(self.handleMessage, _BombDiedMessage())) self._pickUp(bomb.node) # yeet bomb mag = 4000 velocityMag = 2000 radius = 0 velocity = [150, 130, 100] forceDirection = [0, 200, 0] pos = self.node.position bomb.node.handleMessage( "impulse", pos[0], pos[1], pos[2], velocity[0], velocity[1], velocity[2], mag, velocityMag, radius, 0, forceDirection[0], forceDirection[1], forceDirection[2]) for c in self._droppedBombCallbacks: c(self, bomb) return bomb def _pickUp(self, node): if self.node.exists() and node.exists(): self.node.holdBody = 0 # needs to be set before holdNode self.node.holdNode = node def setLandMineCount(self, count): """ Set the number of land-mines this spaz is carrying. """ self.landMineCount = count if self.node.exists(): if self.landMineCount != 0: self.node.counterText = 'x'+str(self.landMineCount) self.node.counterTexture = bs.Powerup.getFactory().texLandMines else: self.node.counterText = '' def curseExplode(self, sourcePlayer=None): """ Explode the poor spaz as happens when a curse timer runs out. """ # convert None to an empty player-ref if sourcePlayer is None: sourcePlayer = bs.Player(None) if self._cursed and self.node.exists(): self.shatter(extreme=True) self.handleMessage(bs.DieMessage()) activity = self._activity() if activity: bs.Blast(position=self.node.position, velocity=self.node.velocity, blastRadius=3.0, blastType='normal', sourcePlayer=(sourcePlayer if sourcePlayer.exists() else self.sourcePlayer)).autoRetain() self._cursed = False def shatter(self, extreme=False): """ Break the poor spaz into little bits. """ if self.shattered: return self.shattered = True if self.frozen: # momentary flash of light light = bs.newNode('light', attrs={'position':self.node.position, 'radius':0.5, 'heightAttenuated':False, 'color': (0.8, 0.8, 1.0)}) bs.animate(light, 'intensity', {0:3.0, 40:0.5, 80:0.07, 300:0}) bs.gameTimer(300, light.delete) # emit ice chunks.. bs.emitBGDynamics(position=self.node.position, velocity=self.node.velocity, count=int(random.random()*10.0+10.0), scale=0.6, spread=0.2, chunkType='ice'); bs.emitBGDynamics(position=self.node.position, velocity=self.node.velocity, count=int(random.random()*10.0+10.0), scale=0.3, spread=0.2, chunkType='ice'); bs.playSound(self.getFactory().shatterSound, 1.0, position=self.node.position) else: bs.playSound(self.getFactory().splatterSound, 1.0, position=self.node.position) self.handleMessage(bs.DieMessage()) self.node.shattered = 2 if extreme else 1 def _hitSelf(self, intensity): # clean exit if we're dead.. try: pos = self.node.position except Exception: return self.handleMessage(bs.HitMessage(flatDamage=50.0*intensity, pos=pos, forceDirection=self.node.velocity, hitType='impact')) self.node.handleMessage("knockout", max(0.0, 50.0*intensity)) if intensity > 5: sounds = self.getFactory().impactSoundsHarder elif intensity > 3: sounds = self.getFactory().impactSoundsHard else: sounds = self.getFactory().impactSoundsMedium s = sounds[random.randrange(len(sounds))] bs.playSound(s, position=pos, volume=5.0) def _getBombTypeTex(self): bombFactory = bs.Powerup.getFactory() if self.bombType == 'sticky': return bombFactory.texStickyBombs elif self.bombType == 'ice': return bombFactory.texIceBombs elif self.bombType == 'impact': return bombFactory.texImpactBombs else: raise Exception() def _flashBillboard(self, tex): self.node.billboardTexture = tex self.node.billboardCrossOut = False bs.animate(self.node, "billboardOpacity", {0:0.0, 100:1.0, 400:1.0, 500:0.0}) def setBombCount(self, count): 'Sets the number of bombs this Spaz has.' # we cant just set bombCount cuz some bombs may be laid currently # so we have to do a relative diff based on max diff = count - self._maxBombCount self._maxBombCount += diff self.bombCount += diff def _glovesWearOffFlash(self): if self.node.exists(): self.node.boxingGlovesFlashing = 1 self.node.billboardTexture = bs.Powerup.getFactory().texPunch self.node.billboardOpacity = 1.0 self.node.billboardCrossOut = True def _glovesWearOff(self): if self._demoMode: # preserve old behavior self._punchPowerScale = gBasePunchPowerScale self._punchCooldown = gBasePunchCooldown else: factory = self.getFactory() self._punchPowerScale = factory.punchPowerScale self._punchCooldown = factory.punchCooldown self._hasBoxingGloves = False if self.node.exists(): bs.playSound(bs.Powerup.getFactory().powerdownSound, position=self.node.position) self.node.boxingGloves = 0 self.node.billboardOpacity = 0.0 def _multiBombWearOffFlash(self): if self.node.exists(): self.node.billboardTexture = bs.Powerup.getFactory().texBomb self.node.billboardOpacity = 1.0 self.node.billboardCrossOut = True def _multiBombWearOff(self): self.setBombCount(self.defaultBombCount) if self.node.exists(): bs.playSound(bs.Powerup.getFactory().powerdownSound, position=self.node.position) self.node.billboardOpacity = 0.0 def _bombWearOffFlash(self): if self.node.exists(): self.node.billboardTexture = self._getBombTypeTex() self.node.billboardOpacity = 1.0 self.node.billboardCrossOut = True def _bombWearOff(self): self.bombType = self.bombTypeDefault if self.node.exists(): bs.playSound(bs.Powerup.getFactory().powerdownSound, position=self.node.position) self.node.billboardOpacity = 0.0 class PlayerSpazDeathMessage(object): """ category: Message Classes A bs.PlayerSpaz has died. Attributes: spaz The bs.PlayerSpaz that died. killed If True, the spaz was killed; If False, they left the game or the round ended. killerPlayer The bs.Player that did the killing, or None. how The particular type of death. """ def __init__(self, spaz, wasKilled, killerPlayer, how): """ Instantiate a message with the given values. """ self.spaz = spaz self.killed = wasKilled self.killerPlayer = killerPlayer self.how = how class PlayerSpazHurtMessage(object): """ category: Message Classes A bs.PlayerSpaz was hurt. Attributes: spaz The bs.PlayerSpaz that was hurt """ def __init__(self, spaz): """ Instantiate with the given bs.Spaz value. """ self.spaz = spaz class PlayerSpaz(Spaz): """ category: Game Flow Classes A bs.Spaz subclass meant to be controlled by a bs.Player. When a PlayerSpaz dies, it delivers a bs.PlayerSpazDeathMessage to the current bs.Activity. (unless the death was the result of the player leaving the game, in which case no message is sent) When a PlayerSpaz is hurt, it delivers a bs.PlayerSpazHurtMessage to the current bs.Activity. """ def __init__(self, color=(1, 1, 1), highlight=(0.5, 0.5, 0.5), character="Spaz", player=None, powerupsExpire=True): """ Create a spaz for the provided bs.Player. Note: this does not wire up any controls; you must call connectControlsToPlayer() to do so. """ # convert None to an empty player-ref if player is None: player = bs.Player(None) Spaz.__init__(self, color=color, highlight=highlight, character=character, sourcePlayer=player, startInvincible=True, powerupsExpire=powerupsExpire) self.lastPlayerAttackedBy = None # FIXME - should use empty player ref self.lastAttackedTime = 0 self.lastAttackedType = None self.heldCount = 0 self.lastPlayerHeldBy = None # FIXME - should use empty player ref here self._player = player # grab the node for this player and wire it to follow our spaz # (so players' controllers know where to draw their guides, etc) if player.exists(): playerNode = bs.getActivity()._getPlayerNode(player) self.node.connectAttr('torsoPosition', playerNode, 'position') def __superHandleMessage(self, msg): super(PlayerSpaz, self).handleMessage(msg) def getPlayer(self): """ Return the bs.Player associated with this spaz. Note that while a valid player object will always be returned, there is no guarantee that the player is still in the game. Call bs.Player.exists() on the return value before doing anything with it. """ return self._player def connectControlsToPlayer(self, enableJump=True, enablePunch=True, enablePickUp=True, enableBomb=True, enableRun=True, enableFly=True): """ Wire this spaz up to the provided bs.Player. Full control of the character is given by default but can be selectively limited by passing False to specific arguments. """ player = self.getPlayer() # reset any currently connected player and/or the player we're wiring up if self._connectedToPlayer is not None: if player != self._connectedToPlayer: player.resetInput() self.disconnectControlsFromPlayer() else: player.resetInput() player.assignInputCall('upDown', self.onMoveUpDown) player.assignInputCall('leftRight', self.onMoveLeftRight) player.assignInputCall('holdPositionPress', self._onHoldPositionPress) player.assignInputCall('holdPositionRelease', self._onHoldPositionRelease) if enableJump: player.assignInputCall('jumpPress', self.onJumpPress) player.assignInputCall('jumpRelease', self.onJumpRelease) if enablePickUp: player.assignInputCall('pickUpPress', self.onPickUpPress) player.assignInputCall('pickUpRelease', self.onPickUpRelease) if enablePunch: player.assignInputCall('punchPress', self.onPunchPress) player.assignInputCall('punchRelease', self.onPunchRelease) if enableBomb: player.assignInputCall('bombPress', self.onBombPress) player.assignInputCall('bombRelease', self.onBombRelease) if enableRun: player.assignInputCall('run', self.onRun) if enableFly: player.assignInputCall('flyPress', self.onFlyPress) player.assignInputCall('flyRelease', self.onFlyRelease) self._connectedToPlayer = player def disconnectControlsFromPlayer(self): """ Completely sever any previously connected bs.Player from control of this spaz. """ if self._connectedToPlayer is not None: self._connectedToPlayer.resetInput() self._connectedToPlayer = None # send releases for anything in case its held.. self.onMoveUpDown(0) self.onMoveLeftRight(0) self._onHoldPositionRelease() self.onJumpRelease() self.onPickUpRelease() self.onPunchRelease() self.onBombRelease() self.onRun(0.0) self.onFlyRelease() else: print ('WARNING: disconnectControlsFromPlayer() called for' ' non-connected player') def handleMessage(self, msg): self._handleMessageSanityCheck() # keep track of if we're being held and by who most recently if isinstance(msg, bs.PickedUpMessage): self.__superHandleMessage(msg) # augment standard behavior self.heldCount += 1 pickedUpBy = msg.node.sourcePlayer if pickedUpBy is not None and pickedUpBy.exists(): self.lastPlayerHeldBy = pickedUpBy elif isinstance(msg, bs.DroppedMessage): self.__superHandleMessage(msg) # augment standard behavior self.heldCount -= 1 if self.heldCount < 0: print "ERROR: spaz heldCount < 0" # let's count someone dropping us as an attack.. try: pickedUpBy = msg.node.sourcePlayer except Exception: pickedUpBy = None if pickedUpBy is not None and pickedUpBy.exists(): # Yeet mag = 2500 velocityMag = 1500 radius = 0 velocity = [10, 20, 10] forceDirection = [20, 200, 100] pos = self.node.position self.node.handleMessage( "impulse", pos[0], pos[1], pos[2], velocity[0], velocity[1], velocity[2], mag, velocityMag, radius, 0, forceDirection[0], forceDirection[1], forceDirection[2]) self.lastPlayerAttackedBy = pickedUpBy self.lastAttackedTime = bs.getGameTime() self.lastAttackedType = ('pickedUp', 'default') elif isinstance(msg, bs.DieMessage): # report player deaths to the game if not self._dead: # immediate-mode or left-game deaths don't count as 'kills' killed = (msg.immediate==False and msg.how!='leftGame') activity = self._activity() if not killed: killerPlayer = None else: # if this player was being held at the time of death, # the holder is the killer if (self.heldCount > 0 and self.lastPlayerHeldBy is not None and self.lastPlayerHeldBy.exists()): killerPlayer = self.lastPlayerHeldBy else: # otherwise, if they were attacked by someone in the # last few seconds, that person's the killer.. # otherwise it was a suicide. # FIXME - currently disabling suicides in Co-Op since # all bot kills would register as suicides; need to # change this from lastPlayerAttackedBy to something # like lastActorAttackedBy to fix that. if (self.lastPlayerAttackedBy is not None and self.lastPlayerAttackedBy.exists() and bs.getGameTime() - self.lastAttackedTime \ < 4000): killerPlayer = self.lastPlayerAttackedBy else: # ok, call it a suicide unless we're in co-op if (activity is not None and not isinstance(activity.getSession(), bs.CoopSession)): killerPlayer = self.getPlayer() else: killerPlayer = None if killerPlayer is not None and not killerPlayer.exists(): killerPlayer = None # only report if both the player and the activity still exist if (killed and activity is not None and self.getPlayer().exists()): activity.handleMessage( PlayerSpazDeathMessage(self, killed, killerPlayer, msg.how)) self.__superHandleMessage(msg) # augment standard behavior # keep track of the player who last hit us for point rewarding elif isinstance(msg, bs.HitMessage): if msg.sourcePlayer is not None and msg.sourcePlayer.exists(): self.lastPlayerAttackedBy = msg.sourcePlayer self.lastAttackedTime = bs.getGameTime() self.lastAttackedType = (msg.hitType, msg.hitSubType) self.__superHandleMessage(msg) # augment standard behavior activity = self._activity() if activity is not None: activity.handleMessage(PlayerSpazHurtMessage(self)) else: Spaz.handleMessage(self, msg) class RespawnIcon(object): """ category: Game Flow Classes An icon with a countdown that appears alongside the screen; used to indicate that a bs.Player is waiting to respawn. """ def __init__(self, player, respawnTime): """ Instantiate with a given bs.Player and respawnTime (in milliseconds) """ activity = bs.getActivity() onRight = False self._visible = True if isinstance(bs.getSession(), bs.TeamsSession): onRight = player.getTeam().getID()%2==1 # store a list of icons in the team try: respawnIcons = (player.getTeam() .gameData['_spazRespawnIconsRight']) except Exception: respawnIcons = (player.getTeam() .gameData['_spazRespawnIconsRight']) = {} offsExtra = -20 else: onRight = False # store a list of icons in the activity try: respawnIcons = activity._spazRespawnIconsRight except Exception: respawnIcons = activity._spazRespawnIconsRight = {} if isinstance(activity.getSession(), bs.FreeForAllSession): offsExtra = -150 else: offsExtra = -20 try: maskTex = player.getTeam().gameData['_spazRespawnIconsMaskTex'] except Exception: maskTex = player.getTeam().gameData['_spazRespawnIconsMaskTex'] = \ bs.getTexture('characterIconMask') # now find the first unused slot and use that index = 0 while (index in respawnIcons and respawnIcons[index]() is not None and respawnIcons[index]()._visible): index += 1 respawnIcons[index] = weakref.ref(self) offs = offsExtra + index*-53 icon = player.getIcon() texture = icon['texture'] hOffs = -10 self._image = bs.NodeActor( bs.newNode('image', attrs={'texture':texture, 'tintTexture':icon['tintTexture'], 'tintColor':icon['tintColor'], 'tint2Color':icon['tint2Color'], 'maskTexture':maskTex, 'position':(-40-hOffs if onRight else 40+hOffs, -180+offs), 'scale':(32, 32), 'opacity':1.0, 'absoluteScale':True, 'attach':'topRight' if onRight else 'topLeft'})) bs.animate(self._image.node, 'opacity', {0:0, 200:0.7}) self._name = bs.NodeActor( bs.newNode('text', attrs={'vAttach':'top', 'hAttach':'right' if onRight else 'left', 'text':bs.Lstr(value=player.getName()), 'maxWidth':100, 'hAlign':'center', 'vAlign':'center', 'shadow':1.0, 'flatness':1.0, 'color':bs.getSafeColor(icon['tintColor']), 'scale':0.5, 'position':(-40-hOffs if onRight else 40+hOffs, -205+49+offs)})) bs.animate(self._name.node, 'scale', {0:0, 100:0.5}) self._text = bs.NodeActor( bs.newNode('text', attrs={'position':(-60-hOffs if onRight else 60+hOffs, -192+offs), 'hAttach':'right' if onRight else 'left', 'hAlign':'right' if onRight else 'left', 'scale':0.9, 'shadow':0.5, 'flatness':0.5, 'vAttach':'top', 'color':bs.getSafeColor(icon['tintColor']), 'text':''})) bs.animate(self._text.node, 'scale', {0:0, 100:0.9}) self._respawnTime = bs.getGameTime()+respawnTime self._update() self._timer = bs.Timer(1000, bs.WeakCall(self._update), repeat=True) def _update(self): remaining = int(round(self._respawnTime-bs.getGameTime())/1000.0) if remaining > 0: if self._text.node.exists(): self._text.node.text = str(remaining) else: self._clear() def _clear(self): self._visible = False self._image = self._text = self._timer = self._name = None class SpazBotPunchedMessage(object): """ category: Message Classes A bs.SpazBot got punched. Attributes: badGuy The bs.SpazBot that got punched. damage How much damage was done to the bs.SpazBot. """ def __init__(self, badGuy, damage): """ Instantiate a message with the given values. """ self.badGuy = badGuy self.damage = damage class SpazBotDeathMessage(object): """ category: Message Classes A bs.SpazBot has died. Attributes: badGuy The bs.SpazBot that was killed. killerPlayer The bs.Player that killed it (or None). how The particular type of death. """ def __init__(self, badGuy, killerPlayer, how): """ Instantiate with given values. """ self.badGuy = badGuy self.killerPlayer = killerPlayer self.how = how class SpazBot(Spaz): """ category: Bot Classes A really dumb AI version of bs.Spaz. Add these to a bs.BotSet to use them. Note: currently the AI has no real ability to navigate obstacles and so should only be used on wide-open maps. When a SpazBot is killed, it delivers a bs.SpazBotDeathMessage to the current activity. When a SpazBot is punched, it delivers a bs.SpazBotPunchedMessage to the current activity. """ character = 'Spaz' punchiness = 0.5 throwiness = 0.7 static = False bouncy = False run = False chargeDistMin = 0.0 # when we can start a new charge chargeDistMax = 2.0 # when we can start a new charge runDistMin = 0.0 # how close we can be to continue running chargeSpeedMin = 0.4 chargeSpeedMax = 1.0 throwDistMin = 5.0 throwDistMax = 9.0 throwRate = 1.0 defaultBombType = 'normal' defaultBombCount = 3 startCursed = False color=gDefaultBotColor highlight=gDefaultBotHighlight def __init__(self): """ Instantiate a spaz-bot. """ Spaz.__init__(self, color=self.color, highlight=self.highlight, character=self.character, sourcePlayer=None, startInvincible=False, canAcceptPowerups=False) # if you need to add custom behavior to a bot, set this to a callable # which takes one arg (the bot) and returns False if the bot's normal # update should be run and True if not self.updateCallback = None self._map = weakref.ref(bs.getActivity().getMap()) self.lastPlayerAttackedBy = None # FIXME - should use empty player-refs self.lastAttackedTime = 0 self.lastAttackedType = None self.targetPointDefault = None self.heldCount = 0 self.lastPlayerHeldBy = None # FIXME - should use empty player-refs here self.targetFlag = None self._chargeSpeed = 0.5*(self.chargeSpeedMin+self.chargeSpeedMax) self._leadAmount = 0.5 self._mode = 'wait' self._chargeClosingIn = False self._lastChargeDist = 0.0 self._running = False self._lastJumpTime = 0 # these cooldowns didnt exist when these bots were calibrated, # so take them out of the equation self._jumpCooldown = 0 self._pickupCooldown = 0 self._flyCooldown = 0 self._bombCooldown = 0 if self.startCursed: self.curse() def _getTargetPlayerPt(self): """ returns the default player pt we're targeting """ bp = bs.Vector(*self.node.position) closestLen = None closestVel = None for pp, pv in self._playerPts: l = (pp-bp).length() # ignore player-points that are significantly below the bot # (keeps bots from following players off cliffs) if (closestLen is None or l < closestLen) and (pp[1] > bp[1] - 5.0): closestLen = l closestVel = pv closest = pp if closestLen is not None: return (bs.Vector(closest[0], closest[1], closest[2]), bs.Vector(closestVel[0], closestVel[1], closestVel[2])) else: return None, None def _setPlayerPts(self, pts): """ Provide the spaz-bot with the locations of players. """ self._playerPts = pts def _updateAI(self): """ Should be called periodically to update the spaz' AI """ if self.updateCallback is not None: if self.updateCallback(self) == True: return # true means bot has been handled t = self.node.position ourPos = bs.Vector(t[0], 0, t[2]) canAttack = True # if we're a flag-bearer, we're pretty simple-minded - just walk # towards the flag and try to pick it up if self.targetFlag is not None: if not self.targetFlag.node.exists(): # our flag musta died :-C self.targetFlag = None return if self.node.holdNode.exists(): try: holdingFlag = (self.node.holdNode.getNodeType() == 'flag') except Exception: holdingFlag = False else: holdingFlag = False # if we're holding the flag, just walk left if holdingFlag: # just walk left self.node.moveLeftRight = -1.0 self.node.moveUpDown = 0.0 # otherwise try to go pick it up else: targetPtRaw = bs.Vector(*self.targetFlag.node.position) targetVel = bs.Vector(0, 0, 0) diff = (targetPtRaw-ourPos) diff = bs.Vector(diff[0], 0, diff[2]) # dont care about y dist = diff.length() toTarget = diff.normal() # if we're holding some non-flag item, drop it if self.node.holdNode.exists(): self.node.pickUpPressed = True self.node.pickUpPressed = False return # if we're a runner, run only when not super-near the flag if self.run and dist > 3.0: self._running = True self.node.run = 1.0 else: self._running = False self.node.run = 0.0 self.node.moveLeftRight = toTarget.x() self.node.moveUpDown = -toTarget.z() if dist < 1.25: self.node.pickUpPressed = True self.node.pickUpPressed = False return # not a flag-bearer.. if we're holding anything but a bomb, drop it else: if self.node.holdNode.exists(): try: holdingBomb = \ (self.node.holdNode.getNodeType() in ['bomb', 'prop']) except Exception: holdingBomb = False if not holdingBomb: self.node.pickUpPressed = True self.node.pickUpPressed = False return targetPtRaw, targetVel = self._getTargetPlayerPt() if targetPtRaw is None: # use default target if we've got one if self.targetPointDefault is not None: targetPtRaw = self.targetPointDefault targetVel = bs.Vector(0, 0, 0) canAttack = False # with no target, we stop moving and drop whatever we're holding else: self.node.moveLeftRight = 0 self.node.moveUpDown = 0 if self.node.holdNode.exists(): self.node.pickUpPressed = True self.node.pickUpPressed = False return # we dont want height to come into play targetPtRaw.data[1] = 0 targetVel.data[1] = 0 distRaw = (targetPtRaw-ourPos).length() # use a point out in front of them as real target # (more out in front the farther from us they are) targetPt = targetPtRaw + targetVel*distRaw*0.3*self._leadAmount diff = (targetPt-ourPos) dist = diff.length() toTarget = diff.normal() if self._mode == 'throw': # we can only throw if alive and well.. if not self._dead and not self.node.knockout: timeTillThrow = self._throwReleaseTime-bs.getGameTime() if not self.node.holdNode.exists(): # if we havnt thrown yet, whip out the bomb if not self._haveDroppedThrowBomb: self.dropBomb() self._haveDroppedThrowBomb = True # otherwise our lack of held node means we successfully # released our bomb.. lets retreat now else: self._mode = 'flee' # oh crap we're holding a bomb.. better throw it. elif timeTillThrow <= 0: # jump and throw.. def _safePickup(node): if node.exists(): self.node.pickUpPressed = True self.node.pickUpPressed = False if dist > 5.0: self.node.jumpPressed = True self.node.jumpPressed = False # throws: bs.gameTimer(100, bs.Call(_safePickup, self.node)) else: # throws: bs.gameTimer(1, bs.Call(_safePickup, self.node)) if self.static: if timeTillThrow < 300: speed = 1.0 elif timeTillThrow < 700 and dist > 3.0: speed = -1.0 # whiplash for long throws else: speed = 0.02 else: if timeTillThrow < 700: # right before throw charge full speed towards target speed = 1.0 else: # earlier we can hold or move backward for a whiplash speed = 0.0125 self.node.moveLeftRight = toTarget.x() * speed self.node.moveUpDown = toTarget.z() * -1.0 * speed elif self._mode == 'charge': if random.random() < 0.3: self._chargeSpeed = random.uniform(self.chargeSpeedMin, self.chargeSpeedMax) # if we're a runner we run during charges *except when near # an edge (otherwise we tend to fly off easily) if self.run and distRaw > self.runDistMin: self._leadAmount = 0.3 self._running = True self.node.run = 1.0 else: self._leadAmont = 0.01 self._running = False self.node.run = 0.0 self.node.moveLeftRight = toTarget.x() * self._chargeSpeed self.node.moveUpDown = toTarget.z() * -1.0*self._chargeSpeed elif self._mode == 'wait': # every now and then, aim towards our target.. # other than that, just stand there if bs.getGameTime()%1234 < 100: self.node.moveLeftRight = toTarget.x() * (400.0/33000) self.node.moveUpDown = toTarget.z() * (-400.0/33000) else: self.node.moveLeftRight = 0 self.node.moveUpDown = 0 elif self._mode == 'flee': # even if we're a runner, only run till we get away from our # target (if we keep running we tend to run off edges) if self.run and dist < 3.0: self._running = True self.node.run = 1.0 else: self._running = False self.node.run = 0.0 self.node.moveLeftRight = toTarget.x() * -1.0 self.node.moveUpDown = toTarget.z() # we might wanna switch states unless we're doing a throw # (in which case thats our sole concern) if self._mode != 'throw': # if we're currently charging, keep track of how far we are # from our target.. when this value increases it means our charge # is over (ran by them or something) if self._mode == 'charge': if (self._chargeClosingIn and dist < 3.0 and dist > self._lastChargeDist): self._chargeClosingIn = False self._lastChargeDist = dist # if we have a clean shot, throw! if (dist >= self.throwDistMin and dist < self.throwDistMax and random.random() < self.throwiness and canAttack): self._mode = 'throw' self._leadAmount = ((0.4+random.random()*0.6) if distRaw > 4.0 else (0.1+random.random()*0.4)) self._haveDroppedThrowBomb = False self._throwReleaseTime = (bs.getGameTime() + (1.0/self.throwRate) *(800 + int(1300*random.random()))) # if we're static, always charge (which for us means barely move) elif self.static: self._mode = 'wait' # if we're too close to charge (and arent in the middle of an # existing charge) run away elif dist < self.chargeDistMin and not self._chargeClosingIn: # ..unless we're near an edge, in which case we got no choice # but to charge.. if self._map()._isPointNearEdge(ourPos, self._running): if self._mode != 'charge': self._mode = 'charge' self._leadAmount = 0.2 self._chargeClosingIn = True self._lastChargeDist = dist else: self._mode = 'flee' # we're within charging distance, backed against an edge, or farther # than our max throw distance.. chaaarge! elif (dist < self.chargeDistMax or dist > self.throwDistMax or self._map()._isPointNearEdge(ourPos, self._running)): if self._mode != 'charge': self._mode = 'charge' self._leadAmount = 0.01 self._chargeClosingIn = True self._lastChargeDist = dist # we're too close to throw but too far to charge - either run # away or just chill if we're near an edge elif dist < self.throwDistMin: # charge if either we're within charge range or # cant retreat to throw self._mode = 'flee' # do some awesome jumps if we're running if ((self._running and dist > 1.2 and dist < 2.2 and bs.getGameTime()-self._lastJumpTime > 1000) or (self.bouncy and bs.getGameTime()-self._lastJumpTime > 400 and random.random() < 0.5)): self._lastJumpTime = bs.getGameTime() self.node.jumpPressed = True self.node.jumpPressed = False # throw punches when real close if dist < (1.6 if self._running else 1.2) and canAttack: if random.random() < self.punchiness: self.onPunchPress() self.onPunchRelease() def __superHandleMessage(self, msg): super(SpazBot, self).handleMessage(msg) def onPunched(self, damage): """ Method override; sends bs.SpazBotPunchedMessage to the current activity. """ bs.getActivity().handleMessage(SpazBotPunchedMessage(self, damage)) def onFinalize(self): Spaz.onFinalize(self) # we're being torn down; release # our callback(s) so there's no chance of them # keeping activities or other things alive.. self.updateCallback = None def handleMessage(self, msg): self._handleMessageSanityCheck() # keep track of if we're being held and by who most recently if isinstance(msg, bs.PickedUpMessage): self.__superHandleMessage(msg) # augment standard behavior self.heldCount += 1 pickedUpBy = msg.node.sourcePlayer if pickedUpBy is not None and pickedUpBy.exists(): self.lastPlayerHeldBy = pickedUpBy elif isinstance(msg, bs.DroppedMessage): self.__superHandleMessage(msg) # augment standard behavior self.heldCount -= 1 if self.heldCount < 0: print "ERROR: spaz heldCount < 0" # let's count someone dropping us as an attack.. try: if msg.node.exists(): pickedUpBy = msg.node.sourcePlayer else: pickedUpBy = bs.Player(None) # empty player ref except Exception as e: print 'EXC on SpazBot DroppedMessage:', e pickedUpBy = bs.Player(None) # empty player ref if pickedUpBy.exists(): self.lastPlayerAttackedBy = pickedUpBy self.lastAttackedTime = bs.getGameTime() self.lastAttackedType = ('pickedUp', 'default') elif isinstance(msg, bs.DieMessage): # report normal deaths for scoring purposes if not self._dead and not msg.immediate: # if this guy was being held at the time of death, the # holder is the killer if (self.heldCount > 0 and self.lastPlayerHeldBy is not None and self.lastPlayerHeldBy.exists()): killerPlayer = self.lastPlayerHeldBy else: # otherwise if they were attacked by someone in the # last few seconds that person's the killer.. # otherwise it was a suicide if (self.lastPlayerAttackedBy is not None and self.lastPlayerAttackedBy.exists() and bs.getGameTime() - self.lastAttackedTime < 4000): killerPlayer = self.lastPlayerAttackedBy else: killerPlayer = None activity = self._activity() if killerPlayer is not None and not killerPlayer.exists(): killerPlayer = None if activity is not None: activity.handleMessage( SpazBotDeathMessage(self, killerPlayer, msg.how)) self.__superHandleMessage(msg) # augment standard behavior # keep track of the player who last hit us for point rewarding elif isinstance(msg, bs.HitMessage): if msg.sourcePlayer is not None and msg.sourcePlayer.exists(): self.lastPlayerAttackedBy = msg.sourcePlayer self.lastAttackedTime = bs.getGameTime() self.lastAttackedType = (msg.hitType, msg.hitSubType) self.__superHandleMessage(msg) else: Spaz.handleMessage(self, msg) class BomberBot(SpazBot): """ category: Bot Classes A bot that throws regular bombs and occasionally punches. """ character='Spaz' punchiness=0.3 class BomberBotLame(BomberBot): """ category: Bot Classes A less aggressive yellow version of bs.BomberBot. """ color=gLameBotColor highlight=gLameBotHighlight punchiness = 0.2 throwRate = 0.7 throwiness = 0.1 chargeSpeedMin = 0.6 chargeSpeedMax = 0.6 class BomberBotStaticLame(BomberBotLame): """ category: Bot Classes A less aggressive yellow version of bs.BomberBot who generally stays in one place. """ static = True throwDistMin = 0.0 class BomberBotStatic(BomberBot): """ category: Bot Classes A version of bs.BomberBot who generally stays in one place. """ static = True throwDistMin = 0.0 class BomberBotPro(BomberBot): """ category: Bot Classes A more aggressive red version of bs.BomberBot. """ pointsMult = 2 color=gProBotColor highlight = gProBotHighlight defaultBombCount = 3 defaultBoxingGloves = True punchiness = 0.7 throwRate = 1.3 run = True runDistMin = 6.0 class BomberBotProShielded(BomberBotPro): """ category: Bot Classes A more aggressive red version of bs.BomberBot who starts with shields. """ pointsMult = 3 defaultShields = True class BomberBotProStatic(BomberBotPro): """ category: Bot Classes A more aggressive red version of bs.BomberBot who generally stays in one place. """ static = True throwDistMin = 0.0 class BomberBotProStaticShielded(BomberBotProShielded): """ category: Bot Classes A more aggressive red version of bs.BomberBot who starts with shields and who generally stays in one place. """ static = True throwDistMin = 0.0 class ToughGuyBot(SpazBot): """ category: Bot Classes A manly bot who walks and punches things. """ character = 'Kronk' punchiness = 0.9 chargeDistMax = 9999.0 chargeSpeedMin = 1.0 chargeSpeedMax = 1.0 throwDistMin = 9999 throwDistMax = 9999 class ToughGuyBotLame(ToughGuyBot): """ category: Bot Classes A less aggressive yellow version of bs.ToughGuyBot. """ color=gLameBotColor highlight=gLameBotHighlight punchiness = 0.3 chargeSpeedMin = 0.6 chargeSpeedMax = 0.6 class ToughGuyBotPro(ToughGuyBot): """ category: Bot Classes A more aggressive red version of bs.ToughGuyBot. """ color=gProBotColor highlight=gProBotHighlight run = True runDistMin = 4.0 defaultBoxingGloves = True punchiness = 0.95 pointsMult = 2 class ToughGuyBotProShielded(ToughGuyBotPro): """ category: Bot Classes A more aggressive version of bs.ToughGuyBot who starts with shields. """ defaultShields = True pointsMult = 3 class NinjaBot(SpazBot): """ category: Bot Classes A speedy attacking melee bot. """ character = 'Snake Shadow' punchiness = 1.0 run = True chargeDistMin = 10.0 chargeDistMax = 9999.0 chargeSpeedMin = 1.0 chargeSpeedMax = 1.0 throwDistMin = 9999 throwDistMax = 9999 pointsMult = 2 class BunnyBot(SpazBot): """ category: Bot Classes A speedy attacking melee bot. """ color=(1, 1, 1) highlight=(1.0, 0.5, 0.5) character = 'Easter Bunny' punchiness = 1.0 run = True bouncy = True defaultBoxingGloves = True chargeDistMin = 10.0 chargeDistMax = 9999.0 chargeSpeedMin = 1.0 chargeSpeedMax = 1.0 throwDistMin = 9999 throwDistMax = 9999 pointsMult = 2 class NinjaBotPro(NinjaBot): """ category: Bot Classes A more aggressive red bs.NinjaBot. """ color=gProBotColor highlight=gProBotHighlight defaultShields = True defaultBoxingGloves = True pointsMult = 3 class NinjaBotProShielded(NinjaBotPro): """ category: Bot Classes A more aggressive red bs.NinjaBot who starts with shields. """ defaultShields = True pointsMult = 4 class ChickBot(SpazBot): """ category: Bot Classes A slow moving bot with impact bombs. """ character = 'Zoe' punchiness = 0.75 throwiness = 0.7 chargeDistMax = 1.0 chargeSpeedMin = 0.3 chargeSpeedMax = 0.5 throwDistMin = 3.5 throwDistMax = 5.5 defaultBombType = 'impact' pointsMult = 2 class ChickBotStatic(ChickBot): """ category: Bot Classes A bs.ChickBot who generally stays in one place. """ static = True throwDistMin = 0.0 class ChickBotPro(ChickBot): """ category: Bot Classes A more aggressive red version of bs.ChickBot. """ color=gProBotColor highlight=gProBotHighlight defaultBombCount = 3 defaultBoxingGloves = True chargeSpeedMin = 1.0 chargeSpeedMax = 1.0 punchiness = 0.9 throwRate = 1.3 run = True runDistMin = 6.0 pointsMult = 3 class ChickBotProShielded(ChickBotPro): """ category: Bot Classes A more aggressive red version of bs.ChickBot who starts with shields. """ defaultShields = True pointsMult = 4 class MelBot(SpazBot): """ category: Bot Classes A crazy bot who runs and throws sticky bombs. """ character = 'Mel' punchiness = 0.9 throwiness = 1.0 run = True chargeDistMin = 4.0 chargeDistMax = 10.0 chargeSpeedMin = 1.0 chargeSpeedMax = 1.0 throwDistMin = 0.0 throwDistMax = 4.0 throwRate = 2.0 defaultBombType = 'sticky' defaultBombCount = 3 pointsMult = 3 class MelBotStatic(MelBot): """ category: Bot Classes A crazy bot who throws sticky-bombs but generally stays in one place. """ static = True class PirateBot(SpazBot): """ category: Bot Classes A bot who runs and explodes in 5 seconds. """ character = 'Jack Morgan' run = True chargeDistMin = 0.0 chargeDistMax = 9999 chargeSpeedMin = 1.0 chargeSpeedMax = 1.0 throwDistMin = 9999 throwDistMax = 9999 startCursed = True pointsMult = 4 class PirateBotNoTimeLimit(PirateBot): """ category: Bot Classes A bot who runs but does not explode on his own. """ curseTime = -1 class PirateBotShielded(PirateBot): """ category: Bot Classes A bs.PirateBot who starts with shields. """ defaultShields = True pointsMult = 5 class BotSet(object): """ category: Bot Classes A container/controller for one or more bs.SpazBots. """ def __init__(self): """ Create a bot-set. """ # we spread our bots out over a few lists so we can update # them in a staggered fashion self._botListCount = 5 self._botAddList = 0 self._botUpdateList = 0 self._botLists = [[] for i in range(self._botListCount)] self._spawnSound = bs.getSound('spawn') self._spawningCount = 0 self.startMoving() def __del__(self): self.clear() def spawnBot(self, botType, pos, spawnTime=3000, onSpawnCall=None): """ Spawn a bot from this set. """ bsUtils.Spawner(pt=pos, spawnTime=spawnTime, sendSpawnMessage=False, spawnCallback=bs.Call(self._spawnBot, botType, pos, onSpawnCall)) self._spawningCount += 1 def _spawnBot(self, botType, pos, onSpawnCall): spaz = botType() bs.playSound(self._spawnSound, position=pos) spaz.node.handleMessage("flash") spaz.node.isAreaOfInterest = 0 spaz.handleMessage(bs.StandMessage(pos, random.uniform(0, 360))) self.addBot(spaz) self._spawningCount -= 1 if onSpawnCall is not None: onSpawnCall(spaz) def haveLivingBots(self): """ Returns whether any bots in the set are alive or spawning. """ haveLiving = any((any((not a._dead for a in l)) for l in self._botLists)) haveSpawning = True if self._spawningCount > 0 else False return (haveLiving or haveSpawning) def getLivingBots(self): """ Returns the living bots in the set. """ bots = [] for l in self._botLists: for b in l: if not b._dead: bots.append(b) return bots def _update(self): # update one of our bot lists each time through.. # first off, remove dead bots from the list # (we check exists() here instead of dead.. we want to keep them # around even if they're just a corpse) try: botList = self._botLists[self._botUpdateList] = \ [b for b in self._botLists[self._botUpdateList] if b.exists()] except Exception: bs.printException("error updating bot list: " +str(self._botLists[self._botUpdateList])) self._botUpdateList = (self._botUpdateList+1)%self._botListCount # update our list of player points for the bots to use playerPts = [] for player in bs.getActivity().players: try: if player.isAlive(): playerPts.append((bs.Vector(*player.actor.node.position), bs.Vector(*player.actor.node.velocity))) except Exception: bs.printException('error on bot-set _update') for b in botList: b._setPlayerPts(playerPts) b._updateAI() def clear(self): """ Immediately clear out any bots in the set. """ # dont do this if the activity is shutting down or dead activity = bs.getActivity(exceptionOnNone=False) if activity is None or activity.isFinalized(): return for i in range(len(self._botLists)): for b in self._botLists[i]: b.handleMessage(bs.DieMessage(immediate=True)) self._botLists[i] = [] def celebrate(self, duration): """ Tell all living bots in the set to celebrate momentarily while continuing onward with their evil bot activities. """ for l in self._botLists: for b in l: if b.node.exists(): b.node.handleMessage('celebrate', duration) def startMoving(self): """ Starts processing bot AI updates and let them start doing their thing. """ self._botUpdateTimer = bs.Timer(50, bs.WeakCall(self._update), repeat=True) def stopMoving(self): """ Tell all bots to stop moving and stops updating their AI. Useful when players have won and you want the enemy bots to just stand and look bewildered. """ self._botUpdateTimer = None for l in self._botLists: for b in l: if b.node.exists(): b.node.moveLeftRight = 0 b.node.moveUpDown = 0 def finalCelebrate(self): """ Tell all bots in the set to stop what they were doing and just jump around and celebrate. Use this when the bots have won a game. """ self._botUpdateTimer = None # at this point stop doing anything but jumping and celebrating for l in self._botLists: for b in l: if b.node.exists(): b.node.moveLeftRight = 0 b.node.moveUpDown = 0 bs.gameTimer(random.randrange(0, 500), bs.Call(b.node.handleMessage, 'celebrate', 10000)) jumpDuration = random.randrange(400, 500) j = random.randrange(0, 200) for i in range(10): b.node.jumpPressed = True b.node.jumpPressed = False j += jumpDuration bs.gameTimer(random.randrange(0, 1000), bs.Call(b.node.handleMessage, 'attackSound')) bs.gameTimer(random.randrange(1000, 2000), bs.Call(b.node.handleMessage, 'attackSound')) bs.gameTimer(random.randrange(2000, 3000), bs.Call(b.node.handleMessage, 'attackSound')) def addBot(self, bot): """ Add a bs.SpazBot instance to the set. """ self._botLists[self._botAddList].append(bot) self._botAddList = (self._botAddList+1)%self._botListCount # define our built-in characters... ############### SPAZ ################## t = Appearance("Spaz") t.colorTexture = "neoSpazColor" t.colorMaskTexture = "neoSpazColorMask" t.iconTexture = "neoSpazIcon" t.iconMaskTexture = "neoSpazIconColorMask" t.headModel = "neoSpazHead" t.torsoModel = "neoSpazTorso" t.pelvisModel = "neoSpazPelvis" t.upperArmModel = "neoSpazUpperArm" t.foreArmModel = "neoSpazForeArm" t.handModel = "neoSpazHand" t.upperLegModel = "neoSpazUpperLeg" t.lowerLegModel = "neoSpazLowerLeg" t.toesModel = "neoSpazToes" t.jumpSounds=["spazJump01", "spazJump02", "spazJump03", "spazJump04"] t.attackSounds=["spazAttack01", "spazAttack02", "spazAttack03", "spazAttack04"] t.impactSounds=["spazImpact01", "spazImpact02", "spazImpact03", "spazImpact04"] t.deathSounds=["spazDeath01"] t.pickupSounds=["spazPickup01"] t.fallSounds=["spazFall01"] t.style = 'spaz' ############### Zoe ################## t = Appearance("Zoe") t.colorTexture = "zoeColor" t.colorMaskTexture = "zoeColorMask" t.defaultColor = (0.6, 0.6, 0.6) t.defaultHighlight = (0, 1, 0) t.iconTexture = "zoeIcon" t.iconMaskTexture = "zoeIconColorMask" t.headModel = "zoeHead" t.torsoModel = "zoeTorso" t.pelvisModel = "zoePelvis" t.upperArmModel = "zoeUpperArm" t.foreArmModel = "zoeForeArm" t.handModel = "zoeHand" t.upperLegModel = "zoeUpperLeg" t.lowerLegModel = "zoeLowerLeg" t.toesModel = "zoeToes" t.jumpSounds=["zoeJump01", "zoeJump02", "zoeJump03"] t.attackSounds=["zoeAttack01", "zoeAttack02", "zoeAttack03", "zoeAttack04"] t.impactSounds=["zoeImpact01", "zoeImpact02", "zoeImpact03", "zoeImpact04"] t.deathSounds=["zoeDeath01"] t.pickupSounds=["zoePickup01"] t.fallSounds=["zoeFall01"] t.style = 'female' ############### Ninja ################## t = Appearance("Snake Shadow") t.colorTexture = "ninjaColor" t.colorMaskTexture = "ninjaColorMask" t.defaultColor = (1, 1, 1) t.defaultHighlight = (0.55, 0.8, 0.55) t.iconTexture = "ninjaIcon" t.iconMaskTexture = "ninjaIconColorMask" t.headModel = "ninjaHead" t.torsoModel = "ninjaTorso" t.pelvisModel = "ninjaPelvis" t.upperArmModel = "ninjaUpperArm" t.foreArmModel = "ninjaForeArm" t.handModel = "ninjaHand" t.upperLegModel = "ninjaUpperLeg" t.lowerLegModel = "ninjaLowerLeg" t.toesModel = "ninjaToes" ninjaAttacks = ['ninjaAttack'+str(i+1)+'' for i in range(7)] ninjaHits = ['ninjaHit'+str(i+1)+'' for i in range(8)] ninjaJumps = ['ninjaAttack'+str(i+1)+'' for i in range(7)] t.jumpSounds=ninjaJumps t.attackSounds=ninjaAttacks t.impactSounds=ninjaHits t.deathSounds=["ninjaDeath1"] t.pickupSounds=ninjaAttacks t.fallSounds=["ninjaFall1"] t.style = 'ninja' ############### Kronk ################## t = Appearance("Kronk") t.colorTexture = "kronk" t.colorMaskTexture = "kronkColorMask" t.defaultColor = (0.4, 0.5, 0.4) t.defaultHighlight = (1, 0.5, 0.3) t.iconTexture = "kronkIcon" t.iconMaskTexture = "kronkIconColorMask" t.headModel = "kronkHead" t.torsoModel = "kronkTorso" t.pelvisModel = "kronkPelvis" t.upperArmModel = "kronkUpperArm" t.foreArmModel = "kronkForeArm" t.handModel = "kronkHand" t.upperLegModel = "kronkUpperLeg" t.lowerLegModel = "kronkLowerLeg" t.toesModel = "kronkToes" kronkSounds = ["kronk1", "kronk2", "kronk3", "kronk4", "kronk5", "kronk6", "kronk7", "kronk8", "kronk9", "kronk10"] t.jumpSounds=kronkSounds t.attackSounds=kronkSounds t.impactSounds=kronkSounds t.deathSounds=["kronkDeath"] t.pickupSounds=kronkSounds t.fallSounds=["kronkFall"] t.style = 'kronk' ############### MEL ################## t = Appearance("Mel") t.colorTexture = "melColor" t.colorMaskTexture = "melColorMask" t.defaultColor = (1, 1, 1) t.defaultHighlight = (0.1, 0.6, 0.1) t.iconTexture = "melIcon" t.iconMaskTexture = "melIconColorMask" t.headModel = "melHead" t.torsoModel = "melTorso" t.pelvisModel = "kronkPelvis" t.upperArmModel = "melUpperArm" t.foreArmModel = "melForeArm" t.handModel = "melHand" t.upperLegModel = "melUpperLeg" t.lowerLegModel = "melLowerLeg" t.toesModel = "melToes" melSounds = ["mel01", "mel02", "mel03", "mel04", "mel05", "mel06", "mel07", "mel08", "mel09", "mel10"] t.attackSounds = melSounds t.jumpSounds = melSounds t.impactSounds = melSounds t.deathSounds=["melDeath01"] t.pickupSounds = melSounds t.fallSounds=["melFall01"] t.style = 'mel' ############### Jack Morgan ################## t = Appearance("Jack Morgan") t.colorTexture = "jackColor" t.colorMaskTexture = "jackColorMask" t.defaultColor = (1, 0.2, 0.1) t.defaultHighlight = (1, 1, 0) t.iconTexture = "jackIcon" t.iconMaskTexture = "jackIconColorMask" t.headModel = "jackHead" t.torsoModel = "jackTorso" t.pelvisModel = "kronkPelvis" t.upperArmModel = "jackUpperArm" t.foreArmModel = "jackForeArm" t.handModel = "jackHand" t.upperLegModel = "jackUpperLeg" t.lowerLegModel = "jackLowerLeg" t.toesModel = "jackToes" hitSounds = ["jackHit01", "jackHit02", "jackHit03", "jackHit04", "jackHit05", "jackHit06", "jackHit07"] sounds = ["jack01", "jack02", "jack03", "jack04", "jack05", "jack06"] t.attackSounds = sounds t.jumpSounds = sounds t.impactSounds = hitSounds t.deathSounds=["jackDeath01"] t.pickupSounds = sounds t.fallSounds=["jackFall01"] t.style = 'pirate' ############### SANTA ################## t = Appearance("Santa Claus") t.colorTexture = "santaColor" t.colorMaskTexture = "santaColorMask" t.defaultColor = (1, 0, 0) t.defaultHighlight = (1, 1, 1) t.iconTexture = "santaIcon" t.iconMaskTexture = "santaIconColorMask" t.headModel = "santaHead" t.torsoModel = "santaTorso" t.pelvisModel = "kronkPelvis" t.upperArmModel = "santaUpperArm" t.foreArmModel = "santaForeArm" t.handModel = "santaHand" t.upperLegModel = "santaUpperLeg" t.lowerLegModel = "santaLowerLeg" t.toesModel = "santaToes" hitSounds = ['santaHit01', 'santaHit02', 'santaHit03', 'santaHit04'] sounds = ['santa01', 'santa02', 'santa03', 'santa04', 'santa05'] t.attackSounds = sounds t.jumpSounds = sounds t.impactSounds = hitSounds t.deathSounds=["santaDeath"] t.pickupSounds = sounds t.fallSounds=["santaFall"] t.style = 'santa' ############### FROSTY ################## t = Appearance("Frosty") t.colorTexture = "frostyColor" t.colorMaskTexture = "frostyColorMask" t.defaultColor = (0.5, 0.5, 1) t.defaultHighlight = (1, 0.5, 0) t.iconTexture = "frostyIcon" t.iconMaskTexture = "frostyIconColorMask" t.headModel = "frostyHead" t.torsoModel = "frostyTorso" t.pelvisModel = "frostyPelvis" t.upperArmModel = "frostyUpperArm" t.foreArmModel = "frostyForeArm" t.handModel = "frostyHand" t.upperLegModel = "frostyUpperLeg" t.lowerLegModel = "frostyLowerLeg" t.toesModel = "frostyToes" frostySounds = ['frosty01', 'frosty02', 'frosty03', 'frosty04', 'frosty05'] frostyHitSounds = ['frostyHit01', 'frostyHit02', 'frostyHit03'] t.attackSounds = frostySounds t.jumpSounds = frostySounds t.impactSounds = frostyHitSounds t.deathSounds=["frostyDeath"] t.pickupSounds = frostySounds t.fallSounds=["frostyFall"] t.style = 'frosty' ############### BONES ################## t = Appearance("Bones") t.colorTexture = "bonesColor" t.colorMaskTexture = "bonesColorMask" t.defaultColor = (0.6, 0.9, 1) t.defaultHighlight = (0.6, 0.9, 1) t.iconTexture = "bonesIcon" t.iconMaskTexture = "bonesIconColorMask" t.headModel = "bonesHead" t.torsoModel = "bonesTorso" t.pelvisModel = "bonesPelvis" t.upperArmModel = "bonesUpperArm" t.foreArmModel = "bonesForeArm" t.handModel = "bonesHand" t.upperLegModel = "bonesUpperLeg" t.lowerLegModel = "bonesLowerLeg" t.toesModel = "bonesToes" bonesSounds = ['bones1', 'bones2', 'bones3'] bonesHitSounds = ['bones1', 'bones2', 'bones3'] t.attackSounds = bonesSounds t.jumpSounds = bonesSounds t.impactSounds = bonesHitSounds t.deathSounds=["bonesDeath"] t.pickupSounds = bonesSounds t.fallSounds=["bonesFall"] t.style = 'bones' # bear ################################### t = Appearance("Bernard") t.colorTexture = "bearColor" t.colorMaskTexture = "bearColorMask" t.defaultColor = (0.7, 0.5, 0.0) #t.defaultHighlight = (0.6, 0.5, 0.8) t.iconTexture = "bearIcon" t.iconMaskTexture = "bearIconColorMask" t.headModel = "bearHead" t.torsoModel = "bearTorso" t.pelvisModel = "bearPelvis" t.upperArmModel = "bearUpperArm" t.foreArmModel = "bearForeArm" t.handModel = "bearHand" t.upperLegModel = "bearUpperLeg" t.lowerLegModel = "bearLowerLeg" t.toesModel = "bearToes" bearSounds = ['bear1', 'bear2', 'bear3', 'bear4'] bearHitSounds = ['bearHit1', 'bearHit2'] t.attackSounds = bearSounds t.jumpSounds = bearSounds t.impactSounds = bearHitSounds t.deathSounds=["bearDeath"] t.pickupSounds = bearSounds t.fallSounds=["bearFall"] t.style = 'bear' # Penguin ################################### t = Appearance("Pascal") t.colorTexture = "penguinColor" t.colorMaskTexture = "penguinColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "penguinIcon" t.iconMaskTexture = "penguinIconColorMask" t.headModel = "penguinHead" t.torsoModel = "penguinTorso" t.pelvisModel = "penguinPelvis" t.upperArmModel = "penguinUpperArm" t.foreArmModel = "penguinForeArm" t.handModel = "penguinHand" t.upperLegModel = "penguinUpperLeg" t.lowerLegModel = "penguinLowerLeg" t.toesModel = "penguinToes" penguinSounds = ['penguin1', 'penguin2', 'penguin3', 'penguin4'] penguinHitSounds = ['penguinHit1', 'penguinHit2'] t.attackSounds = penguinSounds t.jumpSounds = penguinSounds t.impactSounds = penguinHitSounds t.deathSounds=["penguinDeath"] t.pickupSounds = penguinSounds t.fallSounds=["penguinFall"] t.style = 'penguin' # Ali ################################### t = Appearance("Taobao Mascot") t.colorTexture = "aliColor" t.colorMaskTexture = "aliColorMask" t.defaultColor = (1, 0.5, 0) t.defaultHighlight = (1, 1, 1) t.iconTexture = "aliIcon" t.iconMaskTexture = "aliIconColorMask" t.headModel = "aliHead" t.torsoModel = "aliTorso" t.pelvisModel = "aliPelvis" t.upperArmModel = "aliUpperArm" t.foreArmModel = "aliForeArm" t.handModel = "aliHand" t.upperLegModel = "aliUpperLeg" t.lowerLegModel = "aliLowerLeg" t.toesModel = "aliToes" aliSounds = ['ali1', 'ali2', 'ali3', 'ali4'] aliHitSounds = ['aliHit1', 'aliHit2'] t.attackSounds = aliSounds t.jumpSounds = aliSounds t.impactSounds = aliHitSounds t.deathSounds=["aliDeath"] t.pickupSounds = aliSounds t.fallSounds=["aliFall"] t.style = 'ali' # cyborg ################################### t = Appearance("B-9000") t.colorTexture = "cyborgColor" t.colorMaskTexture = "cyborgColorMask" t.defaultColor = (0.5, 0.5, 0.5) t.defaultHighlight = (1, 0, 0) t.iconTexture = "cyborgIcon" t.iconMaskTexture = "cyborgIconColorMask" t.headModel = "cyborgHead" t.torsoModel = "cyborgTorso" t.pelvisModel = "cyborgPelvis" t.upperArmModel = "cyborgUpperArm" t.foreArmModel = "cyborgForeArm" t.handModel = "cyborgHand" t.upperLegModel = "cyborgUpperLeg" t.lowerLegModel = "cyborgLowerLeg" t.toesModel = "cyborgToes" cyborgSounds = ['cyborg1', 'cyborg2', 'cyborg3', 'cyborg4'] cyborgHitSounds = ['cyborgHit1', 'cyborgHit2'] t.attackSounds = cyborgSounds t.jumpSounds = cyborgSounds t.impactSounds = cyborgHitSounds t.deathSounds=["cyborgDeath"] t.pickupSounds = cyborgSounds t.fallSounds=["cyborgFall"] t.style = 'cyborg' # Agent ################################### t = Appearance("Agent Johnson") t.colorTexture = "agentColor" t.colorMaskTexture = "agentColorMask" t.defaultColor = (0.3, 0.3, 0.33) t.defaultHighlight = (1, 0.5, 0.3) t.iconTexture = "agentIcon" t.iconMaskTexture = "agentIconColorMask" t.headModel = "agentHead" t.torsoModel = "agentTorso" t.pelvisModel = "agentPelvis" t.upperArmModel = "agentUpperArm" t.foreArmModel = "agentForeArm" t.handModel = "agentHand" t.upperLegModel = "agentUpperLeg" t.lowerLegModel = "agentLowerLeg" t.toesModel = "agentToes" agentSounds = ['agent1', 'agent2', 'agent3', 'agent4'] agentHitSounds = ['agentHit1', 'agentHit2'] t.attackSounds = agentSounds t.jumpSounds = agentSounds t.impactSounds = agentHitSounds t.deathSounds=["agentDeath"] t.pickupSounds = agentSounds t.fallSounds=["agentFall"] t.style = 'agent' # Jumpsuit ################################### t = Appearance("Lee") t.colorTexture = "jumpsuitColor" t.colorMaskTexture = "jumpsuitColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "jumpsuitIcon" t.iconMaskTexture = "jumpsuitIconColorMask" t.headModel = "jumpsuitHead" t.torsoModel = "jumpsuitTorso" t.pelvisModel = "jumpsuitPelvis" t.upperArmModel = "jumpsuitUpperArm" t.foreArmModel = "jumpsuitForeArm" t.handModel = "jumpsuitHand" t.upperLegModel = "jumpsuitUpperLeg" t.lowerLegModel = "jumpsuitLowerLeg" t.toesModel = "jumpsuitToes" jumpsuitSounds = ['jumpsuit1', 'jumpsuit2', 'jumpsuit3', 'jumpsuit4'] jumpsuitHitSounds = ['jumpsuitHit1', 'jumpsuitHit2'] t.attackSounds = jumpsuitSounds t.jumpSounds = jumpsuitSounds t.impactSounds = jumpsuitHitSounds t.deathSounds=["jumpsuitDeath"] t.pickupSounds = jumpsuitSounds t.fallSounds=["jumpsuitFall"] t.style = 'spaz' # ActionHero ################################### t = Appearance("Todd McBurton") t.colorTexture = "actionHeroColor" t.colorMaskTexture = "actionHeroColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "actionHeroIcon" t.iconMaskTexture = "actionHeroIconColorMask" t.headModel = "actionHeroHead" t.torsoModel = "actionHeroTorso" t.pelvisModel = "actionHeroPelvis" t.upperArmModel = "actionHeroUpperArm" t.foreArmModel = "actionHeroForeArm" t.handModel = "actionHeroHand" t.upperLegModel = "actionHeroUpperLeg" t.lowerLegModel = "actionHeroLowerLeg" t.toesModel = "actionHeroToes" actionHeroSounds = ['actionHero1', 'actionHero2', 'actionHero3', 'actionHero4'] actionHeroHitSounds = ['actionHeroHit1', 'actionHeroHit2'] t.attackSounds = actionHeroSounds t.jumpSounds = actionHeroSounds t.impactSounds = actionHeroHitSounds t.deathSounds=["actionHeroDeath"] t.pickupSounds = actionHeroSounds t.fallSounds=["actionHeroFall"] t.style = 'spaz' # Assassin ################################### t = Appearance("Zola") t.colorTexture = "assassinColor" t.colorMaskTexture = "assassinColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "assassinIcon" t.iconMaskTexture = "assassinIconColorMask" t.headModel = "assassinHead" t.torsoModel = "assassinTorso" t.pelvisModel = "assassinPelvis" t.upperArmModel = "assassinUpperArm" t.foreArmModel = "assassinForeArm" t.handModel = "assassinHand" t.upperLegModel = "assassinUpperLeg" t.lowerLegModel = "assassinLowerLeg" t.toesModel = "assassinToes" assassinSounds = ['assassin1', 'assassin2', 'assassin3', 'assassin4'] assassinHitSounds = ['assassinHit1', 'assassinHit2'] t.attackSounds = assassinSounds t.jumpSounds = assassinSounds t.impactSounds = assassinHitSounds t.deathSounds=["assassinDeath"] t.pickupSounds = assassinSounds t.fallSounds=["assassinFall"] t.style = 'spaz' # Wizard ################################### t = Appearance("Grumbledorf") t.colorTexture = "wizardColor" t.colorMaskTexture = "wizardColorMask" t.defaultColor = (0.2, 0.4, 1.0) t.defaultHighlight = (0.06, 0.15, 0.4) t.iconTexture = "wizardIcon" t.iconMaskTexture = "wizardIconColorMask" t.headModel = "wizardHead" t.torsoModel = "wizardTorso" t.pelvisModel = "wizardPelvis" t.upperArmModel = "wizardUpperArm" t.foreArmModel = "wizardForeArm" t.handModel = "wizardHand" t.upperLegModel = "wizardUpperLeg" t.lowerLegModel = "wizardLowerLeg" t.toesModel = "wizardToes" wizardSounds = ['wizard1', 'wizard2', 'wizard3', 'wizard4'] wizardHitSounds = ['wizardHit1', 'wizardHit2'] t.attackSounds = wizardSounds t.jumpSounds = wizardSounds t.impactSounds = wizardHitSounds t.deathSounds=["wizardDeath"] t.pickupSounds = wizardSounds t.fallSounds=["wizardFall"] t.style = 'spaz' # Cowboy ################################### t = Appearance("Butch") t.colorTexture = "cowboyColor" t.colorMaskTexture = "cowboyColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "cowboyIcon" t.iconMaskTexture = "cowboyIconColorMask" t.headModel = "cowboyHead" t.torsoModel = "cowboyTorso" t.pelvisModel = "cowboyPelvis" t.upperArmModel = "cowboyUpperArm" t.foreArmModel = "cowboyForeArm" t.handModel = "cowboyHand" t.upperLegModel = "cowboyUpperLeg" t.lowerLegModel = "cowboyLowerLeg" t.toesModel = "cowboyToes" cowboySounds = ['cowboy1', 'cowboy2', 'cowboy3', 'cowboy4'] cowboyHitSounds = ['cowboyHit1', 'cowboyHit2'] t.attackSounds = cowboySounds t.jumpSounds = cowboySounds t.impactSounds = cowboyHitSounds t.deathSounds=["cowboyDeath"] t.pickupSounds = cowboySounds t.fallSounds=["cowboyFall"] t.style = 'spaz' # Witch ################################### t = Appearance("Witch") t.colorTexture = "witchColor" t.colorMaskTexture = "witchColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "witchIcon" t.iconMaskTexture = "witchIconColorMask" t.headModel = "witchHead" t.torsoModel = "witchTorso" t.pelvisModel = "witchPelvis" t.upperArmModel = "witchUpperArm" t.foreArmModel = "witchForeArm" t.handModel = "witchHand" t.upperLegModel = "witchUpperLeg" t.lowerLegModel = "witchLowerLeg" t.toesModel = "witchToes" witchSounds = ['witch1', 'witch2', 'witch3', 'witch4'] witchHitSounds = ['witchHit1', 'witchHit2'] t.attackSounds = witchSounds t.jumpSounds = witchSounds t.impactSounds = witchHitSounds t.deathSounds=["witchDeath"] t.pickupSounds = witchSounds t.fallSounds=["witchFall"] t.style = 'spaz' # Warrior ################################### t = Appearance("Warrior") t.colorTexture = "warriorColor" t.colorMaskTexture = "warriorColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "warriorIcon" t.iconMaskTexture = "warriorIconColorMask" t.headModel = "warriorHead" t.torsoModel = "warriorTorso" t.pelvisModel = "warriorPelvis" t.upperArmModel = "warriorUpperArm" t.foreArmModel = "warriorForeArm" t.handModel = "warriorHand" t.upperLegModel = "warriorUpperLeg" t.lowerLegModel = "warriorLowerLeg" t.toesModel = "warriorToes" warriorSounds = ['warrior1', 'warrior2', 'warrior3', 'warrior4'] warriorHitSounds = ['warriorHit1', 'warriorHit2'] t.attackSounds = warriorSounds t.jumpSounds = warriorSounds t.impactSounds = warriorHitSounds t.deathSounds=["warriorDeath"] t.pickupSounds = warriorSounds t.fallSounds=["warriorFall"] t.style = 'spaz' # Superhero ################################### t = Appearance("Middle-Man") t.colorTexture = "superheroColor" t.colorMaskTexture = "superheroColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "superheroIcon" t.iconMaskTexture = "superheroIconColorMask" t.headModel = "superheroHead" t.torsoModel = "superheroTorso" t.pelvisModel = "superheroPelvis" t.upperArmModel = "superheroUpperArm" t.foreArmModel = "superheroForeArm" t.handModel = "superheroHand" t.upperLegModel = "superheroUpperLeg" t.lowerLegModel = "superheroLowerLeg" t.toesModel = "superheroToes" superheroSounds = ['superhero1', 'superhero2', 'superhero3', 'superhero4'] superheroHitSounds = ['superheroHit1', 'superheroHit2'] t.attackSounds = superheroSounds t.jumpSounds = superheroSounds t.impactSounds = superheroHitSounds t.deathSounds=["superheroDeath"] t.pickupSounds = superheroSounds t.fallSounds=["superheroFall"] t.style = 'spaz' # Alien ################################### t = Appearance("Alien") t.colorTexture = "alienColor" t.colorMaskTexture = "alienColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "alienIcon" t.iconMaskTexture = "alienIconColorMask" t.headModel = "alienHead" t.torsoModel = "alienTorso" t.pelvisModel = "alienPelvis" t.upperArmModel = "alienUpperArm" t.foreArmModel = "alienForeArm" t.handModel = "alienHand" t.upperLegModel = "alienUpperLeg" t.lowerLegModel = "alienLowerLeg" t.toesModel = "alienToes" alienSounds = ['alien1', 'alien2', 'alien3', 'alien4'] alienHitSounds = ['alienHit1', 'alienHit2'] t.attackSounds = alienSounds t.jumpSounds = alienSounds t.impactSounds = alienHitSounds t.deathSounds=["alienDeath"] t.pickupSounds = alienSounds t.fallSounds=["alienFall"] t.style = 'spaz' # OldLady ################################### t = Appearance("OldLady") t.colorTexture = "oldLadyColor" t.colorMaskTexture = "oldLadyColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "oldLadyIcon" t.iconMaskTexture = "oldLadyIconColorMask" t.headModel = "oldLadyHead" t.torsoModel = "oldLadyTorso" t.pelvisModel = "oldLadyPelvis" t.upperArmModel = "oldLadyUpperArm" t.foreArmModel = "oldLadyForeArm" t.handModel = "oldLadyHand" t.upperLegModel = "oldLadyUpperLeg" t.lowerLegModel = "oldLadyLowerLeg" t.toesModel = "oldLadyToes" oldLadySounds = ['oldLady1', 'oldLady2', 'oldLady3', 'oldLady4'] oldLadyHitSounds = ['oldLadyHit1', 'oldLadyHit2'] t.attackSounds = oldLadySounds t.jumpSounds = oldLadySounds t.impactSounds = oldLadyHitSounds t.deathSounds=["oldLadyDeath"] t.pickupSounds = oldLadySounds t.fallSounds=["oldLadyFall"] t.style = 'spaz' # Gladiator ################################### t = Appearance("Gladiator") t.colorTexture = "gladiatorColor" t.colorMaskTexture = "gladiatorColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "gladiatorIcon" t.iconMaskTexture = "gladiatorIconColorMask" t.headModel = "gladiatorHead" t.torsoModel = "gladiatorTorso" t.pelvisModel = "gladiatorPelvis" t.upperArmModel = "gladiatorUpperArm" t.foreArmModel = "gladiatorForeArm" t.handModel = "gladiatorHand" t.upperLegModel = "gladiatorUpperLeg" t.lowerLegModel = "gladiatorLowerLeg" t.toesModel = "gladiatorToes" gladiatorSounds = ['gladiator1', 'gladiator2', 'gladiator3', 'gladiator4'] gladiatorHitSounds = ['gladiatorHit1', 'gladiatorHit2'] t.attackSounds = gladiatorSounds t.jumpSounds = gladiatorSounds t.impactSounds = gladiatorHitSounds t.deathSounds=["gladiatorDeath"] t.pickupSounds = gladiatorSounds t.fallSounds=["gladiatorFall"] t.style = 'spaz' # Wrestler ################################### t = Appearance("Wrestler") t.colorTexture = "wrestlerColor" t.colorMaskTexture = "wrestlerColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "wrestlerIcon" t.iconMaskTexture = "wrestlerIconColorMask" t.headModel = "wrestlerHead" t.torsoModel = "wrestlerTorso" t.pelvisModel = "wrestlerPelvis" t.upperArmModel = "wrestlerUpperArm" t.foreArmModel = "wrestlerForeArm" t.handModel = "wrestlerHand" t.upperLegModel = "wrestlerUpperLeg" t.lowerLegModel = "wrestlerLowerLeg" t.toesModel = "wrestlerToes" wrestlerSounds = ['wrestler1', 'wrestler2', 'wrestler3', 'wrestler4'] wrestlerHitSounds = ['wrestlerHit1', 'wrestlerHit2'] t.attackSounds = wrestlerSounds t.jumpSounds = wrestlerSounds t.impactSounds = wrestlerHitSounds t.deathSounds=["wrestlerDeath"] t.pickupSounds = wrestlerSounds t.fallSounds=["wrestlerFall"] t.style = 'spaz' # OperaSinger ################################### t = Appearance("Gretel") t.colorTexture = "operaSingerColor" t.colorMaskTexture = "operaSingerColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "operaSingerIcon" t.iconMaskTexture = "operaSingerIconColorMask" t.headModel = "operaSingerHead" t.torsoModel = "operaSingerTorso" t.pelvisModel = "operaSingerPelvis" t.upperArmModel = "operaSingerUpperArm" t.foreArmModel = "operaSingerForeArm" t.handModel = "operaSingerHand" t.upperLegModel = "operaSingerUpperLeg" t.lowerLegModel = "operaSingerLowerLeg" t.toesModel = "operaSingerToes" operaSingerSounds = ['operaSinger1', 'operaSinger2', 'operaSinger3', 'operaSinger4'] operaSingerHitSounds = ['operaSingerHit1', 'operaSingerHit2'] t.attackSounds = operaSingerSounds t.jumpSounds = operaSingerSounds t.impactSounds = operaSingerHitSounds t.deathSounds=["operaSingerDeath"] t.pickupSounds = operaSingerSounds t.fallSounds=["operaSingerFall"] t.style = 'spaz' # Pixie ################################### t = Appearance("Pixel") t.colorTexture = "pixieColor" t.colorMaskTexture = "pixieColorMask" t.defaultColor = (0, 1, 0.7) t.defaultHighlight = (0.65, 0.35, 0.75) t.iconTexture = "pixieIcon" t.iconMaskTexture = "pixieIconColorMask" t.headModel = "pixieHead" t.torsoModel = "pixieTorso" t.pelvisModel = "pixiePelvis" t.upperArmModel = "pixieUpperArm" t.foreArmModel = "pixieForeArm" t.handModel = "pixieHand" t.upperLegModel = "pixieUpperLeg" t.lowerLegModel = "pixieLowerLeg" t.toesModel = "pixieToes" pixieSounds = ['pixie1', 'pixie2', 'pixie3', 'pixie4'] pixieHitSounds = ['pixieHit1', 'pixieHit2'] t.attackSounds = pixieSounds t.jumpSounds = pixieSounds t.impactSounds = pixieHitSounds t.deathSounds=["pixieDeath"] t.pickupSounds = pixieSounds t.fallSounds=["pixieFall"] t.style = 'pixie' # Robot ################################### t = Appearance("Robot") t.colorTexture = "robotColor" t.colorMaskTexture = "robotColorMask" t.defaultColor = (0.3, 0.5, 0.8) t.defaultHighlight = (1, 0, 0) t.iconTexture = "robotIcon" t.iconMaskTexture = "robotIconColorMask" t.headModel = "robotHead" t.torsoModel = "robotTorso" t.pelvisModel = "robotPelvis" t.upperArmModel = "robotUpperArm" t.foreArmModel = "robotForeArm" t.handModel = "robotHand" t.upperLegModel = "robotUpperLeg" t.lowerLegModel = "robotLowerLeg" t.toesModel = "robotToes" robotSounds = ['robot1', 'robot2', 'robot3', 'robot4'] robotHitSounds = ['robotHit1', 'robotHit2'] t.attackSounds = robotSounds t.jumpSounds = robotSounds t.impactSounds = robotHitSounds t.deathSounds=["robotDeath"] t.pickupSounds = robotSounds t.fallSounds=["robotFall"] t.style = 'spaz' # Bunny ################################### t = Appearance("Easter Bunny") t.colorTexture = "bunnyColor" t.colorMaskTexture = "bunnyColorMask" t.defaultColor = (1, 1, 1) t.defaultHighlight = (1, 0.5, 0.5) t.iconTexture = "bunnyIcon" t.iconMaskTexture = "bunnyIconColorMask" t.headModel = "bunnyHead" t.torsoModel = "bunnyTorso" t.pelvisModel = "bunnyPelvis" t.upperArmModel = "bunnyUpperArm" t.foreArmModel = "bunnyForeArm" t.handModel = "bunnyHand" t.upperLegModel = "bunnyUpperLeg" t.lowerLegModel = "bunnyLowerLeg" t.toesModel = "bunnyToes" bunnySounds = ['bunny1', 'bunny2', 'bunny3', 'bunny4'] bunnyHitSounds = ['bunnyHit1', 'bunnyHit2'] t.attackSounds = bunnySounds t.jumpSounds = ['bunnyJump'] t.impactSounds = bunnyHitSounds t.deathSounds=["bunnyDeath"] t.pickupSounds = bunnySounds t.fallSounds=["bunnyFall"] t.style = 'bunny'