1 import cocos
2 import cocos.euclid as eu
3 import utils
4 import math
5 import twisted
6 import random
7 from twisted.internet.endpoints import TCP4ClientEndpoint, TCP4ServerEndpoint
8 from twisted.python.log import err
9 from twisted.internet.error import *
10 from twisted.internet import reactor
11
12 from cocos import collision_model
13 from cocos.director import director
14 from cocos.layer import Layer
15 from cocos.layer.scrolling import ScrollingManager
16 from cocos.actions.instant_actions import CallFuncS
17 from cocos.actions.interval_actions import Delay
18
19
20 from pyglet.event import EventDispatcher
21 from pyglet.window import key
22
23 import constants
24 from commands import *
25 from constants import *
26 from imageLayer import ImageLayer
27 from maps import Map, Vertex
28 from models import *
29 from player_new import Player
30
31 from utils import get_action_button_clicked
32 from playerai import ComputerPlayer
33 from research import RESEARCH
34 from game_layers import StatusMenu, InfoLayer, SettingsLayer,ActionButton,TransTimer, MenuButton, HotkeysLayer, StickyNote, TutorialXButton
35 import music
36 from game import EndMenu
37 from tutorial import *
38 from server import ClientNoteFactory
39 from client import ServerNoteFactory
40
42 is_event_handler = True
43
45 super(ClientController, self).__init__()
46
47 if mapName:
48 self.map = self._init_map_and_cm(mapName)
49 self._add_tutorial(mapName)
50 self.tutorial = None
51
52 self.curLevel = 1
53
54 self.curAction = None
55
56 self.selectedUnits = []
57
58 self.players = {}
59
60 self.player = None
61
62 self.playerList = []
63
64 self.pid = -1
65
66 self.arrow_flags = {key.LEFT: 0, key.RIGHT: 0, key.UP: 0, key.DOWN: 0}
67
68 self.mouse_flags = {"x": 0, "y": 0}
69
70 self.ip = ""
71
72 self.serverStarted = False
73
74 self.connectedToServer = False
75
76 self.endpoint = None
77
78 self.visibleASes = set()
79
80 self.isControlDown = False
81
82 self.hotKeyedUnits = {
83 key._0 : None,
84 key._1 : None,
85 key._2 : None,
86 key._3 : None,
87 key._4 : None,
88 key._5 : None,
89 key._6 : None,
90 key._7 : None,
91 key._8 : None,
92 key._9 : None
93 }
94
96 if mapName == "level1":
97 self.tutorial = Tutorial(1,self)
98 return
99 elif mapName[:-1] == "level" and mapName[-1] in [str(i) for i in range(2,6)]:
100
101 self.stickyNote = StickyNote(mapName[-1])
102 self.stickyXButton = TutorialXButton(self.stickyNote)
103 self.map.add(self.stickyNote)
104 self.map.add(self.stickyXButton)
105 self.cm.add(self.stickyXButton)
106
108 if mapName == "srandom" or mapName == "sRandom" or mapName == "sr" or mapName == "sR":
109
110 self.mapName = str(random.randint(100,10000))
111 m = Map("random", numPlayers = 2, AIPlayers = ["1"], seed = int(self.mapName))
112 elif mapName == "random" or mapName == "Random" or mapName == "r" or mapName == "R":
113
114 self.mapName = str(random.randint(100,10000))
115 m = Map("random", numPlayers = 2, seed = int(self.mapName))
116 elif mapName.isdigit():
117 m = Map("random", numPlayers = 2, seed = int(mapName))
118 self.mapName = mapName
119 else:
120 m = Map(os.path.join("maps", mapName + ".map"))
121 self.mapName = mapName
122
123 self.cm = collision_model.CollisionManagerGrid(
124 -BLEED, m.w * CELL_SIZE + BLEED, -BLEED, m.h * CELL_SIZE + BLEED, CELL_SIZE / 2, CELL_SIZE / 2)
125 m.cm = self.cm
126 self.scroller = ScrollingManager(viewport=director.window)
127 self.scroller.add(m)
128
129 self.h = CELL_SIZE * m.h
130 if self.h < WINDOW_HEIGHT:
131 self.h = WINDOW_HEIGHT
132
133 self.w = CELL_SIZE * m.w
134 if self.w < WINDOW_WIDTH:
135 self.w = WINDOW_WIDTH
136
137 return m
138
140 super(ClientController, self).on_enter()
141 if constants.MUSIC:
142 music.theme_player.play()
143 if self.tutorial:
144 self.tutorial.first_prompt("on_enter")
145 self.push_handlers(
146 self.tutorial.player_add_unit, self.tutorial.player_unit_attack)
147 self.push_handlers(
148 self.tutorial.click_on_move, self.tutorial.click_on_action)
149 self.schedule(self.step)
150 self.infoLayer = InfoLayer(self.map, self.scroller, self.pid)
151 self.settingsMenu = SettingsLayer()
152 self.statusMenu = StatusMenu(self.settingsMenu,self.player)
153 self.hotkeysLayer = HotkeysLayer(self.hotKeyedUnits,self.scroller)
154 self.hotkeysLayer.toggle_hotkeys_menu()
155 self.add(self.statusMenu,z=1)
156 self.add(self.infoLayer,z=1)
157 self.add(self.settingsMenu,z=2)
158 self.add(self.hotkeysLayer,z=2)
159
160
162 self.stop_server()
163 if constants.MUSIC:
164 music.theme_player.stop()
165
166 for child in self.map.get_children():
167 child.stop()
168
169 if didWin:
170 s = cocos.scene.Scene(EndMenu("Won",self.curLevel))
171 else:
172 s = cocos.scene.Scene(EndMenu("Lost",self.curLevel))
173 s.add(ImageLayer(os.path.join("images", "backgrounds", "menu-chalkboard.png")),z=-1)
174 cocos.director.director.replace(s)
175
176
178 try:
179 self.serverStarted = False
180 d = self.endpoint.stopListening()
181 d.addBoth(err, err)
182 except:
183 pass
184
185 - def step(self, dt):
186
187 if self.mouse_flags["x"] == 0 and self.mouse_flags["y"] == 0:
188
189 buttons = self.arrow_flags
190 move_dir = eu.Vector2(buttons[key.RIGHT] - buttons[key.LEFT],
191 buttons[key.UP] - buttons[key.DOWN])
192 else:
193
194 move_dir = eu.Vector2(self.mouse_flags['x'], self.mouse_flags['y'])
195 newPos = move_dir.normalize() * dt * MAP_MOUSE_SCROLL_SPEED
196 newx, newy = self.clamp(newPos)
197 self.scroller.set_focus(newx, newy)
198
212
213 '''
214 Methods responding to local events
215 '''
234
236 if k == key.ESCAPE:
237 self.end_game(False)
238 return True
239
240
241 if k == key.N and self.selectedUnits != []:
242 vert = self.selectedUnits[0].curVertex
243 if len(vert.troopSlots) > 1 or (issubclass(type(self.selectedUnits[0]), Building) and len(vert.troopSlots) > 0):
244 if (issubclass(type(self.selectedUnits[0]), Building)):
245
246 getNextTroop = True
247 else:
248 getNextTroop = False
249
250 i = 0
251 troopSlotIndex = vert.troopSlots.keys()[i]
252 haveNotChangedTroop = True
253
254
255 while haveNotChangedTroop:
256 troop = vert.troopSlots[troopSlotIndex].troop
257 if troop != None:
258 if getNextTroop and troop == self.selectedUnits[0]:
259
260 break
261 elif getNextTroop and troop.pid == self.selectedUnits[0].pid and troop != self.selectedUnits[0]:
262
263 self._deselect_all_units()
264 troop.set_is_selected(True, self.map, self.cm, self.player)
265 self.selectedUnits = [troop]
266 break
267 elif troop.isSelected:
268 getNextTroop = True
269
270
271 i = (i + 1) % len(vert.troopSlots.keys())
272 troopSlotIndex = vert.troopSlots.keys()[i]
273
274
275 if k == key.B and self.selectedUnits != []:
276 vert = self.selectedUnits[0].curVertex
277 if vert.building != None and vert.building.pid == self.selectedUnits[0].pid and vert.building != self.selectedUnits[0]:
278 self._deselect_all_units()
279 vert.building.set_is_selected(True, self.map, self.cm, self.player)
280 self.selectedUnits = [vert.building]
281
282
283 if k == key.LCTRL or k == key.RCTRL:
284
285 self.isControlDown = True
286
287
288 if k in self.hotKeyedUnits.keys():
289 if self.isControlDown:
290
291 if len(self.selectedUnits) > 0:
292
293 self.hotKeyedUnits[k] = self.selectedUnits[0]
294 self.hotkeysLayer.update_hotkeys_menu()
295
296 elif self.hotKeyedUnits[k] != None:
297
298
299 if issubclass(type(self.hotKeyedUnits[k]), Unit) or self.hotKeyedUnits[k].isSelectable:
300 self._deselect_all_units()
301 self.hotKeyedUnits[k].set_is_selected(True, self.map, self.cm, self.player)
302 self.selectedUnits = [self.hotKeyedUnits[k]]
303 self.hotkeysLayer.update_hotkeys_menu()
304 self._set_focus_to_unit(self.hotKeyedUnits[k])
305
306
307
308 if k in self.arrow_flags.keys():
309 self.arrow_flags[k] = 1
310 return True
311
312 return False
313
315
316
317 selType = None
318
319
320 if k in self.arrow_flags.keys():
321 self.arrow_flags[k] = 0
322
323 if k == key.LCTRL or k == key.RCTRL:
324
325 self.isControlDown = False
326
327
328 if k == key.BACKSPACE:
329 if len(self.selectedUnits) > 0:
330 temp = list(self.selectedUnits)
331 self._deselect_all_units()
332 for unit in temp:
333 utils.play_sound("delete.wav")
334 unit.destroy_action()
335
336 if len(self.selectedUnits) > 0:
337 selType = type(self.selectedUnits[0])
338 actNum = None
339 if selType and issubclass(selType, Troop):
340 actNum = TROOP_HOTKEYS.get(k, None)
341 elif selType and issubclass(selType, Building):
342 actNum = BUILDING_HOTKEYS.get(k, None)
343
344 if actNum != None:
345 for unit in self.selectedUnits:
346 if actNum < len(unit.actionList):
347 self._deselect_all_units()
348 self.execute_action(unit.get_action_list(self.player)[actNum], unit)
349 break
350
351
352 return True
353
355
356 x, y = self.scroller.pixel_from_screen(x, y)
357 if self.infoLayer.miniMapToggled and self.infoLayer.miniMap.cshape.touches_point(x,y):
358 return
359
360
361
362
363
364 clicked_units = self.cm.objs_touching_point(x, y)
365 temp_units = []
366 actionButton = None
367
368 for unit in clicked_units:
369 if type(unit) == ActionButton:
370 actionButton = unit
371 break
372 if unit.opacity == 255:
373 if issubclass(type(unit),Unit) and not unit.isSelectable:
374 continue
375 temp_units.append(unit)
376 if unit == self.stickyXButton:
377
378
379 self.map.remove(self.stickyNote)
380 self.map.remove(self.stickyXButton)
381 self.cm.remove_tricky(self.stickyXButton)
382 utils.play_sound("delete.wav")
383
384 if self.stickyNote.levelFiveCounter in [1,2]:
385
386 self.stickyNote = StickyNote("5", self.stickyNote.levelFiveCounter)
387 self.stickyXButton = TutorialXButton(self.stickyNote)
388 self.map.add(self.stickyNote)
389 self.map.add(self.stickyXButton)
390 self.cm.add(self.stickyXButton)
391
392 clicked_units = temp_units[:1]
393
394 if buttons == 1:
395 if self.curAction:
396 if self.curAction == "Shake":
397 for unit in list(clicked_units):
398 if type(unit) == Handshake and unit.pid == self.pid:
399 self.hand_shake(unit,self.selectedUnits.pop())
400
401 elif self.curAction == "Attack":
402 for unit in list(clicked_units):
403 if (issubclass(type(unit), Unit) and unit.pid != self.pid) or issubclass(type(unit), Vertex):
404 if self.selectedUnits:
405 self.dispatch_event("player_unit_attack", self.selectedUnits[0])
406 attacker = self.selectedUnits.pop()
407 if type(self) == ClientController:
408 self.server_attack_unit(attacker,unit)
409 else:
410 self.attack_unit(unit,attacker)
411
412 self.curAction = None
413 return
414
415 self._deselect_all_units()
416 if actionButton:
417 self.execute_action(actionButton.name, actionButton.unitParent)
418 else:
419 self._select_units(clicked_units)
420
421 if buttons == 4:
422 if not self.selectedUnits:
423 return
424 for unit in list(clicked_units):
425 if type(unit) == Vertex:
426 self._move_selected_units(unit)
427 return
428
433
435 if actionName[0] == "B":
436 if actionName[1:] == "CPU":
437 for a in self.visibleASes:
438 if self.map.AS[a].cores:
439 k, core = self.map.AS[a].cores.popitem()
440 self.map.AS[a].usedCores[k] = core
441 self.server_build_unit(actionName[1:],core.vid, unit)
442 break
443 else:
444 self.server_build_unit(actionName[1:], unit.curVertex.vid, unit)
445 elif actionName[0] == "T":
446 self.server_build_unit(actionName[1:], unit.curVertex.vid, None)
447 elif actionName == "DEL":
448 self.server_remove_unit(unit)
449 elif actionName[0] == "R":
450 self.perform_research(actionName, unit)
451 elif actionName == "Ping":
452 unit.ping(self.server_remove_unit)
453 elif actionName == "UPingOfDeath":
454 self.upgrade_unit(unit,actionName[1:])
455 elif actionName == "UNMap":
456 self.upgrade_unit(unit,actionName[1:])
457 elif actionName == "USinkhole":
458 self.upgrade_unit(unit,actionName[1:])
459 elif actionName == "Shake":
460 self.curAction = "Shake"
461 self.selectedUnits = [unit]
462 elif actionName == "Attack" or actionName == "Decimate" or actionName == "Inject":
463 self.curAction = "Attack"
464 self.selectedUnits = [unit]
465 elif actionName == "Decrypt":
466 self.upgrade_unit(unit,unit.originalType)
467
468 '''
469 Methods responding to local/network events
470 '''
471
473
474 unit.curVertex = vertex
475 if vertex.vid == unit.destVertex.vid:
476 slot = vertex.add_troop(unit)
477
478 if slot:
479 action = MoveTo(slot.position, 0.2)
480 action += CallFuncS(self._update_cshape, slot.position)
481 action += CallFuncS(self.cm.add)
482 unit.do(action)
483
484
485 - def build_unit(self, tid, owner, vid=-1, uid=-1,onInit=False):
494
497
505
507 newResearch.on_completion(owner)
508 '''
509 #DEPRECATED
510 owner.completedResearch *= newResearch.uid
511 for s in self.map.availResearch:
512 research = RESEARCH[s]
513 if (owner.completedResearch % research.dependencies) == 0 and (owner.completedResearch % research.uid != 0) and (s not in owner.availableResearch):
514 # first cond ensures we have met dependencies
515 # second cond ensures we haven't done the research already
516 owner.availableResearch.append(s)
517 '''
518
520 dest = self.map.vertices[str(path[-1])]
521 if dest != unit.destVertex:
522 unit.curVertex.remove_troop(unit)
523 unit.destVertex = dest
524 self.cm.remove_tricky(unit)
525
526
527 if unit.pid == self.pid:
528 utils.play_sound("Move.wav")
529 action = MoveTo(unit.curVertex.position, 0.2)
530
531
532 for i in range(1, len(path)):
533 vertex = self.map.vertices[str(path[i])]
534 action += MoveTo(vertex.position, 1 / unit.speed)
535 unit.do(action)
536
538 unit.cshape.center = position
539
542
543 '''
544 Twisted client methods
545 '''
546
548 from twisted.internet import reactor
549 endpoint = TCP4ClientEndpoint(reactor, SERVER_IP, 8750)
550 factory = ClientNoteFactory()
551 return endpoint.connect(factory)
552
558
560
561 d = self.connect_end_point()
562 def c(ampProto):
563 if type(target) == Vertex:
564 return ampProto.callRemote(AttackAnimation, pid=attacker.pid,uid=attacker.uid,tpid=int(target.vid),tuid=-1,path=[])
565 else:
566 return ampProto.callRemote(AttackAnimation, pid=attacker.pid,uid=attacker.uid,tpid=target.pid,tuid=target.uid,path=[])
567
568 d.addCallback(c)
569 d.addErrback(err)
570 reactor.callLater(10, d.cancel)
571
572
574 d = self.connect_end_point()
575 def c(ampProto):
576 if builder:
577 u = int(builder.uid)
578 return ampProto.callRemote(BuildUnit, pid=self.pid, tid=unitName, vid=vid, uid=-1,buid=u)
579 else:
580 return ampProto.callRemote(BuildUnit, pid=self.pid, tid=unitName, vid=vid, uid=-1,buid=-1)
581 d.addCallback(c)
582 d.addErrback(err)
583 reactor.callLater(10, d.cancel)
584
590 d.addCallback(c)
591 d.addErrback(err)
592 reactor.callLater(10, d.cancel)
593
600 d.addCallback(c)
601 d.addErrback(err)
602 reactor.callLater(10, d.cancel)
603
604
606 if self.ip:
607 return False
608 self.ip = str(utils.get_ip())
609 if (not self.ip) or self.ip == SERVER_IP or self.connectedToServer:
610 print "no connection to the network"
611 return False
612 d = self.connect_end_point()
613 def c(ampProto):
614 return ampProto.callRemote(Connect, ip=self.ip)
615 d.addCallback(c)
616 d.addErrback(err)
617 reactor.callLater(10, d.cancel)
618
619
620 def connected_server(args):
621 pid = args['id']
622 otherPids = args['cur']
623 mapName = args['map']
624
625 if pid != -1:
626 print "my pid is ", pid
627 self.pid = pid
628 self.playerList = otherPids
629 self.map = self._init_map_and_cm(mapName)
630 else:
631 print "Connected server but can't play game, map is full or game already started"
632 d.addCallback(connected_server)
633 d.addErrback(err)
634 reactor.callLater(10, d.cancel)
635
636 return True
637
638 '''
639 Helper methods
640 '''
641
643 p = self.players[unit.pid]
644 p.units.pop(unit.uid)
645 if issubclass(type(unit), Building):
646 unit.curVertex.remove_building()
647 else:
648 unit.curVertex.remove_troop(unit)
649 self.map.remove(unit)
650 if unit.pid == self.pid and unit in self.cm.known_objs():
651 self.cm.remove_tricky(unit)
652 unit.isRemoved = True
653 if len(p.units) <= 0:
654 if p.pid == self.pid:
655 self.end_game(False)
656 else:
657 self.end_game(True)
658
659 - def build_unit_common(self, tid, owner, curVertex=None, uid=-1, builder=None, onInit=False):
660
661
662 if owner.idleCPUs or tid == "CPU":
663
664 unitType = eval(tid)
665 newUnit = unitType(curVertex, pid=owner.pid)
666
667
668 if newUnit.slotIndex == -1:
669 print "vertex full"
670 if newUnit.pid == self.pid:
671 utils.play_sound("error.wav")
672 return None
673
674
675 newUnit.add_to_map(self.map)
676 uid = owner.set_unit_uid(newUnit, uid)
677
678
679 if newUnit.pid == self.pid:
680 self.dispatch_event("player_add_unit", tid)
681
682 if newUnit.buildTime == 0 or onInit:
683 newUnit.health = newUnit.maxHealth
684 self._complete_build(newUnit, owner, newUnit.uid,onInit=onInit)
685
686
687 if not onInit:
688 if tid != "CPU":
689 self.start_cpu(newUnit, owner, builder)
690 elif builder:
691 self.remove_unit(builder)
692 if owner == self.player:
693 utils.play_sound("Clock.wav")
694
695
696 if type(curVertex) == Vertex and owner.pid == self.pid:
697 self._show_as(curVertex.asID)
698
699 return newUnit
700
701 print "no idle CPU"
702 if owner.pid == self.pid and not onInit:
703 utils.play_sound("error.wav")
704 return None
705
712
714 for unit in self.selectedUnits:
715 unit.set_is_selected(False, self.map, self.cm, self.player)
716 self.selectedUnits = []
717
728
729 - def start_cpu(self, newUnit, owner, builder):
731
733 if self.serverStarted:
734 return False
735 if type(self) != ClientController:
736 pf = ClientNoteFactory()
737 else:
738 pf = ServerNoteFactory()
739
740 from twisted.internet import reactor
741 endpoint = TCP4ServerEndpoint(reactor, 8750)
742 d = endpoint.listen(pf)
743 def c(port):
744 self.endpoint = port
745 d.addCallback(c)
746 d.addErrback(err)
747 print "started server"
748 self.serverStarted = True
749 return True
750
751
753 self.map.pid = self.pid
754 self.map.draw_map()
755
756
757 for pid in self.playerList:
758 self.players[pid] = Player(pid)
759 self.players[self.pid] = Player(self.pid)
760 self.player = self.players[self.pid]
761
762
763 for pid in self.map.AIPlayers:
764 self.players[pid] = ComputerPlayer(pid)
765
766 for p in self.players.keys():
767 self.init_units(p)
768 ai = self.players[p]
769 if type(ai) == ComputerPlayer and type(self)!= ClientController:
770
771
772 if not self.tutorial:
773 self.schedule_interval(ai.ai_loop, 1)
774 pass
775
776
777
778
780 p = self.players[pid]
781
782 for r in self.map.startingResearch[pid]:
783 p.completedResearch *= RESEARCH[r].uid
784
785 for r in self.map.availResearch:
786 if p.completedResearch % RESEARCH[r].dependencies == 0:
787 p.availableResearch.append(r)
788
790
791 p = self.players[pid]
792 focus = None
793 for vid in reversed(sorted(self.map.startingUnits[pid])):
794 for t in self.map.startingUnits[pid][vid]:
795 newUnit = self.build_unit(t, p, vid=vid, onInit=True)
796 if t == "Server" and pid == self.pid:
797 focus = newUnit.position
798 if t == "CPU":
799 c = self.map.AS[newUnit.curVertex.asID].cores.pop(vid)
800 self.map.AS[newUnit.curVertex.asID].usedCores[vid] = c
801 if focus:
802 self.scroller.set_focus(focus[0] - WINDOW_WIDTH / 2, focus[1] - WINDOW_HEIGHT / 2)
803
805 g = cocos.scene.Scene(self.map, self)
806 g.add(ImageLayer(os.path.join("images", "backgrounds", "notebook-paper.png")), z=BACKGROUND_Z)
807 self.connectedToServer = True
808 cocos.director.director.push(g)
809
810
824
825
826 ClientController.register_event_type("click_on_move")
827 ClientController.register_event_type("click_on_action")
828 ClientController.register_event_type('player_unit_attack')
829 ClientController.register_event_type('player_add_unit')
830