# This file is part of MarMOTS. # # MarMOTS is free software: you can redistribute it and/or modify it under the terms of the GNU Affero # General Public License as published by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # MarMOTS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General # Public License for more details. # # You should have received a copy of the GNU Affero General Public License along with MarMOTS. If not, # see . # # Copyright 2020 Jeremy Penner import ansi_cython as ansi from tpers import TPrs, Partial from scripting import Vm from basetoken import AutoJoiner, AutoKiller, MsgScroller, Mi, MiButton, MiTypein, MiMenu, OvPopup from basetoken import LeaveJoinToken, Selor, OvMenu from util import RgstWrapped from engine import Game, Token, TokenClient, Event from datetime import datetime import traceback class Chl(object): def __init__(self, client, st): self.user = client.cld.user self.st = st.rstrip() class ChlMsg(Chl): def StUser(self): return "<" + self.user + ">" def Col(self): return ansi.WHITE class ChlAct(Chl): def StUser(self): return "* " + self.user def Col(self): return ansi.MAGENTA class ChlSys(Chl): def StUser(self): return self.user def Col(self): return ansi.BLUE class Chat(TPrs): def InitTransient(self): TPrs.InitTransient(self) self.cchl = 100 self.MakeTransient("game", None) self.MakeTransient("evChat", None) def InitPersistent(self): TPrs.InitPersistent(self) self.rgchl = [] def AttachToGame(self, game): with self.SetTransiently(): self.game = game self.evChat = Event(game) def DetachFromGame(self): with self.SetTransiently(): self.game = None self.evChat = None def Add(self, chl): if len(chl.st) > 0: self.rgchl.insert(0, chl) if len(self.rgchl) > self.cchl: self.rgchl = self.rgchl[:self.cchl] self.evChat.fire(chl) def SendMsg(self, client, st): if st.lower().startswith("/me ") and len(st.rstrip()) > 3: self.Add(ChlAct(client, st[4:])) else: self.Add(ChlMsg(client, st)) def CChl(self): return len(self.rgchl) def CUsers(self): return len(self.game.RgclientConnected()) def DrawMsg(self, ascr, x, y, w, h, colBg=ansi.BLACK, ichl=0): yDraw = y + h - 1 for chl in self.rgchl[ichl:]: stUser = chl.StUser() colFg = chl.Col() wMsg = w - len(stUser) - 1 rgstMsg = RgstWrapped(chl.st, wMsg) rgstMsg.reverse() for istMsg, stMsg in enumerate(rgstMsg): if istMsg == len(rgstMsg) - 1: st = "{0} {1:{2}}".format(stUser, stMsg, wMsg) else: st = "{0:{1}} {2:{3}}".format("", len(stUser), stMsg, wMsg) ascr.PutSt(st, x, yDraw, colFg, colBg) yDraw = yDraw - 1 if yDraw == y - 1: return while yDraw >= y: ascr.PutSt("{0:{1}}".format("", w), x, yDraw, ansi.WHITE, colBg) yDraw = yDraw - 1 def DrawUsers(self, ascr, x, y, w, h, colBg=ansi.BLACK, iuser=0): yDraw = y for client in sorted(self.game.RgclientConnected(), lambda c1, c2: cmp(c1.Cldg(self.game).iclient, c2.Cldg(self.game).iclient))[iuser:]: ascr.PutSt("{0:{1}}".format(str(client.Cldg(self.game).iclient) + ") " + client.cld.user, w), x, yDraw, ansi.WHITE, colBg) yDraw = yDraw + 1 if yDraw == y + h: break while yDraw < y + h: ascr.PutSt("{0:{1}}".format("", w), x, yDraw, ansi.WHITE, colBg) yDraw = yDraw + 1 class MiChat(Mi): KWNDMSG = 0 KWNDUSERS = 1 def InitPersistent(self, client, height, chat): Mi.InitPersistent(self) self.client = client self.chat = chat self.height = height assert height > 2 self.kwnd = self.KWNDMSG self.imsg = 0 self.iuser = 0 def StName(self): return "Chat" def HandleKey(self, key): if self.kwnd == self.KWNDUSERS: if key == ansi.K_LEFT: self.kwnd = self.KWNDMSG elif (key == 'a' or key == 'A') and self.iuser > 0: self.iuser = self.iuser - 1 elif (key == 'z' or key == 'Z') and self.iuser < self.chat.CUsers() - 1: self.iuser = self.iuser + 1 else: return False else: #KWNDMSG if key == ansi.K_RIGHT: self.kwnd = self.KWNDUSERS elif (key == 'a' or key == 'A') and self.imsg < self.chat.CChl() - 1: self.imsg = self.imsg + 1 elif (key == 'z' or key == 'Z') and self.imsg > 0: self.imsg = self.imsg - 1 else: return False return True def Height(self, w): return self.height def ColBgDefault(self): return ansi.BLACK def ColBgSelected(self, fPri): return ansi.BLACK def ColFg(self): return ansi.WHITE def FIgnoreParent(self): return True def Draw(self, ascr, x, y, w, selor): wChat = (w / 4) * 3 wUser = (w / 4) - 1 self.chat.DrawMsg(ascr, x, y, wChat, self.height - 1, self.ColBg(selor), self.imsg) for yT in range(y, y + self.height - 1): ascr.PutAch(ansi.MkAch(chr(179)), x + wChat, yT) ascr.PutSt("{0:{1}<{2}}{3}{0:{1}<{4}}".format("", chr(196), wChat, chr(193), wUser), x, y + self.height - 1) self.chat.DrawUsers(ascr, x + wChat + 1, y, wUser, self.height - 1, self.ColBg(selor), self.iuser) if selor.KSel() == Selor.PRI: if self.kwnd == self.KWNDMSG: xBar = x + wChat - 1 pct = 1 - (self.imsg / float(self.chat.CChl())) else: # KWNDUSERS xBar = x + w - 1 pct = self.iuser / float(self.chat.CUsers()) yBar = int(y + (pct * (self.height - 4))) ascr.PutAch(ansi.MkAch("A", ansi.RED, ansi.WHITE), xBar, yBar) ascr.PutAch(ansi.MkAch(chr(186), ansi.RED, ansi.WHITE), xBar, yBar + 1) ascr.PutAch(ansi.MkAch("Z", ansi.RED, ansi.WHITE), xBar, yBar + 2) class Joiner(LeaveJoinToken): def InitPersistent(self, owner): Token.InitPersistent(self, owner) self.msgScroller = self.game.rgtoken("msgscroller")[0] def OnJoin(self, client): with client.Cldg(self.game).SetTransiently() as cldg: cldg.iclient = -1 iclient = 1 rgiclient = [clientT.Cldg(self.game).iclient for clientT in self.game.RgclientConnected()] while iclient in rgiclient: iclient = iclient + 1 cldg.iclient = iclient self.msgScroller.evPost.fire(client.cld.user + "(#" + str(cldg.iclient) + ") has joined! :)") self.game.Chat().Add(ChlSys(client, "has joined")) def OnLeave(self, client): self.msgScroller.evPost.fire(client.cld.user + " has left! :(") self.game.Chat().Add(ChlSys(client, "has left")) class Player(TokenClient): def InitPersistent(self, owner, client): TokenClient.InitPersistent(self, owner, client, "drawable", "player") self.rbot = self.game.vm.AddPlayer() self.vm = self.game.vm chat = self.game.chat miQuit = MiButton("Leave Game", self.client.leaveGame) self.mi = MiMenu([None, MiChat(self.client, 13, chat), MiTypein(Partial(chat.SendMsg, self.client)), None, miQuit, None]) self.ovPopup = OvPopup(self, self.client, self.mi) self.ovDialog = None self.evDialogDismiss = Event(self.game) def die(self): self.vm.RemovePlayer(self.rbot) TokenClient.die(self) def run(self): while True: key = self.EvKey().receive(self) posNew = self.rbot.Pos().Clone() if key == ansi.K_UP: posNew.Up() elif key == ansi.K_DOWN: posNew.Down() elif key == ansi.K_LEFT: posNew.Left() elif key == ansi.K_RIGHT: posNew.Right() elif key == ansi.K_TAB: self.ovPopup.evShow.fire() self.ovPopup.evDone.receive(self) if not posNew.Equals(self.rbot.Pos()): # is there an rbot here? rgrbotCollide = [rbot for rbot in self.vm.Rgrbot() if rbot != self.rbot and rbot.Pos().Equals(posNew)] if len(rgrbotCollide) > 0: try: rgrbotCollide[0].botdef.syntOnTouch.Eval(self) except: traceback.print_exc() elif self.game.board.AchAtPos(posNew) == ansi.achBlank: self.rbot.Move(posNew) def draw(self, ascr, client): if client == self.client: self.game.board.draw(ascr) for rbot in self.vm.Rgrbot(): ascr.PutAch(rbot.Ach(), rbot.Pos().X(), rbot.Pos().Y()) def GlobalMsg(self, stMsg): self.game.rgtoken("msgscroller")[0].evPost.fire(stMsg) def OnDialogOptionSelected(self, synt): self.evDialogDismiss.fire(synt) return True def DismissDialog(self): if self.ovDialog: self.ovDialog.die() self.ovDialog = None def ShowDialog(self, mi): self.DismissDialog() self.ovDialog = OvMenu(self, self.client, mi, OvMenu.MIDDLE) while self.ovDialog: ev, val = Event.select(self, self.evDialogDismiss, self.EvKey()) if ev == self.evDialogDismiss: self.DismissDialog() val.Eval(self) break elif val == ansi.K_TAB: self.DismissDialog() class GameWorld(Game): def InitPersistent(self, board): Game.InitPersistent(self) self.board = board self.vm = Vm(board.defs) self.dtStart = datetime.now() self.chat = Chat() def InitTransient(self): Game.InitTransient(self) if not self.FDead(): self.chat.AttachToGame(self) self.rgdgdie.append(self.chat.DetachFromGame) def Chat(self): return self.chat def StName(self): return self.dtStart.strftime("%b %d, %Y at %I:%M%p") + " [" + str(len(self.RgclientConnected())) + "]" def GetRgclsTokTrans(self): return [[AutoJoiner, Player], MsgScroller, Joiner, AutoKiller] def GetRgtagDraw(self): return ["player", "overlay"]