-
Notifications
You must be signed in to change notification settings - Fork 8
/
player.py
393 lines (352 loc) · 14.7 KB
/
player.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# -*- coding: utf-8 -*-
# Screaming Strike player handler class
# Copyright (C) 2019 Yukio Nozawa <[email protected]>
# License: GPL V2.0 (See copying.txt for details)
import bgtsound
import random
import bonusCounter
import enemy
import globalVars
import itemConstants
import itemEffects
import window
DEFAULT_PUNCH_RANGE = 4
DEFAULT_PUNCH_SPEED = 200
class Player():
"""This class represents a player."""
def initialize(self, field):
"""Initializes this player.
:param field: The field instance on which this player should be bound.
:type field: gameField.GameField
"""
self.paused = False
self.field = field
self.lives = 3
self.x = field.getCenterPosition()
self.punchTimer = window.Timer()
self.punching = False
self.punchSpeed = DEFAULT_PUNCH_SPEED
self.punchRange = DEFAULT_PUNCH_RANGE
self.score = 0
self.score_validator = []
self.punches = 0
self.hits = 0
self.hitPercentage = 0
self.consecutiveHitBonus = bonusCounter.BonusCounter()
self.consecutiveHitBonus.initialize()
self.consecutiveMissUnbonus = bonusCounter.BonusCounter()
self.consecutiveMissUnbonus.initialize()
self.consecutiveHits = 0
self.consecutiveMisses = 0
self.itemEffects = []
self.penetrate = False
self.autoDestructionRemaining = 0
self.lastHighscore = globalVars.appMain.statsStorage.get("hs_" + self.field.modeHandler.getName())
self.gotHighscore = False
def frameUpdate(self):
"""Call this method once per frame to keep everything updated."""
self.consecutiveHitBonus.frameUpdate()
self.consecutiveMissUnbonus.frameUpdate()
if self.punching is False and globalVars.appMain.keyPressed(window.K_SPACE):
self.punchLaunch()
if self.punching is True and self.punchTimer.elapsed >= self.punchSpeed:
self.punchHit()
if self.x != 0 and globalVars.appMain.keyPressed(window.K_LEFT):
self.moveTo(self.x - 1)
if self.x != self.field.getX() - 1 and globalVars.appMain.keyPressed(window.K_RIGHT):
self.moveTo(self.x + 1)
for elem in self.itemEffects[:]:
if not elem.frameUpdate(self.field.modeHandler):
self.itemEffects.remove(elem)
def punchLaunch(self):
"""Launches a punch. If this player is already punching, this method does nothing. """
if self.punching:
return
self.punches += 1
self.punching = True
s = bgtsound.sound()
s.load(globalVars.appMain.sounds["fists.ogg"])
s.pan = self.field.getPan(self.x)
s.pitch = int(DEFAULT_PUNCH_SPEED / self.punchSpeed * 100) + random.randint(-10, 10)
s.play()
self.punchTimer.restart()
def punchHit(self):
"""Process punch hits. Called from frameUpdate as it is needed."""
self.punching = False
hit = 0
for pos in range(int(self.punchRange) + 1):
for elem in self.field.enemies:
if elem is not None and elem.state == enemy.STATE_ALIVE and self.x == elem.x and elem.y == pos:
elem.hit()
self.field.modeHandler.onEnemyDefeated()
hit += 1
self.hits += 1
self.consecutiveHits += 1
self.processConsecutiveMisses()
self.calcHitPercentage() # penetration would higher the percentage, but I don't care
self.field.logDefeat()
if not self.penetrate:
break
# end if
# end for enemies
if not self.penetrate and hit > 0:
break
for elem in self.field.items:
if elem.state == itemConstants.STATE_ALIVE and self.x == elem.x and elem.y == pos:
if globalVars.appMain.keyPressing(window.K_UP):
elem.punch()
else:
elem.obtain()
self.field.modeHandler.onItemObtained()
self.processItemHit(elem)
# end item hit
hit = True
self.hits += 1
self.consecutiveHits += 1
self.processConsecutiveMisses()
self.calcHitPercentage()
if not self.penetrate:
break
# end if
# end for items
# end for range
if not hit:
self.punchMiss()
# end punchHit
def punchMiss(self):
"""Processes punch miss. Called from punchHit."""
self.consecutiveMisses += 1
if self.hits > 0:
self.calcHitPercentage()
self.processConsecutiveHits()
def processConsecutiveHits(self):
"""Processes consecutive hits bonus. Called from punchHit."""
if not self.field.modeHandler.allowConsecutiveHitsBonus:
return
if self.consecutiveHits > 5:
self.field.log(_("%(hits)d consecutive hits bonus!") % {"hits": self.consecutiveHits})
self.addScore(self.consecutiveHits * self.consecutiveHits * self.field.level * self.field.level)
self.consecutiveHitBonus.start(self.consecutiveHits)
# end if
self.consecutiveHits = 0
def processConsecutiveMisses(self):
"""Processes consecutive misses penalty. Called from punchHit."""
if not self.field.modeHandler.allowConsecutiveMissesBonus:
return
if self.consecutiveMisses > 5:
self.field.log(_("%(misses)d consecutive misses penalty!") % {"misses": self.consecutiveMisses})
self.addScore(self.consecutiveMisses * self.consecutiveMisses * self.field.level * self.field.level * -1)
self.consecutiveMissUnbonus.start(self.consecutiveMisses * -1)
# end if
self.consecutiveMisses = 0
def processItemHit(self, it):
"""
Processes hitting an item. Called from punchHit. This method just checks the item is good or nasty, then calls the specific methods.
:param it: Item instance which got hit.
:type it: item.Item
"""
if it.type == itemConstants.TYPE_NASTY:
self.processNastyItemHit(it)
else:
self.processGoodItemHit(it)
def processNastyItemHit(self, it):
"""
Processes a nasty item hit. Called from processItemHit.
:param it: Item instance which got hit.
:type it: item.Item
"""
if it.identifier == itemConstants.NASTY_SHRINK:
# When player's punching range cannot be decreased further, find an existing shrink effect and extend the effect expiration.
self.processShrinkItemEffect()
return
if it.identifier == itemConstants.NASTY_BLURRED:
e = itemEffects.BlurredEffect()
e.initialize(self)
e.activate(self.field.modeHandler)
self.itemEffects.append(e)
return
if it.identifier == itemConstants.NASTY_SLOWDOWN:
e = itemEffects.SlowDownEffect()
e.initialize(self)
e.activate(self.field.modeHandler)
self.itemEffects.append(e)
return
def processShrinkItemEffect(self):
"""
Processes an edge case where player's punching range is 1 and cannot be decreased further.
In this case, find an existing shrink effect, which will be expired next.
Extend the found shrink effect's expiration.
Otherwise, normally process the shrink effect.
"""
if self.punchRange == 1:
shrinks = [e for e in self.itemEffects if e.name == itemConstants.NAMES[itemConstants.TYPE_NASTY]
[itemConstants.NASTY_SHRINK]] # length should never be 0
shrinks.sort(key=lambda e: e.calculateTimeRemaining())
shrinks[0].extend(itemConstants.BASE_EFFECT_TIME)
return
# normal
e = itemEffects.ShrinkEffect()
e.initialize(self)
e.activate(self.field.modeHandler)
self.itemEffects.append(e)
def processGoodItemHit(self, it):
"""
Processes a good item hit. Called from processItemHit.
:param it: Item instance which got hit.
:type it: item.Item
"""
if it.identifier == itemConstants.GOOD_MEGATONPUNCH:
existing = self.findEffect("Megaton punch")
if existing is None:
e = itemEffects.MegatonPunchEffect()
e.initialize(self)
e.activate(self.field.modeHandler)
self.itemEffects.append(e)
else:
existing.extend(itemConstants.BASE_EFFECT_TIME)
return
if it.identifier == itemConstants.GOOD_BOOST:
e = itemEffects.BoostEffect()
e.initialize(self)
e.activate(self.field.modeHandler)
self.itemEffects.append(e)
return
if it.identifier == itemConstants.GOOD_PENETRATION:
existing = self.findEffect("Penetration")
if existing is None:
e = itemEffects.PenetrationEffect()
e.initialize(self)
e.activate(self.field.modeHandler)
self.itemEffects.append(e)
else:
existing.extend(itemConstants.BASE_EFFECT_TIME)
return
if it.identifier == itemConstants.GOOD_DESTRUCTION:
ok = self.field.startDestruction()
if not ok: # Already destructing
self.autoDestructionRemaining += 1
self.field.log(_("This effect will be used when it's necessary! (Remaining %(r)d)") % {"r": self.autoDestructionRemaining})
return
if it.identifier == itemConstants.GOOD_EXTRALIFE:
self.lives += 1
self.field.log(_("Extra life! (now %(lives)d lives)") % {"lives": self.lives})
s = bgtsound.sound()
s.load(globalVars.appMain.sounds["extraLife.ogg"])
s.pitch = 60 + (self.lives * 10)
s.play()
return
def findEffect(self, name):
"""
Checks whether this player has the specified effect currently active.
:param name: Name of the effect to search(Blurred, Megaton Punch, etc).
:type name: str
:rtype: bool
"""
for elem in self.itemEffects:
if elem.name == name:
return elem
return None
def setPunchRange(self, r):
"""
Changes this player's effective punch length.
:param r: New range.
:type r: int
"""
previous = self.punchRange
self.field.log(_("The effective range of your Punch is now %(range)d (from %(from)d)") % {"range": r, "from": previous})
self.punchRange = r
def setPunchSpeed(self, s):
"""
Changes this player's punching speed.
:param s: New speed in milliseconds.
:type s: int
"""
previous = self.punchSpeed
self.field.log(_("The speed of your punch is now %(speed)d milliseconds (from %(from)d)") % {"speed": s, "from": previous})
self.punchSpeed = s
def setPenetration(self, p):
"""
Sets whether this player's punches penetrate.
:param p: Penetrate?
:type p: bool
"""
if p is True:
self.field.log(_("Your punches now penetrate enemies and items!"))
else:
self.field.log(_("Your punches no longer penetrate enemies and items!"))
self.penetrate = p
def calcHitPercentage(self):
"""Calculates the hitting percentage of this player and updates the hitPercentage property."""
self.hitPercentage = self.hits / self.punches * 100
def moveTo(self, p):
"""
Moves this player to the specified position.
:param p: New position.
:type p: int
"""
self.x = p
s = bgtsound.sound()
s.load(globalVars.appMain.sounds["change.ogg"])
s.pan = self.field.getPan(self.x)
s.play()
def hit(self):
"""Called when this player gets hit by one of the enemies. Returns true when the attack succeeds, or False if player attacks back with auto destruction."""
if self.autoDestructionRemaining > 0:
self.autoDestructionRemaining -= 1
self.field.log(_("You're about to be attacked, but you have a counter!"))
self.field.startDestruction()
return False
# end auto destruction
self.lives -= 1
self.field.log(_("You've been slapped! (%(lives)d HP remaining)") % {"lives": self.lives})
s = bgtsound.sound()
if self.lives > 0:
s.load(globalVars.appMain.sounds["attacked.ogg"])
s.play()
else:
self.field.log(_("Game over!"))
s.load(globalVars.appMain.sounds["gameover.ogg"])
s.volume = -10
s.play()
return False
def addScore(self, score):
"""
Add a specified amount of score to this player. Also checks for high score.
:param score: Score to add.
:type score: int
"""
self.score += score
self.score_validator.append(score)
if self.gotHighscore is False and self.score >= self.lastHighscore:
self.processHighscore()
which = _("added")
if score <= 0:
which = _("subtracted")
self.field.log(_("Point: %(added).1f %(changestr)s (%(total).1f)") % {"added": score, "changestr": which, "total": self.score})
# end addScore
def processHighscore(self):
"""Plays the highscore sound and logs that this player has achieved high score."""
bgtsound.playOneShot(globalVars.appMain.sounds["highscore.ogg"])
self.gotHighscore = True
# end processHighscore
def getNewHighscore(self):
"""Returns the new high score if this player got one, otherwise None."""
return None if self.gotHighscore is False else int(self.score)
# end getNewHighscore
def getPreviousHighscore(self):
"""Retrieves the previous high score. If this player has achieved a new high score, the previous one is returned. Otherwise, the currently standing highscore is returned.
:rtype: int
"""
return self.lastHighscore
# end getPreviousHighscore
def setPaused(self, p):
"""Pauses this player."""
if p == self.paused:
return
self.paused = p
for elem in self.itemEffects:
elem.setPaused(p)
# end item effects
self.punchTimer.setPaused(p)
# end pause
# end class Player